Merge "Add HC permissions to shell"
diff --git a/Android.bp b/Android.bp
index c0a2abb..2740ccc 100644
--- a/Android.bp
+++ b/Android.bp
@@ -102,13 +102,14 @@
         ":android.hardware.keymaster-V4-java-source",
         ":android.hardware.security.keymint-V3-java-source",
         ":android.hardware.security.secureclock-V1-java-source",
+        ":android.hardware.thermal-V1-java-source",
         ":android.hardware.tv.tuner-V2-java-source",
         ":android.security.apc-java-source",
         ":android.security.authorization-java-source",
         ":android.security.legacykeystore-java-source",
         ":android.security.maintenance-java-source",
         ":android.security.metrics-java-source",
-        ":android.system.keystore2-V1-java-source",
+        ":android.system.keystore2-V3-java-source",
         ":credstore_aidl",
         ":dumpstate_aidl",
         ":framework_native_aidl",
@@ -404,6 +405,7 @@
         "framework-permission-aidl-java",
         "spatializer-aidl-java",
         "audiopolicy-types-aidl-java",
+        "sounddose-aidl-java",
     ],
 }
 
diff --git a/OWNERS b/OWNERS
index d4d1936..09a721f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -15,6 +15,7 @@
 narayan@google.com #{LAST_RESORT_SUGGESTION}
 ogunwale@google.com #{LAST_RESORT_SUGGESTION}
 roosa@google.com #{LAST_RESORT_SUGGESTION}
+smoreland@google.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@android.com #{LAST_RESORT_SUGGESTION}
 svetoslavganov@google.com #{LAST_RESORT_SUGGESTION}
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index dade7c3..53e81c7 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -27,7 +27,6 @@
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.compat.annotation.ChangeId;
-import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -285,11 +284,10 @@
      * The permission {@link Manifest.permission#SCHEDULE_EXACT_ALARM} will be denied, unless the
      * user explicitly allows it from Settings.
      *
-     * TODO (b/226439802): Either enable it in the next SDK or replace it with a better alternative.
      * @hide
      */
     @ChangeId
-    @Disabled
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public static final long SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT = 226439802L;
 
     @UnsupportedAppUsage
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 0c65b99..4242cf8 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -36,7 +36,7 @@
  * Note android.app.job is the better package to put this class, but we can't move it there
  * because that'd break robolectric. Grr.
  *
- * @hide 
+ * @hide
  */
 public class JobSchedulerImpl extends JobScheduler {
     IJobScheduler mBinder;
@@ -109,6 +109,15 @@
     }
 
     @Override
+    public int getPendingJobReason(int jobId) {
+        try {
+            return mBinder.getPendingJobReason(jobId);
+        } catch (RemoteException e) {
+            return PENDING_JOB_REASON_UNDEFINED;
+        }
+    }
+
+    @Override
     public boolean canRunLongJobs() {
         try {
             return mBinder.canRunLongJobs(mContext.getOpPackageName());
@@ -149,7 +158,10 @@
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     @Override
     public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
-        // TODO(255767350): implement
+        try {
+            mBinder.registerUserVisibleJobObserver(observer);
+        } catch (RemoteException e) {
+        }
     }
 
     @RequiresPermission(allOf = {
@@ -157,7 +169,10 @@
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     @Override
     public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
-        // TODO(255767350): implement
+        try {
+            mBinder.unregisterUserVisibleJobObserver(observer);
+        } catch (RemoteException e) {
+        }
     }
 
     @RequiresPermission(allOf = {
@@ -165,6 +180,9 @@
             android.Manifest.permission.INTERACT_ACROSS_USERS_FULL})
     @Override
     public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
-        // TODO(255767350): implement
+        try {
+            mBinder.stopUserVisibleJobsForUser(packageName, userId);
+        } catch (RemoteException e) {
+        }
     }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
index a3390b7..96494ec 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.app.Notification;
 import android.app.job.JobWorkItem;
 
 /**
@@ -104,4 +105,17 @@
      */
     void updateTransferredNetworkBytes(int jobId, in JobWorkItem item,
             long transferredDownloadBytes, long transferredUploadBytes);
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's
+     * lifecycle.
+     * This is required for all user-initiated job and optional for other jobs.
+     *
+     * @param jobId Unique integer used to identify this job.
+     * @param notificationId The ID for this notification, as per
+     *                       {@link android.app.NotificationManager#notify(int, Notification)}.
+     * @param notification The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     */
+    void setNotification(int jobId, int notificationId,
+            in Notification notification, int jobEndNotificationPolicy);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index d2be32e..c87a2af 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -16,6 +16,7 @@
 
 package android.app.job;
 
+import android.app.job.IUserVisibleJobObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
@@ -33,8 +34,15 @@
     void cancelAll();
     ParceledListSlice getAllPendingJobs();
     JobInfo getPendingJob(int jobId);
+    int getPendingJobReason(int jobId);
     boolean canRunLongJobs(String packageName);
     boolean hasRunLongJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
     ParceledListSlice getAllJobSnapshots();
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void registerUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void unregisterUserVisibleJobObserver(in IUserVisibleJobObserver observer);
+    @EnforcePermission(allOf={"MANAGE_ACTIVITY_TASKS", "INTERACT_ACROSS_USERS_FULL"})
+    void stopUserVisibleJobsForUser(String packageName, int userId);
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index ed72530..0205430 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -98,6 +98,12 @@
      */
     public static final int INTERNAL_STOP_REASON_SUCCESSFUL_FINISH =
             JobProtoEnums.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH; // 10.
+    /**
+     * The user stopped the job via some UI (eg. Task Manager).
+     * @hide
+     */
+    public static final int INTERNAL_STOP_REASON_USER_UI_STOP =
+            JobProtoEnums.INTERNAL_STOP_REASON_USER_UI_STOP; // 11.
 
     /**
      * All the stop reason codes. This should be regarded as an immutable array at runtime.
@@ -121,6 +127,7 @@
             INTERNAL_STOP_REASON_DATA_CLEARED,
             INTERNAL_STOP_REASON_RTC_UPDATED,
             INTERNAL_STOP_REASON_SUCCESSFUL_FINISH,
+            INTERNAL_STOP_REASON_USER_UI_STOP,
     };
 
     /**
@@ -141,6 +148,7 @@
             case INTERNAL_STOP_REASON_DATA_CLEARED: return "data_cleared";
             case INTERNAL_STOP_REASON_RTC_UPDATED: return "rtc_updated";
             case INTERNAL_STOP_REASON_SUCCESSFUL_FINISH: return "successful_finish";
+            case INTERNAL_STOP_REASON_USER_UI_STOP: return "user_ui_stop";
             default: return "unknown:" + reasonCode;
         }
     }
@@ -230,7 +238,7 @@
     public static final int STOP_REASON_APP_STANDBY = 12;
     /**
      * The user stopped the job. This can happen either through force-stop, adb shell commands,
-     * or uninstalling.
+     * uninstalling, or some other UI.
      */
     public static final int STOP_REASON_USER = 13;
     /** The system is doing some processing that requires stopping this job. */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 13b6652..659db9f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -23,10 +23,14 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.usage.UsageStatsManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.ClipData;
 import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.NetworkRequest;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PersistableBundle;
@@ -133,6 +137,132 @@
      */
     public static final int RESULT_SUCCESS = 1;
 
+    /** The job doesn't exist. */
+    public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2;
+    /** The job is currently running and is therefore not pending. */
+    public static final int PENDING_JOB_REASON_EXECUTING = -1;
+    /**
+     * There is no known reason why the job is pending.
+     * If additional reasons are added on newer Android versions, the system may return this reason
+     * to apps whose target SDK is not high enough to expect that reason.
+     */
+    public static final int PENDING_JOB_REASON_UNDEFINED = 0;
+    /**
+     * The app is in a state that prevents the job from running
+     * (eg. the {@link JobService} component is disabled).
+     */
+    public static final int PENDING_JOB_REASON_APP = 1;
+    /**
+     * The current standby bucket prevents the job from running.
+     *
+     * @see UsageStatsManager#STANDBY_BUCKET_RESTRICTED
+     */
+    public static final int PENDING_JOB_REASON_APP_STANDBY = 2;
+    /**
+     * The app is restricted from running in the background.
+     *
+     * @see ActivityManager#isBackgroundRestricted()
+     * @see PackageManager#isInstantApp()
+     */
+    public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3;
+    /**
+     * The requested battery-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresBatteryNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4;
+    /**
+     * The requested charging constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresCharging(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5;
+    /**
+     * The requested connectivity constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiredNetwork(NetworkRequest)
+     * @see JobInfo.Builder#setRequiredNetworkType(int)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6;
+    /**
+     * The requested content trigger constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#addTriggerContentUri(JobInfo.TriggerContentUri)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7;
+    /**
+     * The requested idle constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresDeviceIdle(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8;
+    /**
+     * The minimum latency has not transpired.
+     *
+     * @see JobInfo.Builder#setMinimumLatency(long)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9;
+    /**
+     * The system's estimate of when the app will be launched is far away enough to warrant delaying
+     * this job.
+     *
+     * @see JobInfo#isPrefetch()
+     * @see JobInfo.Builder#setPrefetch(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10;
+    /**
+     * The requested storage-not-low constraint is not satisfied.
+     *
+     * @see JobInfo.Builder#setRequiresStorageNotLow(boolean)
+     */
+    public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11;
+    /**
+     * The job is being deferred due to the device state (eg. Doze, battery saver, memory usage,
+     * thermal status, etc.).
+     */
+    public static final int PENDING_JOB_REASON_DEVICE_STATE = 12;
+    /**
+     * JobScheduler thinks it can defer this job to a more optimal running time.
+     */
+    public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13;
+    /**
+     * The app has consumed all of its current quota.
+     *
+     * @see UsageStatsManager#getAppStandbyBucket()
+     * @see JobParameters#STOP_REASON_QUOTA
+     */
+    public static final int PENDING_JOB_REASON_QUOTA = 14;
+    /**
+     * JobScheduler is respecting one of the user's actions (eg. force stop or adb shell commands)
+     * to defer this job.
+     */
+    public static final int PENDING_JOB_REASON_USER = 15;
+
+    /** @hide */
+    @IntDef(prefix = {"PENDING_JOB_REASON_"}, value = {
+            PENDING_JOB_REASON_UNDEFINED,
+            PENDING_JOB_REASON_APP,
+            PENDING_JOB_REASON_APP_STANDBY,
+            PENDING_JOB_REASON_BACKGROUND_RESTRICTION,
+            PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW,
+            PENDING_JOB_REASON_CONSTRAINT_CHARGING,
+            PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY,
+            PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER,
+            PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE,
+            PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY,
+            PENDING_JOB_REASON_CONSTRAINT_PREFETCH,
+            PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW,
+            PENDING_JOB_REASON_DEVICE_STATE,
+            PENDING_JOB_REASON_EXECUTING,
+            PENDING_JOB_REASON_INVALID_JOB_ID,
+            PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION,
+            PENDING_JOB_REASON_QUOTA,
+            PENDING_JOB_REASON_USER,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PendingJobReason {
+    }
+
     /**
      * Schedule a job to be executed.  Will replace any currently scheduled job with the same
      * ID with the new information in the {@link JobInfo}.  If a job with the given ID is currently
@@ -250,6 +380,15 @@
     public abstract @Nullable JobInfo getPendingJob(int jobId);
 
     /**
+     * Returns a reason why the job is pending and not currently executing. If there are multiple
+     * reasons why a job may be pending, this will only return one of them.
+     */
+    @PendingJobReason
+    public int getPendingJobReason(int jobId) {
+        return PENDING_JOB_REASON_UNDEFINED;
+    }
+
+    /**
      * Returns {@code true} if the calling app currently holds the
      * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
      */
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index bad641c..6279959 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -19,13 +19,18 @@
 import static android.app.job.JobScheduler.THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION;
 
 import android.annotation.BytesLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
 import android.compat.Compatibility;
 import android.content.Intent;
 import android.os.IBinder;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * <p>Entry point for the callback from the {@link android.app.job.JobScheduler}.</p>
  * <p>This is the base class that handles asynchronous requests that were previously scheduled. You
@@ -63,6 +68,32 @@
     public static final String PERMISSION_BIND =
             "android.permission.BIND_JOB_SERVICE";
 
+    /**
+     * Detach the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will remain shown even after JobScheduler stops the job.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_DETACH = 0;
+    /**
+     * Cancel and remove the notification supplied to
+     * {@link #setNotification(JobParameters, int, Notification, int)} when the job ends.
+     * The notification will be removed from the notification shade.
+     *
+     * @hide
+     */
+    public static final int JOB_END_NOTIFICATION_POLICY_REMOVE = 1;
+
+    /** @hide */
+    @IntDef(prefix = {"JOB_END_NOTIFICATION_POLICY_"}, value = {
+            JOB_END_NOTIFICATION_POLICY_DETACH,
+            JOB_END_NOTIFICATION_POLICY_REMOVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JobEndNotificationPolicy {
+    }
+
     private JobServiceEngine mEngine;
 
     /** @hide */
@@ -84,9 +115,9 @@
                 public long getTransferredDownloadBytes(@NonNull JobParameters params,
                         @Nullable JobWorkItem item) {
                     if (item == null) {
-                        return JobService.this.getTransferredDownloadBytes();
+                        return JobService.this.getTransferredDownloadBytes(params);
                     } else {
-                        return JobService.this.getTransferredDownloadBytes(item);
+                        return JobService.this.getTransferredDownloadBytes(params, item);
                     }
                 }
 
@@ -95,9 +126,9 @@
                 public long getTransferredUploadBytes(@NonNull JobParameters params,
                         @Nullable JobWorkItem item) {
                     if (item == null) {
-                        return JobService.this.getTransferredUploadBytes();
+                        return JobService.this.getTransferredUploadBytes(params);
                     } else {
-                        return JobService.this.getTransferredUploadBytes(item);
+                        return JobService.this.getTransferredUploadBytes(params, item);
                     }
                 }
             };
@@ -274,7 +305,7 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredDownloadBytes() {
+    public long getTransferredDownloadBytes(@NonNull JobParameters params) {
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
             // non-data transfer jobs.
@@ -298,7 +329,7 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredUploadBytes() {
+    public long getTransferredUploadBytes(@NonNull JobParameters params) {
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
             // non-data transfer jobs.
@@ -324,9 +355,10 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredDownloadBytes(@NonNull JobWorkItem item) {
+    public long getTransferredDownloadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
         if (item == null) {
-            return getTransferredDownloadBytes();
+            return getTransferredDownloadBytes(params);
         }
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -353,9 +385,10 @@
      */
     // TODO(255371817): specify the actual time JS will wait for progress before requesting
     @BytesLong
-    public long getTransferredUploadBytes(@NonNull JobWorkItem item) {
+    public long getTransferredUploadBytes(@NonNull JobParameters params,
+            @NonNull JobWorkItem item) {
         if (item == null) {
-            return getTransferredUploadBytes();
+            return getTransferredUploadBytes(params);
         }
         if (Compatibility.isChangeEnabled(THROW_ON_INVALID_DATA_TRANSFER_IMPLEMENTATION)) {
             // Regular jobs don't have to implement this and JobScheduler won't call this API for
@@ -364,4 +397,42 @@
         }
         return 0;
     }
+
+    /**
+     * Provide JobScheduler with a notification to post and tie to this job's lifecycle.
+     * This is required for all user-initiated jobs
+     * (scheduled via {link JobInfo.Builder#setUserInitiated(boolean)}) and optional for
+     * other jobs. If the app does not call this method for a required notification within
+     * 10 seconds after {@link #onStartJob(JobParameters)} is called,
+     * the system will trigger an ANR and stop this job.
+     *
+     * <p>
+     * Note that certain types of jobs
+     * (e.g. {@link JobInfo.Builder#setDataTransfer data transfer jobs}) may require the
+     * notification to have certain characteristics and their documentation will state
+     * any such requirements.
+     *
+     * <p>
+     * JobScheduler will not remember this notification after the job has finished running,
+     * so apps must call this every time the job is started (if required or desired).
+     *
+     * <p>
+     * If separate jobs use the same notification ID with this API, the most recently provided
+     * notification will be shown to the user, and the
+     * {@code jobEndNotificationPolicy} of the last job to stop will be applied.
+     *
+     * @param params                   The parameters identifying this job, as supplied to
+     *                                 the job in the {@link #onStartJob(JobParameters)} callback.
+     * @param notificationId           The ID for this notification, as per
+     *                                 {@link android.app.NotificationManager#notify(int,
+     *                                 Notification)}.
+     * @param notification             The notification to be displayed.
+     * @param jobEndNotificationPolicy The policy to apply to the notification when the job stops.
+     * @hide
+     */
+    public final void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        mEngine.setNotification(params, notificationId, notification, jobEndNotificationPolicy);
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
index 83296a6..53e452f 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobServiceEngine.java
@@ -21,6 +21,7 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.Service;
 import android.compat.Compatibility;
 import android.content.Intent;
@@ -73,6 +74,8 @@
     private static final int MSG_UPDATE_TRANSFERRED_NETWORK_BYTES = 5;
     /** Message that the client wants to update JobScheduler of the estimated transfer size. */
     private static final int MSG_UPDATE_ESTIMATED_NETWORK_BYTES = 6;
+    /** Message that the client wants to give JobScheduler a notification to tie to the job. */
+    private static final int MSG_SET_NOTIFICATION = 7;
 
     private final IJobService mBinder;
 
@@ -250,6 +253,24 @@
                     args.recycle();
                     break;
                 }
+                case MSG_SET_NOTIFICATION: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final JobParameters params = (JobParameters) args.arg1;
+                    final Notification notification = (Notification) args.arg2;
+                    IJobCallback callback = params.getCallback();
+                    if (callback != null) {
+                        try {
+                            callback.setNotification(params.getJobId(),
+                                    args.argi1, notification, args.argi2);
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "Error providing notification: binder has gone away.");
+                        }
+                    } else {
+                        Log.e(TAG, "setNotification() called for a nonexistent job.");
+                    }
+                    args.recycle();
+                    break;
+                }
                 default:
                     Log.e(TAG, "Unrecognised message received.");
                     break;
@@ -432,4 +453,27 @@
         args.argl2 = uploadBytes;
         mHandler.obtainMessage(MSG_UPDATE_ESTIMATED_NETWORK_BYTES, args).sendToTarget();
     }
-}
\ No newline at end of file
+
+    /**
+     * Give JobScheduler a notification to tie to this job's lifecycle.
+     *
+     * @hide
+     * @see JobService#setNotification(JobParameters, int, Notification, int)
+     */
+    public void setNotification(@NonNull JobParameters params, int notificationId,
+            @NonNull Notification notification,
+            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        if (params == null) {
+            throw new NullPointerException("params");
+        }
+        if (notification == null) {
+            throw new NullPointerException("notification");
+        }
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = params;
+        args.arg2 = notification;
+        args.argi1 = notificationId;
+        args.argi2 = jobEndNotificationPolicy;
+        mHandler.obtainMessage(MSG_SET_NOTIFICATION, args).sendToTarget();
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index f9dd0b3..30986dd 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -193,6 +193,7 @@
     }
 
     private final Object mLock;
+    private final JobNotificationCoordinator mNotificationCoordinator;
     private final JobSchedulerService mService;
     private final Context mContext;
     private final Handler mHandler;
@@ -418,6 +419,7 @@
         mLock = mService.getLock();
         mContext = service.getTestableContext();
         mInjector = injector;
+        mNotificationCoordinator = new JobNotificationCoordinator();
 
         mHandler = JobSchedulerBackgroundThread.getHandler();
 
@@ -451,7 +453,8 @@
                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
         for (int i = 0; i < STANDARD_CONCURRENCY_LIMIT; i++) {
             mIdleContexts.add(
-                    mInjector.createJobServiceContext(mService, this, batteryStats,
+                    mInjector.createJobServiceContext(mService, this,
+                            mNotificationCoordinator, batteryStats,
                             mService.mJobPackageTracker, mContext.getMainLooper()));
         }
     }
@@ -1175,7 +1178,7 @@
 
             if (jobStatus != null && !jsc.isWithinExecutionGuaranteeTime()
                     && restriction.isJobRestricted(jobStatus)) {
-                jsc.cancelExecutingJobLocked(restriction.getReason(),
+                jsc.cancelExecutingJobLocked(restriction.getStopReason(),
                         restriction.getInternalReason(),
                         JobParameters.getInternalReasonCodeDescription(
                                 restriction.getInternalReason()));
@@ -1184,6 +1187,22 @@
     }
 
     @GuardedBy("mLock")
+    void stopUserVisibleJobsLocked(int userId, @NonNull String packageName,
+            @JobParameters.StopReason int reason, int internalReasonCode) {
+        for (int i = mActiveServices.size() - 1; i >= 0; --i) {
+            final JobServiceContext jsc = mActiveServices.get(i);
+            final JobStatus jobStatus = jsc.getRunningJobLocked();
+
+            if (jobStatus != null && userId == jobStatus.getSourceUserId()
+                    && jobStatus.getSourcePackageName().equals(packageName)
+                    && jobStatus.isUserVisibleJob()) {
+                jsc.cancelExecutingJobLocked(reason, internalReasonCode,
+                        JobParameters.getInternalReasonCodeDescription(internalReasonCode));
+            }
+        }
+    }
+
+    @GuardedBy("mLock")
     void stopNonReadyActiveJobsLocked() {
         for (int i = 0; i < mActiveServices.size(); i++) {
             JobServiceContext serviceContext = mActiveServices.get(i);
@@ -1208,7 +1227,7 @@
                 final JobRestriction restriction = mService.checkIfRestricted(running);
                 if (restriction != null) {
                     final int internalReasonCode = restriction.getInternalReason();
-                    serviceContext.cancelExecutingJobLocked(restriction.getReason(),
+                    serviceContext.cancelExecutingJobLocked(restriction.getStopReason(),
                             internalReasonCode,
                             "restricted due to "
                                     + JobParameters.getInternalReasonCodeDescription(
@@ -1324,6 +1343,7 @@
                 mActivePkgStats.add(
                         jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(),
                         packageStats);
+                mService.resetPendingJobReasonCache(jobStatus);
             }
             if (mService.getPendingJobQueue().remove(jobStatus)) {
                 mService.mJobPackageTracker.noteNonpending(jobStatus);
@@ -1670,7 +1690,7 @@
 
     @NonNull
     private JobServiceContext createNewJobServiceContext() {
-        return mInjector.createJobServiceContext(mService, this,
+        return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
                 IBatteryStats.Stub.asInterface(
                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
                 mService.mJobPackageTracker, mContext.getMainLooper());
@@ -2595,10 +2615,11 @@
     static class Injector {
         @NonNull
         JobServiceContext createJobServiceContext(JobSchedulerService service,
-                JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+                JobConcurrencyManager concurrencyManager,
+                JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats,
                 JobPackageTracker tracker, Looper looper) {
-            return new JobServiceContext(service, concurrencyManager, batteryStats,
-                    tracker, looper);
+            return new JobServiceContext(service, concurrencyManager, notificationCoordinator,
+                    batteryStats, tracker, looper);
         }
     }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
new file mode 100644
index 0000000..ce5ade5
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java
@@ -0,0 +1,144 @@
+/*
+ * 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.job;
+
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_DETACH;
+import static android.app.job.JobService.JOB_END_NOTIFICATION_POLICY_REMOVE;
+
+import android.annotation.NonNull;
+import android.app.Notification;
+import android.app.job.JobService;
+import android.content.pm.UserPackage;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.SparseSetArray;
+
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
+
+class JobNotificationCoordinator {
+    private static final String TAG = "JobNotificationCoordinator";
+
+    /**
+     * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs
+     * are associated with each app's notifications.
+     */
+    private final ArrayMap<UserPackage, SparseSetArray<JobServiceContext>> mCurrentAssociations =
+            new ArrayMap<>();
+    /**
+     * Set of NotificationDetails for each running job.
+     */
+    private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails =
+            new ArrayMap<>();
+
+    private static final class NotificationDetails {
+        @NonNull
+        public final UserPackage userPackage;
+        public final int notificationId;
+        public final int appPid;
+        public final int appUid;
+        @JobService.JobEndNotificationPolicy
+        public final int jobEndNotificationPolicy;
+
+        NotificationDetails(@NonNull UserPackage userPackage, int appPid, int appUid,
+                int notificationId,
+                @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+            this.userPackage = userPackage;
+            this.notificationId = notificationId;
+            this.appPid = appPid;
+            this.appUid = appUid;
+            this.jobEndNotificationPolicy = jobEndNotificationPolicy;
+        }
+    }
+
+    private final NotificationManagerInternal mNotificationManagerInternal;
+
+    JobNotificationCoordinator() {
+        mNotificationManagerInternal = LocalServices.getService(NotificationManagerInternal.class);
+    }
+
+    void enqueueNotification(@NonNull JobServiceContext hostingContext, @NonNull String packageName,
+            int callingPid, int callingUid, int notificationId, @NonNull Notification notification,
+            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy);
+        final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext);
+        if (oldDetails != null && oldDetails.notificationId != notificationId) {
+            // App is switching notification IDs. Remove association with the old one.
+            removeNotificationAssociation(hostingContext);
+        }
+        final int userId = UserHandle.getUserId(callingUid);
+        // TODO(260848384): ensure apps can't cancel the notification for user-initiated job
+        //       eg., by calling NotificationManager.cancel/All or deleting the notification channel
+        mNotificationManagerInternal.enqueueNotification(
+                packageName, packageName, callingUid, callingPid, /* tag */ null,
+                notificationId, notification, userId);
+        final UserPackage userPackage = UserPackage.of(userId, packageName);
+        final NotificationDetails details = new NotificationDetails(
+                userPackage, callingPid, callingUid, notificationId, jobEndNotificationPolicy);
+        SparseSetArray<JobServiceContext> appNotifications = mCurrentAssociations.get(userPackage);
+        if (appNotifications == null) {
+            appNotifications = new SparseSetArray<>();
+            mCurrentAssociations.put(userPackage, appNotifications);
+        }
+        appNotifications.add(notificationId, hostingContext);
+        mNotificationDetails.put(hostingContext, details);
+    }
+
+    void removeNotificationAssociation(@NonNull JobServiceContext hostingContext) {
+        final NotificationDetails details = mNotificationDetails.remove(hostingContext);
+        if (details == null) {
+            return;
+        }
+        final SparseSetArray<JobServiceContext> associations =
+                mCurrentAssociations.get(details.userPackage);
+        if (associations == null || !associations.remove(details.notificationId, hostingContext)) {
+            Slog.wtf(TAG, "Association data structures not in sync");
+            return;
+        }
+        ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId);
+        if (associatedContexts == null || associatedContexts.isEmpty()) {
+            // No more jobs using this notification. Apply the final job stop policy.
+            if (details.jobEndNotificationPolicy == JOB_END_NOTIFICATION_POLICY_REMOVE) {
+                final String packageName = details.userPackage.packageName;
+                mNotificationManagerInternal.cancelNotification(
+                        packageName, packageName, details.appUid, details.appPid, /* tag */ null,
+                        details.notificationId, UserHandle.getUserId(details.appUid));
+            }
+        }
+    }
+
+    private void validateNotification(@NonNull String packageName, int callingUid,
+            @NonNull Notification notification,
+            @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) {
+        if (notification == null) {
+            throw new NullPointerException("notification");
+        }
+        if (notification.getSmallIcon() == null) {
+            throw new IllegalArgumentException("small icon required");
+        }
+        if (null == mNotificationManagerInternal.getNotificationChannel(
+                packageName, callingUid, notification.getChannelId())) {
+            throw new IllegalArgumentException("invalid notification channel");
+        }
+        if (jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_DETACH
+                && jobEndNotificationPolicy != JOB_END_NOTIFICATION_POLICY_REMOVE) {
+            throw new IllegalArgumentException("invalid job end notification policy");
+        }
+    }
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index ee08f85..6f58c7ce 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -16,11 +16,14 @@
 
 package com.android.server.job;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -31,6 +34,7 @@
 import android.app.IUidObserver;
 import android.app.compat.CompatChanges;
 import android.app.job.IJobScheduler;
+import android.app.job.IUserVisibleJobObserver;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobProtoEnums;
@@ -38,6 +42,7 @@
 import android.app.job.JobService;
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.UsageStatsManagerInternal;
 import android.compat.annotation.ChangeId;
@@ -68,6 +73,7 @@
 import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -248,6 +254,8 @@
     static final int MSG_UID_IDLE = 7;
     static final int MSG_CHECK_CHANGED_JOB_LIST = 8;
     static final int MSG_CHECK_MEDIA_EXEMPTION = 9;
+    static final int MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS = 10;
+    static final int MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE = 11;
 
     /** List of controllers that will notify this service of updates to jobs. */
     final List<StateController> mControllers;
@@ -258,6 +266,8 @@
     private final List<RestrictingController> mRestrictiveControllers;
     /** Need direct access to this for testing. */
     private final StorageController mStorageController;
+    /** Needed to get estimated transfer time. */
+    private final ConnectivityController mConnectivityController;
     /** Need directly for sending uid state changes */
     private final DeviceIdleJobsController mDeviceIdleJobsController;
     /** Needed to get next estimated launch time. */
@@ -279,6 +289,9 @@
     @GuardedBy("mLock")
     private final SparseArray<String> mCloudMediaProviderPackages = new SparseArray<>();
 
+    private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers =
+            new RemoteCallbackList<>();
+
     private final CountQuotaTracker mQuotaTracker;
     private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
     private static final String QUOTA_TRACKER_SCHEDULE_LOGGED =
@@ -363,6 +376,9 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
 
+    @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
+    private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+
     /**
      * Named indices into standby bucket arrays, for clarity in referring to
      * specific buckets' bookkeeping.
@@ -450,6 +466,13 @@
                         case Constants.KEY_RUNTIME_MIN_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_MIN_EJ_GUARANTEE_MS:
                         case Constants.KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS:
+                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_USER_INITIATED_LIMIT_MS:
+                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR:
+                        case Constants.KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS:
+                        case Constants.KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS:
                             if (!runtimeUpdated) {
                                 mConstants.updateRuntimeConstantsLocked();
                                 runtimeUpdated = true;
@@ -541,6 +564,21 @@
         private static final String KEY_RUNTIME_MIN_EJ_GUARANTEE_MS = "runtime_min_ej_guarantee_ms";
         private static final String KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS =
                 "runtime_min_high_priority_guarantee_ms";
+        private static final String KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+                "runtime_min_data_transfer_guarantee_ms";
+        private static final String KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+                "runtime_data_transfer_limit_ms";
+        private static final String KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+                "runtime_min_user_initiated_guarantee_ms";
+        private static final String KEY_RUNTIME_USER_INITIATED_LIMIT_MS =
+                "runtime_user_initiated_limit_ms";
+        private static final String
+                KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+                "runtime_min_user_initiated_data_transfer_guarantee_buffer_factor";
+        private static final String KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+                "runtime_min_user_initiated_data_transfer_guarantee_ms";
+        private static final String KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+                "runtime_user_initiated_data_transfer_limit_ms";
 
         private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files";
 
@@ -570,6 +608,20 @@
         public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS;
         @VisibleForTesting
         static final long DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS = 5 * MINUTE_IN_MILLIS;
+        public static final long DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+                DEFAULT_RUNTIME_MIN_GUARANTEE_MS;
+        public static final long DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS =
+                DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS;
+        public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS);
+        public static final long DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS =
+                Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS);
+        public static final float
+                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f;
+        public static final long DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+                Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS);
+        public static final long DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+                Math.max(Long.MAX_VALUE, DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS);
         static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true;
         private static final boolean DEFAULT_USE_TARE_POLICY = false;
 
@@ -686,6 +738,49 @@
                 DEFAULT_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS;
 
         /**
+         * The minimum amount of time we try to guarantee normal data transfer jobs will run for.
+         */
+        public long RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS =
+                DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+
+        /**
+         * The maximum amount of time we will let a normal data transfer job run for. This will only
+         * apply if there are no other limits that apply to the specific data transfer job.
+         */
+        public long RUNTIME_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS;
+
+        /**
+         * The minimum amount of time we try to guarantee normal user-initiated jobs will run for.
+         */
+        public long RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS =
+                DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+
+        /**
+         * The maximum amount of time we will let a user-initiated job run for. This will only
+         * apply if there are no other limits that apply to the specific user-initiated job.
+         */
+        public long RUNTIME_USER_INITIATED_LIMIT_MS = DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS;
+
+        /**
+         * A factor to apply to estimated transfer durations for user-initiated data transfer jobs
+         * so that we give some extra time for unexpected situations. This will be at least 1 and
+         * so can just be multiplied with the original value to get the final value.
+         */
+        public float RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR =
+                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR;
+
+        /**
+         * The minimum amount of time we try to guarantee user-initiated data transfer jobs
+         * will run for.
+         */
+        public long RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS =
+                DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+
+        /** The maximum amount of time we will let a user-initiated data transfer job run for. */
+        public long RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS =
+                DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+
+        /**
          * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be
          * saved in a single file.
          */
@@ -787,7 +882,14 @@
                     DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     KEY_RUNTIME_MIN_GUARANTEE_MS, KEY_RUNTIME_MIN_EJ_GUARANTEE_MS,
-                    KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS);
+                    KEY_RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS,
+                    KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                    KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                    KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+                    KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                    KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+                    KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+                    KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS);
 
             // Make sure min runtime for regular jobs is at least 10 minutes.
             RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS,
@@ -805,6 +907,49 @@
             RUNTIME_FREE_QUOTA_MAX_LIMIT_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
                     properties.getLong(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                             DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS));
+            // Make sure min runtime is at least as long as regular jobs.
+            RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+                    properties.getLong(
+                            KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                            DEFAULT_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS));
+            // Max limit should be at least the min guarantee AND the free quota.
+            RUNTIME_DATA_TRANSFER_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    Math.max(RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                            properties.getLong(
+                                    KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+                                    DEFAULT_RUNTIME_DATA_TRANSFER_LIMIT_MS)));
+            // Make sure min runtime is at least as long as regular jobs.
+            RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS = Math.max(RUNTIME_MIN_GUARANTEE_MS,
+                    properties.getLong(
+                            KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                            DEFAULT_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS));
+            // Max limit should be at least the min guarantee AND the free quota.
+            RUNTIME_USER_INITIATED_LIMIT_MS = Math.max(RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
+                    Math.max(RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                            properties.getLong(
+                                    KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+                                    DEFAULT_RUNTIME_USER_INITIATED_LIMIT_MS)));
+            // The buffer factor should be at least 1 (so we don't decrease the time).
+            RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = Math.max(1,
+                    properties.getFloat(
+                            KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                            DEFAULT_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR
+                    ));
+            // Make sure min runtime is at least as long as other user-initiated jobs.
+            RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = Math.max(
+                    RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                    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.
+            // 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,
+                    Math.max(RUNTIME_USER_INITIATED_LIMIT_MS,
+                            properties.getLong(
+                                    KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+                                    DEFAULT_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS)));
         }
 
         private boolean updateTareSettingsLocked(boolean isTareEnabled) {
@@ -853,6 +998,20 @@
                     RUNTIME_MIN_HIGH_PRIORITY_GUARANTEE_MS).println();
             pw.print(KEY_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
                     .println();
+            pw.print(KEY_RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                    RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS).println();
+            pw.print(KEY_RUNTIME_DATA_TRANSFER_LIMIT_MS,
+                    RUNTIME_DATA_TRANSFER_LIMIT_MS).println();
+            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                    RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS).println();
+            pw.print(KEY_RUNTIME_USER_INITIATED_LIMIT_MS,
+                    RUNTIME_USER_INITIATED_LIMIT_MS).println();
+            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR,
+                    RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println();
+            pw.print(KEY_RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+                    RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS).println();
+            pw.print(KEY_RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+                    RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS).println();
 
             pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println();
 
@@ -1357,6 +1516,134 @@
         }
     }
 
+    @JobScheduler.PendingJobReason
+    private int getPendingJobReason(int uid, int jobId) {
+        int reason;
+        // Some apps may attempt to query this frequently, so cache the reason under a separate lock
+        // so that the rest of JS processing isn't negatively impacted.
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason != null) {
+                reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
+                if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
+                    return reason;
+                }
+            }
+        }
+        synchronized (mLock) {
+            reason = getPendingJobReasonLocked(uid, jobId);
+            if (DEBUG) {
+                Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+            }
+        }
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            if (jobIdToReason == null) {
+                jobIdToReason = new SparseIntArray();
+                mPendingJobReasonCache.put(uid, jobIdToReason);
+            }
+            jobIdToReason.put(jobId, reason);
+        }
+        return reason;
+    }
+
+    @JobScheduler.PendingJobReason
+    @GuardedBy("mLock")
+    private int getPendingJobReasonLocked(int uid, int jobId) {
+        // Very similar code to isReadyToBeExecutedLocked.
+
+        JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+        if (job == null) {
+            // Job doesn't exist.
+            return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
+        }
+
+        if (isCurrentlyRunningLocked(job)) {
+            return JobScheduler.PENDING_JOB_REASON_EXECUTING;
+        }
+
+        final boolean jobReady = job.isReady();
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " ready=" + jobReady);
+        }
+
+        if (!jobReady) {
+            return job.getPendingJobReason();
+        }
+
+        final boolean userStarted = areUsersStartedLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " userStarted=" + userStarted);
+        }
+        if (!userStarted) {
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+
+        final boolean backingUp = mBackingUpUids.get(job.getSourceUid());
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " backingUp=" + backingUp);
+        }
+
+        if (backingUp) {
+            // TODO: Should we make a special reason for this?
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        JobRestriction restriction = checkIfRestricted(job);
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " restriction=" + restriction);
+        }
+        if (restriction != null) {
+            return restriction.getPendingReason();
+        }
+
+        // The following can be a little more expensive (especially jobActive, since we need to
+        // go through the array of all potentially active jobs), so we are doing them
+        // later...  but still before checking with the package manager!
+        final boolean jobPending = mPendingJobQueue.contains(job);
+
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " pending=" + jobPending);
+        }
+
+        if (jobPending) {
+            // We haven't started the job for some reason. Presumably, there are too many jobs
+            // running.
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+
+        final boolean jobActive = mConcurrencyManager.isJobRunningLocked(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " active=" + jobActive);
+        }
+        if (jobActive) {
+            return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+        }
+
+        // Validate that the defined package+service is still present & viable.
+        final boolean componentUsable = isComponentUsable(job);
+
+        if (DEBUG) {
+            Slog.v(TAG, "getPendingJobReasonLocked: " + job.toShortString()
+                    + " componentUsable=" + componentUsable);
+        }
+        if (!componentUsable) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
     public JobInfo getPendingJob(int uid, int jobId) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
@@ -1370,6 +1657,14 @@
         }
     }
 
+    private void stopUserVisibleJobsInternal(@NonNull String packageName, int userId) {
+        synchronized (mLock) {
+            mConcurrencyManager.stopUserVisibleJobsLocked(userId, packageName,
+                    JobParameters.STOP_REASON_USER,
+                    JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
+        }
+    }
+
     private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
         // There's no guarantee that the process has been stopped by the time we get
         // here, but since this is a user-initiated action, it should be fine to just
@@ -1389,6 +1684,9 @@
         synchronized (mLock) {
             mJobs.removeJobsOfUnlistedUsers(umi.getUserIds());
         }
+        synchronized (mPendingJobReasonCache) {
+            mPendingJobReasonCache.clear();
+        }
     }
 
     private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
@@ -1705,9 +2003,9 @@
         final FlexibilityController flexibilityController =
                 new FlexibilityController(this, mPrefetchController);
         mControllers.add(flexibilityController);
-        final ConnectivityController connectivityController =
+        mConnectivityController =
                 new ConnectivityController(this, flexibilityController);
-        mControllers.add(connectivityController);
+        mControllers.add(mConnectivityController);
         mControllers.add(new TimeController(this));
         final IdleController idleController = new IdleController(this, flexibilityController);
         mControllers.add(idleController);
@@ -1723,16 +2021,16 @@
         mDeviceIdleJobsController = new DeviceIdleJobsController(this);
         mControllers.add(mDeviceIdleJobsController);
         mQuotaController =
-                new QuotaController(this, backgroundJobsController, connectivityController);
+                new QuotaController(this, backgroundJobsController, mConnectivityController);
         mControllers.add(mQuotaController);
         mControllers.add(new ComponentController(this));
         mTareController =
-                new TareController(this, backgroundJobsController, connectivityController);
+                new TareController(this, backgroundJobsController, mConnectivityController);
         mControllers.add(mTareController);
 
         mRestrictiveControllers = new ArrayList<>();
         mRestrictiveControllers.add(batteryController);
-        mRestrictiveControllers.add(connectivityController);
+        mRestrictiveControllers.add(mConnectivityController);
         mRestrictiveControllers.add(idleController);
 
         // Create restrictions
@@ -1874,6 +2172,8 @@
         jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
         final boolean update = lastJob != null;
         mJobs.add(jobStatus);
+        // Clear potentially cached INVALID_JOB_ID reason.
+        resetPendingJobReasonCache(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 StateController controller = mControllers.get(i);
@@ -1895,6 +2195,13 @@
         // Deal with any remaining work items in the old job.
         jobStatus.stopTrackingJobLocked(incomingJob);
 
+        synchronized (mPendingJobReasonCache) {
+            SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasonCache != null) {
+                reasonCache.delete(jobStatus.getJobId());
+            }
+        }
+
         // Remove from store as well as controllers.
         final boolean removed = mJobs.remove(jobStatus, removeFromPersisted);
         if (!removed) {
@@ -1917,6 +2224,16 @@
         return removed;
     }
 
+    /** Remove the pending job reason for this job from the cache. */
+    void resetPendingJobReasonCache(@NonNull JobStatus jobStatus) {
+        synchronized (mPendingJobReasonCache) {
+            final SparseIntArray reasons = mPendingJobReasonCache.get(jobStatus.getUid());
+            if (reasons != null) {
+                reasons.delete(jobStatus.getJobId());
+            }
+        }
+    }
+
     /** Return {@code true} if the specified job is currently executing. */
     @GuardedBy("mLock")
     public boolean isCurrentlyRunningLocked(JobStatus job) {
@@ -2006,6 +2323,7 @@
         }
         delayMillis =
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
+        // TODO(255767350): demote all jobs to regular for user stops so they don't keep privileges
         JobStatus newJob = new JobStatus(failureToReschedule,
                 elapsedNowMillis + delayMillis,
                 JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
@@ -2210,11 +2528,20 @@
     public void onControllerStateChanged(@Nullable ArraySet<JobStatus> changedJobs) {
         if (changedJobs == null) {
             mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                mPendingJobReasonCache.clear();
+            }
         } else if (changedJobs.size() > 0) {
             synchronized (mLock) {
                 mChangedJobList.addAll(changedJobs);
             }
             mHandler.obtainMessage(MSG_CHECK_CHANGED_JOB_LIST).sendToTarget();
+            synchronized (mPendingJobReasonCache) {
+                for (int i = changedJobs.size() - 1; i >= 0; --i) {
+                    final JobStatus job = changedJobs.valueAt(i);
+                    resetPendingJobReasonCache(job);
+                }
+            }
         }
     }
 
@@ -2347,6 +2674,52 @@
                         args.recycle();
                         break;
                     }
+
+                    case MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS: {
+                        final IUserVisibleJobObserver observer =
+                                (IUserVisibleJobObserver) message.obj;
+                        synchronized (mLock) {
+                            for (int i = mConcurrencyManager.mActiveServices.size() - 1; i >= 0;
+                                    --i) {
+                                JobServiceContext context =
+                                        mConcurrencyManager.mActiveServices.get(i);
+                                final JobStatus jobStatus = context.getRunningJobLocked();
+                                if (jobStatus != null && jobStatus.isUserVisibleJob()) {
+                                    try {
+                                        observer.onUserVisibleJobStateChanged(
+                                                jobStatus.getUserVisibleJobSummary(),
+                                                /* isRunning */ true);
+                                    } catch (RemoteException e) {
+                                        // Will be unregistered automatically by
+                                        // RemoteCallbackList's dead-object tracking,
+                                        // so don't need to remove it here.
+                                        break;
+                                    }
+                                }
+                            }
+                        }
+                        break;
+                    }
+
+                    case MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE: {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final JobServiceContext context = (JobServiceContext) args.arg1;
+                        final JobStatus jobStatus = (JobStatus) args.arg2;
+                        final UserVisibleJobSummary summary = jobStatus.getUserVisibleJobSummary();
+                        final boolean isRunning = args.argi1 == 1;
+                        for (int i = mUserVisibleJobObservers.beginBroadcast() - 1; i >= 0; --i) {
+                            try {
+                                mUserVisibleJobObservers.getBroadcastItem(i)
+                                        .onUserVisibleJobStateChanged(summary, isRunning);
+                            } catch (RemoteException e) {
+                                // Will be unregistered automatically by RemoteCallbackList's
+                                // dead-object tracking, so nothing we need to do here.
+                            }
+                        }
+                        mUserVisibleJobObservers.finishBroadcast();
+                        args.recycle();
+                        break;
+                    }
                 }
                 maybeRunPendingJobsLocked();
             }
@@ -2568,6 +2941,23 @@
                 if (DEBUG) {
                     Slog.d(TAG, "maybeQueueReadyJobsForExecutionLocked: Not running anything.");
                 }
+                final int numRunnableJobs = runnableJobs.size();
+                if (numRunnableJobs > 0) {
+                    synchronized (mPendingJobReasonCache) {
+                        for (int i = 0; i < numRunnableJobs; ++i) {
+                            final JobStatus job = runnableJobs.get(i);
+                            SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+                            if (reasons == null) {
+                                reasons = new SparseIntArray();
+                                mPendingJobReasonCache.put(job.getUid(), reasons);
+                            }
+                            // We're force batching these jobs, so consider it an optimization
+                            // policy reason.
+                            reasons.put(job.getJobId(),
+                                    JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION);
+                        }
+                    }
+                }
             }
 
             // Be ready for next time
@@ -2783,7 +3173,30 @@
     /** Returns the minimum amount of time we should let this job run before timing out. */
     public long getMinJobExecutionGuaranteeMs(JobStatus job) {
         synchronized (mLock) {
-            if (job.shouldTreatAsExpeditedJob()) {
+            final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+                    && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+            if (job.shouldTreatAsUserInitiated()) {
+                if (shouldTreatAsDataTransfer) {
+                    final long estimatedTransferTimeMs =
+                            mConnectivityController.getEstimatedTransferTimeMs(job);
+                    if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) {
+                        return mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS;
+                    }
+                    // Try to give the job at least as much time as we think the transfer will take,
+                    // but cap it at the maximum limit
+                    final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs
+                            * mConstants
+                            .RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR);
+                    return Math.min(mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+                            Math.max(factoredTransferTimeMs,
+                                    mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+                            ));
+                }
+                return mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS;
+            } else if (shouldTreatAsDataTransfer) {
+                // For now, don't increase a bg data transfer's minimum guarantee.
+                return mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS;
+            } else if (job.shouldTreatAsExpeditedJob()) {
                 // Don't guarantee RESTRICTED jobs more than 5 minutes.
                 return job.getEffectiveStandbyBucket() != RESTRICTED_INDEX
                         ? mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS
@@ -2799,6 +3212,16 @@
     /** Returns the maximum amount of time this job could run for. */
     public long getMaxJobExecutionTimeMs(JobStatus job) {
         synchronized (mLock) {
+            final boolean shouldTreatAsDataTransfer = job.getJob().isDataTransfer()
+                    && checkRunLongJobsPermission(job.getSourceUid(), job.getSourcePackageName());
+            if (job.shouldTreatAsUserInitiated()) {
+                if (shouldTreatAsDataTransfer) {
+                    return mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS;
+                }
+                return mConstants.RUNTIME_USER_INITIATED_LIMIT_MS;
+            } else if (shouldTreatAsDataTransfer) {
+                return mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS;
+            }
             return Math.min(mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mConstants.USE_TARE_POLICY
                             ? mTareController.getMaxJobExecutionTimeMsLocked(job)
@@ -2843,6 +3266,16 @@
         return adjustJobBias(bias, job);
     }
 
+    void informObserversOfUserVisibleJobChange(JobServiceContext context, JobStatus jobStatus,
+            boolean isRunning) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = context;
+        args.arg2 = jobStatus;
+        args.argi1 = isRunning ? 1 : 0;
+        mHandler.obtainMessage(MSG_INFORM_OBSERVERS_OF_USER_VISIBLE_JOB_CHANGE, args)
+                .sendToTarget();
+    }
+
     private final class BatteryStateTracker extends BroadcastReceiver {
         /**
          * Track whether we're "charging", where charging means that we're ready to commit to
@@ -3370,6 +3803,18 @@
         }
 
         @Override
+        public int getPendingJobReason(int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
         public JobInfo getPendingJob(int jobId) throws RemoteException {
             final int uid = Binder.getCallingUid();
 
@@ -3436,13 +3881,6 @@
             return checkRunLongJobsPermission(uid, packageName);
         }
 
-        private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
-            // Returns true if both the appop and permission are granted.
-            return PermissionChecker.checkPermissionForPreflight(getContext(),
-                    android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
-                    packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
-        }
-
         /**
          * "dumpsys" infrastructure
          */
@@ -3553,6 +3991,38 @@
                 return new ParceledListSlice<>(snapshots);
             }
         }
+
+        @Override
+        @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+        public void registerUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+            super.registerUserVisibleJobObserver_enforcePermission();
+            if (observer == null) {
+                throw new NullPointerException("observer");
+            }
+            mUserVisibleJobObservers.register(observer);
+            mHandler.obtainMessage(MSG_INFORM_OBSERVER_OF_ALL_USER_VISIBLE_JOBS, observer)
+                    .sendToTarget();
+        }
+
+        @Override
+        @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+        public void unregisterUserVisibleJobObserver(@NonNull IUserVisibleJobObserver observer) {
+            super.unregisterUserVisibleJobObserver_enforcePermission();
+            if (observer == null) {
+                throw new NullPointerException("observer");
+            }
+            mUserVisibleJobObservers.unregister(observer);
+        }
+
+        @Override
+        @EnforcePermission(allOf = {MANAGE_ACTIVITY_TASKS, INTERACT_ACROSS_USERS_FULL})
+        public void stopUserVisibleJobsForUser(@NonNull String packageName, int userId) {
+            super.stopUserVisibleJobsForUser_enforcePermission();
+            if (packageName == null) {
+                throw new NullPointerException("packageName");
+            }
+            JobSchedulerService.this.stopUserVisibleJobsInternal(packageName, userId);
+        }
     }
 
     // Shell command infrastructure: run the given job immediately
@@ -3689,13 +4159,27 @@
         }
     }
 
+    private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
+        // Returns true if both the appop and permission are granted.
+        return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
+                android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
+                packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+    }
+
+    @VisibleForTesting
+    protected ConnectivityController getConnectivityController() {
+        return mConnectivityController;
+    }
+
     // Shell command infrastructure
     int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
             if (uid < 0) {
-                pw.print("unknown("); pw.print(pkgName); pw.println(")");
+                pw.print("unknown(");
+                pw.print(pkgName);
+                pw.println(")");
                 return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
             }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 9aa6b1c..df47f17 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -24,6 +24,7 @@
 import android.annotation.BytesLong;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.Notification;
 import android.app.job.IJobCallback;
 import android.app.job.IJobService;
 import android.app.job.JobInfo;
@@ -109,6 +110,7 @@
     /** Make callbacks to {@link JobSchedulerService} to inform on job completion status. */
     private final JobCompletedListener mCompletedListener;
     private final JobConcurrencyManager mJobConcurrencyManager;
+    private final JobNotificationCoordinator mNotificationCoordinator;
     private final JobSchedulerService mService;
     /** Used for service binding, etc. */
     private final Context mContext;
@@ -235,9 +237,16 @@
                 long downloadBytes, long uploadBytes) {
             doUpdateTransferredNetworkBytes(this, jobId, item, downloadBytes, uploadBytes);
         }
+
+        @Override
+        public void setNotification(int jobId, int notificationId,
+                Notification notification, int jobEndNotificationPolicy) {
+            doSetNotification(this, jobId, notificationId, notification, jobEndNotificationPolicy);
+        }
     }
 
     JobServiceContext(JobSchedulerService service, JobConcurrencyManager concurrencyManager,
+            JobNotificationCoordinator notificationCoordinator,
             IBatteryStats batteryStats, JobPackageTracker tracker, Looper looper) {
         mContext = service.getContext();
         mLock = service.getLock();
@@ -247,6 +256,7 @@
         mJobPackageTracker = tracker;
         mCallbackHandler = new JobServiceHandler(looper);
         mJobConcurrencyManager = concurrencyManager;
+        mNotificationCoordinator = notificationCoordinator;
         mCompletedListener = service;
         mPowerManager = mContext.getSystemService(PowerManager.class);
         mAvailable = true;
@@ -593,6 +603,30 @@
         }
     }
 
+    private void doSetNotification(JobCallback cb, int jodId, int notificationId,
+            Notification notification, int jobEndNotificationPolicy) {
+        final int callingPid = Binder.getCallingPid();
+        final int callingUid = Binder.getCallingUid();
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                if (!verifyCallerLocked(cb)) {
+                    return;
+                }
+                if (callingUid != mRunningJob.getUid()) {
+                    Slog.wtfStack(TAG, "Calling UID isn't the same as running job's UID...");
+                    throw new SecurityException("Can't post notification on behalf of another app");
+                }
+                final String callingPkgName = mRunningJob.getServiceComponent().getPackageName();
+                mNotificationCoordinator.enqueueNotification(this, callingPkgName,
+                        callingPid, callingUid, notificationId,
+                        notification, jobEndNotificationPolicy);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private void doUpdateTransferredNetworkBytes(JobCallback jobCallback, int jobId,
             @Nullable JobWorkItem item, long downloadBytes, long uploadBytes) {
         // TODO(255393346): Make sure apps call this appropriately and monitor for abuse
@@ -884,6 +918,9 @@
                     return;
                 }
                 scheduleOpTimeOutLocked();
+                if (mRunningJob.isUserVisibleJob()) {
+                    mService.informObserversOfUserVisibleJobChange(this, mRunningJob, true);
+                }
                 break;
             default:
                 Slog.e(TAG, "Handling started job but job wasn't starting! Was "
@@ -1116,6 +1153,7 @@
                     JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT,
                     String.valueOf(mRunningJob.getJobId()));
         }
+        mNotificationCoordinator.removeNotificationAssociation(this);
         if (mWakeLock != null) {
             mWakeLock.release();
         }
@@ -1134,6 +1172,9 @@
         mPendingInternalStopReason = 0;
         mPendingDebugStopReason = null;
         removeOpTimeOutLocked();
+        if (completedJob.isUserVisibleJob()) {
+            mService.informObserversOfUserVisibleJobChange(this, completedJob, false);
+        }
         mCompletedListener.onJobCompletedLocked(completedJob, internalStopReason, reschedule);
         mJobConcurrencyManager.onJobCompletedLocked(this, completedJob, workType);
     }
@@ -1157,6 +1198,7 @@
     private void scheduleOpTimeOutLocked() {
         removeOpTimeOutLocked();
 
+        // TODO(260848384): enforce setNotification timeout for user-initiated jobs
         final long timeoutMillis;
         switch (mVerb) {
             case VERB_EXECUTING:
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 145ac52..a1153e3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -91,8 +91,11 @@
 
     /** Threshold to adjust how often we want to write to the db. */
     private static final long JOB_PERSIST_DELAY = 2000L;
-    private static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
+    @VisibleForTesting
+    static final String JOB_FILE_SPLIT_PREFIX = "jobs_";
     private static final int ALL_UIDS = -1;
+    @VisibleForTesting
+    static final int INVALID_UID = -2;
 
     final Object mLock;
     final Object mWriteScheduleLock;    // used solely for invariants around write scheduling
@@ -529,6 +532,25 @@
         return values;
     }
 
+    @VisibleForTesting
+    static int extractUidFromJobFileName(@NonNull File file) {
+        final String fileName = file.getName();
+        if (fileName.startsWith(JOB_FILE_SPLIT_PREFIX)) {
+            try {
+                final int subEnd = fileName.length() - 4; // -4 for ".xml"
+                final int uid = Integer.parseInt(
+                        fileName.substring(JOB_FILE_SPLIT_PREFIX.length(), subEnd));
+                if (uid < 0) {
+                    return INVALID_UID;
+                }
+                return uid;
+            } catch (Exception e) {
+                Slog.e(TAG, "Unexpected file name format", e);
+            }
+        }
+        return INVALID_UID;
+    }
+
     /**
      * Runnable that writes {@link #mJobSet} out to xml.
      * NOTE: This Runnable locks on mLock
@@ -543,6 +565,42 @@
 
             private void prepare() {
                 mCopyAllJobs = !mUseSplitFiles || mPendingJobWriteUids.get(ALL_UIDS);
+                if (mUseSplitFiles) {
+                    // Put the set of changed UIDs in the copy list so that we update each file,
+                    // especially if we've dropped all jobs for that UID.
+                    if (mPendingJobWriteUids.get(ALL_UIDS)) {
+                        // ALL_UIDS is only used when we switch file splitting policy or for tests,
+                        // so going through the file list here shouldn't be
+                        // a large performance hit on user devices.
+
+                        final File[] files;
+                        try {
+                            files = mJobFileDirectory.listFiles();
+                        } catch (SecurityException e) {
+                            Slog.wtf(TAG, "Not allowed to read job file directory", e);
+                            return;
+                        }
+                        if (files == null) {
+                            Slog.wtfStack(TAG, "Couldn't get job file list");
+                        } else {
+                            for (File file : files) {
+                                final int uid = extractUidFromJobFileName(file);
+                                if (uid != INVALID_UID) {
+                                    mJobStoreCopy.put(uid, new ArrayList<>());
+                                }
+                            }
+                        }
+                    } else {
+                        for (int i = 0; i < mPendingJobWriteUids.size(); ++i) {
+                            mJobStoreCopy.put(mPendingJobWriteUids.keyAt(i), new ArrayList<>());
+                        }
+                    }
+                } else {
+                    // Single file mode.
+                    // Put the catchall UID in the copy list so that we update the single file,
+                    // especially if we've dropped all persisted jobs.
+                    mJobStoreCopy.put(ALL_UIDS, new ArrayList<>());
+                }
             }
 
             @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 16dd1672..6166921 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -42,7 +42,6 @@
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
-import android.util.DataUnit;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pools;
@@ -82,6 +81,8 @@
     private static final boolean DEBUG = JobSchedulerService.DEBUG
             || Log.isLoggable(TAG, Log.DEBUG);
 
+    public static final long UNKNOWN_TIME = -1L;
+
     // The networking stack has a hard limit so we can't make this configurable.
     private static final int MAX_NETWORK_CALLBACKS = 125;
     /**
@@ -570,9 +571,8 @@
             // If we don't know the bandwidth, all we can do is hope the job finishes the minimum
             // chunk in time.
             if (bandwidthDown > 0) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidthDown) / 8));
+                final long estimatedMillis =
+                        calculateTransferTimeMs(minimumChunkBytes, bandwidthDown);
                 if (estimatedMillis > maxJobExecutionTimeMs) {
                     // If we'd never finish the minimum chunk before the timeout, we'd be insane!
                     Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over "
@@ -585,9 +585,8 @@
             final long bandwidthUp = capabilities.getLinkUpstreamBandwidthKbps();
             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
             if (bandwidthUp > 0) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((minimumChunkBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidthUp) / 8));
+                final long estimatedMillis =
+                        calculateTransferTimeMs(minimumChunkBytes, bandwidthUp);
                 if (estimatedMillis > maxJobExecutionTimeMs) {
                     // If we'd never finish the minimum chunk before the timeout, we'd be insane!
                     Slog.w(TAG, "Minimum chunk " + minimumChunkBytes + " bytes over " + bandwidthUp
@@ -615,9 +614,7 @@
             final long bandwidth = capabilities.getLinkDownstreamBandwidthKbps();
             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
             if (bandwidth > 0) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((downloadBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+                final long estimatedMillis = calculateTransferTimeMs(downloadBytes, bandwidth);
                 if (estimatedMillis > maxJobExecutionTimeMs) {
                     // If we'd never finish before the timeout, we'd be insane!
                     Slog.w(TAG, "Estimated " + downloadBytes + " download bytes over " + bandwidth
@@ -633,9 +630,7 @@
             final long bandwidth = capabilities.getLinkUpstreamBandwidthKbps();
             // If we don't know the bandwidth, all we can do is hope the job finishes in time.
             if (bandwidth > 0) {
-                // Divide by 8 to convert bits to bytes.
-                final long estimatedMillis = ((uploadBytes * DateUtils.SECOND_IN_MILLIS)
-                        / (DataUnit.KIBIBYTES.toBytes(bandwidth) / 8));
+                final long estimatedMillis = calculateTransferTimeMs(uploadBytes, bandwidth);
                 if (estimatedMillis > maxJobExecutionTimeMs) {
                     // If we'd never finish before the timeout, we'd be insane!
                     Slog.w(TAG, "Estimated " + uploadBytes + " upload bytes over " + bandwidth
@@ -649,6 +644,48 @@
         return false;
     }
 
+    /**
+     * Return the estimated amount of time this job will be transferring data,
+     * based on the current network speed.
+     */
+    public long getEstimatedTransferTimeMs(JobStatus jobStatus) {
+        final long downloadBytes = jobStatus.getEstimatedNetworkDownloadBytes();
+        final long uploadBytes = jobStatus.getEstimatedNetworkUploadBytes();
+        if (downloadBytes == JobInfo.NETWORK_BYTES_UNKNOWN
+                && uploadBytes == JobInfo.NETWORK_BYTES_UNKNOWN) {
+            return UNKNOWN_TIME;
+        }
+        if (jobStatus.network == null) {
+            // This job doesn't have a network assigned.
+            return UNKNOWN_TIME;
+        }
+        NetworkCapabilities capabilities = getNetworkCapabilities(jobStatus.network);
+        if (capabilities == null) {
+            return UNKNOWN_TIME;
+        }
+        final long estimatedDownloadTimeMs = calculateTransferTimeMs(downloadBytes,
+                capabilities.getLinkDownstreamBandwidthKbps());
+        final long estimatedUploadTimeMs = calculateTransferTimeMs(uploadBytes,
+                capabilities.getLinkUpstreamBandwidthKbps());
+        if (estimatedDownloadTimeMs == UNKNOWN_TIME) {
+            return estimatedUploadTimeMs;
+        } else if (estimatedUploadTimeMs == UNKNOWN_TIME) {
+            return estimatedDownloadTimeMs;
+        }
+        return estimatedDownloadTimeMs + estimatedUploadTimeMs;
+    }
+
+    @VisibleForTesting
+    static long calculateTransferTimeMs(long transferBytes, long bandwidthKbps) {
+        if (transferBytes == JobInfo.NETWORK_BYTES_UNKNOWN || bandwidthKbps <= 0) {
+            return UNKNOWN_TIME;
+        }
+        return (transferBytes * DateUtils.SECOND_IN_MILLIS)
+                // Multiply by 1000 to convert kilobits to bits.
+                // Divide by 8 to convert bits to bytes.
+                / (bandwidthKbps * 1000 / 8);
+    }
+
     private static boolean isCongestionDelayed(JobStatus jobStatus, Network network,
             NetworkCapabilities capabilities, Constants constants) {
         // If network is congested, and job is less than 50% through the
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index da20684..419127e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -29,10 +29,13 @@
 import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS;
 
 import android.annotation.ElapsedRealtimeLong;
+import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.app.job.JobWorkItem;
+import android.app.job.UserVisibleJobSummary;
 import android.content.ClipData;
 import android.content.ComponentName;
 import android.net.Network;
@@ -106,6 +109,13 @@
     static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint
     static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint
 
+    private static final int IMPLICIT_CONSTRAINTS = 0
+            | CONSTRAINT_BACKGROUND_NOT_RESTRICTED
+            | CONSTRAINT_DEVICE_NOT_DOZING
+            | CONSTRAINT_FLEXIBLE
+            | CONSTRAINT_TARE_WEALTH
+            | CONSTRAINT_WITHIN_QUOTA;
+
     // The following set of dynamic constraints are for specific use cases (as explained in their
     // relative naming and comments). Right now, they apply different constraints, which is fine,
     // but if in the future, we have overlapping dynamic constraint sets, removing one constraint
@@ -446,6 +456,12 @@
      */
     private boolean mExpeditedTareApproved;
 
+    /**
+     * Summary describing this job. Lazily created in {@link #getUserVisibleJobSummary()}
+     * since not every job will need it.
+     */
+    private UserVisibleJobSummary mUserVisibleJobSummary;
+
     /////// Booleans that track if a job is ready to run. They should be updated whenever dependent
     /////// states change.
 
@@ -1329,6 +1345,36 @@
     }
 
     /**
+     * @return true if the job was scheduled as a user-initiated job and it hasn't been downgraded
+     * for any reason.
+     */
+    public boolean shouldTreatAsUserInitiated() {
+        // TODO(248386641): implement
+        return false;
+    }
+
+    /**
+     * Return a summary that uniquely identifies the underlying job.
+     */
+    @NonNull
+    public UserVisibleJobSummary getUserVisibleJobSummary() {
+        if (mUserVisibleJobSummary == null) {
+            mUserVisibleJobSummary = new UserVisibleJobSummary(
+                    callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+        }
+        return mUserVisibleJobSummary;
+    }
+
+    /**
+     * @return true if this is a job whose execution should be made visible to the user.
+     */
+    public boolean isUserVisibleJob() {
+        // TODO(255767350): limit to user-initiated jobs
+        // Placeholder implementation until we have the code in
+        return shouldTreatAsExpeditedJob();
+    }
+
+    /**
      * @return true if the job is exempted from Doze restrictions and therefore allowed to run
      * in Doze.
      */
@@ -1613,6 +1659,101 @@
         }
     }
 
+    /**
+     * If {@link #isReady()} returns false, this will return a single reason why the job isn't
+     * ready. If {@link #isReady()} returns true, this will return
+     * {@link JobScheduler#PENDING_JOB_REASON_UNDEFINED}.
+     */
+    @JobScheduler.PendingJobReason
+    public int getPendingJobReason() {
+        final int unsatisfiedConstraints = ~satisfiedConstraints
+                & (requiredConstraints | mDynamicConstraints | IMPLICIT_CONSTRAINTS);
+        if ((CONSTRAINT_BACKGROUND_NOT_RESTRICTED & unsatisfiedConstraints) != 0) {
+            // The BACKGROUND_NOT_RESTRICTED constraint could be unsatisfied either because
+            // the app is background restricted, or because we're restricting background work
+            // in battery saver. Assume that background restriction is the reason apps that
+            // jobs are not ready, and battery saver otherwise.
+            // This has the benefit of being consistent for background restricted apps
+            // (they'll always get BACKGROUND_RESTRICTION) as the reason, regardless of
+            // battery saver state.
+            if (mIsUserBgRestricted) {
+                return JobScheduler.PENDING_JOB_REASON_BACKGROUND_RESTRICTION;
+            }
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_BATTERY_NOT_LOW & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_BATTERY_NOT_LOW & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CHARGING & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_CHARGING & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CHARGING;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_CONNECTIVITY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY;
+        }
+        if ((CONSTRAINT_CONTENT_TRIGGER & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER;
+        }
+        if ((CONSTRAINT_DEVICE_NOT_DOZING & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_DEVICE_STATE;
+        }
+        if ((CONSTRAINT_FLEXIBLE & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION;
+        }
+        if ((CONSTRAINT_IDLE & unsatisfiedConstraints) != 0) {
+            if ((CONSTRAINT_IDLE & requiredConstraints) != 0) {
+                // The developer requested this constraint, so it makes sense to return the
+                // explicit constraint reason.
+                return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE;
+            }
+            // Hard-coding right now since the current dynamic constraint sets don't overlap
+            // TODO: return based on active dynamic constraint sets when they start overlapping
+            return JobScheduler.PENDING_JOB_REASON_APP_STANDBY;
+        }
+        if ((CONSTRAINT_PREFETCH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_PREFETCH;
+        }
+        if ((CONSTRAINT_STORAGE_NOT_LOW & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW;
+        }
+        if ((CONSTRAINT_TARE_WEALTH & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+        if ((CONSTRAINT_TIMING_DELAY & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY;
+        }
+        if ((CONSTRAINT_WITHIN_QUOTA & unsatisfiedConstraints) != 0) {
+            return JobScheduler.PENDING_JOB_REASON_QUOTA;
+        }
+
+        if (getEffectiveStandbyBucket() == NEVER_INDEX) {
+            Slog.wtf(TAG, "App in NEVER bucket querying pending job reason");
+            // The user hasn't officially launched this app.
+            return JobScheduler.PENDING_JOB_REASON_USER;
+        }
+        if (serviceProcessName != null) {
+            return JobScheduler.PENDING_JOB_REASON_APP;
+        }
+
+        if (!isReady()) {
+            Slog.wtf(TAG, "Unknown reason job isn't ready");
+        }
+        return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
+    }
+
     /** @return whether or not the @param constraint is satisfied */
     public boolean isConstraintSatisfied(int constraint) {
         return (satisfiedConstraints&constraint) != 0;
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
index 4067541..7aab67a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/JobRestriction.java
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.util.IndentingPrintWriter;
 import android.util.proto.ProtoOutputStream;
 
@@ -28,20 +29,23 @@
  * Used by {@link JobSchedulerService} to impose additional restrictions regarding whether jobs
  * should be scheduled or not based on the state of the system/device.
  * Every restriction is associated with exactly one stop reason, which could be retrieved using
- * {@link #getReason()} (and the internal reason via {@link #getInternalReason()}).
+ * {@link #getStopReason()}, one pending reason (retrievable via {@link #getPendingReason()},
+ * (and the internal reason via {@link #getInternalReason()}).
  * Note, that this is not taken into account for the jobs that have
  * {@link JobInfo#BIAS_FOREGROUND_SERVICE} bias or higher.
  */
 public abstract class JobRestriction {
 
     final JobSchedulerService mService;
-    private final int mReason;
+    private final int mStopReason;
+    private final int mPendingReason;
     private final int mInternalReason;
 
-    JobRestriction(JobSchedulerService service, @JobParameters.StopReason int reason,
-            int internalReason) {
+    protected JobRestriction(JobSchedulerService service, @JobParameters.StopReason int stopReason,
+            @JobScheduler.PendingJobReason int pendingReason, int internalReason) {
         mService = service;
-        mReason = reason;
+        mPendingReason = pendingReason;
+        mStopReason = stopReason;
         mInternalReason = internalReason;
     }
 
@@ -70,10 +74,15 @@
     public void dumpConstants(ProtoOutputStream proto) {
     }
 
-    /** @return reason code for the Restriction. */
+    @JobScheduler.PendingJobReason
+    public final int getPendingReason() {
+        return mPendingReason;
+    }
+
+    /** @return stop reason code for the Restriction. */
     @JobParameters.StopReason
-    public final int getReason() {
-        return mReason;
+    public final int getStopReason() {
+        return mStopReason;
     }
 
     public final int getInternalReason() {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index ca2fd60..830031e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -18,6 +18,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
 import android.os.PowerManager;
 import android.os.PowerManager.OnThermalStatusChangedListener;
 import android.util.IndentingPrintWriter;
@@ -42,6 +43,7 @@
 
     public ThermalStatusRestriction(JobSchedulerService service) {
         super(service, JobParameters.STOP_REASON_DEVICE_STATE,
+                JobScheduler.PENDING_JOB_REASON_DEVICE_STATE,
                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index b1c8b51..1a775b4 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -111,6 +111,7 @@
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseLongArray;
+import android.util.SparseSetArray;
 import android.util.TimeUtils;
 import android.view.Display;
 import android.widget.Toast;
@@ -463,6 +464,12 @@
     private final Map<String, String> mAppStandbyProperties = new ArrayMap<>();
 
     /**
+     * Set of apps that were restored via backup & restore, per user, that need their
+     * standby buckets to be adjusted when installed.
+     */
+    private final SparseSetArray<String> mAppsToRestoreToRare = new SparseSetArray<>();
+
+    /**
      * List of app-ids of system packages, populated on boot, when system services are ready.
      */
     private final ArrayList<Integer> mSystemPackagesAppIds = new ArrayList<>();
@@ -1663,18 +1670,29 @@
         final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED;
         final long nowElapsed = mInjector.elapsedRealtime();
         for (String packageName : restoredApps) {
-            // If the package is not installed, don't allow the bucket to be set.
+            // If the package is not installed, don't allow the bucket to be set. Instead, add it
+            // to a list of all packages whose buckets need to be adjusted when installed.
             if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
-                Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+                Slog.i(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+                mAppsToRestoreToRare.add(userId, packageName);
                 continue;
             }
 
-            final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false);
-            // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
-            if (standbyBucket == STANDBY_BUCKET_NEVER) {
-                setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason,
-                        nowElapsed, false);
-            }
+            restoreAppToRare(packageName, userId, nowElapsed, reason);
+        }
+        // Clear out the list of restored apps that need to have their standby buckets adjusted
+        // if they still haven't been installed eight hours after restore.
+        // Note: if the device reboots within these first 8 hours, this list will be lost since it's
+        // not persisted - this is the expected behavior for now and may be updated in the future.
+        mHandler.postDelayed(() -> mAppsToRestoreToRare.remove(userId), 8 * ONE_HOUR);
+    }
+
+    /** Adjust the standby bucket of the given package for the user to RARE. */
+    private void restoreAppToRare(String pkgName, int userId, long nowElapsed, int reason) {
+        final int standbyBucket = getAppStandbyBucket(pkgName, userId, nowElapsed, false);
+        // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
+        if (standbyBucket == STANDBY_BUCKET_NEVER) {
+            setAppStandbyBucket(pkgName, userId, STANDBY_BUCKET_RARE, reason, nowElapsed, false);
         }
     }
 
@@ -2176,8 +2194,15 @@
                     Intent.ACTION_PACKAGE_ADDED.equals(action))) {
                 if (intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
                     maybeUnrestrictBuggyApp(pkgName, userId);
-                } else {
+                } else if (!Intent.ACTION_PACKAGE_ADDED.equals(action)) {
                     clearAppIdleForPackage(pkgName, userId);
+                } else {
+                    // Package was just added and it's not being replaced.
+                    if (mAppsToRestoreToRare.contains(userId, pkgName)) {
+                        restoreAppToRare(pkgName, userId, mInjector.elapsedRealtime(),
+                                REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED);
+                        mAppsToRestoreToRare.remove(userId, pkgName);
+                    }
                 }
             }
             synchronized (mSystemExemptionAppOpMode) {
diff --git a/api/Android.bp b/api/Android.bp
index b0ce9af..318748e 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -118,9 +118,11 @@
     ],
     system_server_classpath: [
         "service-art",
+        "service-configinfrastructure",
         "service-healthconnect",
         "service-media-s",
         "service-permission",
+        "service-rkp",
         "service-sdksandbox",
     ],
 }
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index c4d90c6..80512f7 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1112,6 +1112,11 @@
 
         int nextReadPos;
 
+        if (strlen(l) == 0) {
+            s = ++endl;
+            continue;
+        }
+
         int topLineNumbers = sscanf(l, "%d %d %d %d", &width, &height, &fps, &progress);
         if (topLineNumbers == 3 || topLineNumbers == 4) {
             // SLOGD("> w=%d, h=%d, fps=%d, progress=%d", width, height, fps, progress);
diff --git a/core/api/current.txt b/core/api/current.txt
index 0f34e42..bedd6ee 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -1042,7 +1042,6 @@
     field public static final int max = 16843062; // 0x1010136
     field public static final int maxAspectRatio = 16844128; // 0x1010560
     field public static final int maxButtonHeight = 16844029; // 0x10104fd
-    field public static final int maxConcurrentSessionsCount;
     field public static final int maxDate = 16843584; // 0x1010340
     field public static final int maxEms = 16843095; // 0x1010157
     field public static final int maxHeight = 16843040; // 0x1010120
@@ -7283,8 +7282,8 @@
     method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri);
     method public int getDesiredMinimumHeight();
     method public int getDesiredMinimumWidth();
-    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
-    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable();
     method public static android.app.WallpaperManager getInstance(android.content.Context);
     method @Nullable public android.app.WallpaperColors getWallpaperColors(int);
     method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int);
@@ -7294,8 +7293,8 @@
     method public boolean hasResourceWallpaper(@RawRes int);
     method public boolean isSetWallpaperAllowed();
     method public boolean isWallpaperSupported();
-    method public android.graphics.drawable.Drawable peekDrawable();
-    method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
+    method @Nullable public android.graphics.drawable.Drawable peekDrawable();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable();
     method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener);
     method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException;
@@ -7475,7 +7474,7 @@
     method public boolean getBluetoothContactSharingDisabled(@NonNull android.content.ComponentName);
     method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
     method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
-    method @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
+    method @Deprecated @Nullable public java.util.Set<java.lang.String> getCrossProfileCalendarPackages(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
     method public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
     method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
@@ -7624,7 +7623,7 @@
     method @Deprecated public void setCertInstallerPackage(@NonNull android.content.ComponentName, @Nullable String) throws java.lang.SecurityException;
     method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
-    method public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
+    method @Deprecated public void setCrossProfileCalendarPackages(@NonNull android.content.ComponentName, @Nullable java.util.Set<java.lang.String>);
     method public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
@@ -8014,6 +8013,9 @@
     field public static final int TAG_MEDIA_UNMOUNT = 210014; // 0x3345e
     field public static final int TAG_OS_SHUTDOWN = 210010; // 0x3345a
     field public static final int TAG_OS_STARTUP = 210009; // 0x33459
+    field public static final int TAG_PACKAGE_INSTALLED = 210041; // 0x33479
+    field public static final int TAG_PACKAGE_UNINSTALLED = 210043; // 0x3347b
+    field public static final int TAG_PACKAGE_UPDATED = 210042; // 0x3347a
     field public static final int TAG_PASSWORD_CHANGED = 210036; // 0x33474
     field public static final int TAG_PASSWORD_COMPLEXITY_REQUIRED = 210035; // 0x33473
     field public static final int TAG_PASSWORD_COMPLEXITY_SET = 210017; // 0x33461
@@ -8485,7 +8487,26 @@
     method public abstract int enqueue(@NonNull android.app.job.JobInfo, @NonNull android.app.job.JobWorkItem);
     method @NonNull public abstract java.util.List<android.app.job.JobInfo> getAllPendingJobs();
     method @Nullable public abstract android.app.job.JobInfo getPendingJob(int);
+    method public int getPendingJobReason(int);
     method public abstract int schedule(@NonNull android.app.job.JobInfo);
+    field public static final int PENDING_JOB_REASON_APP = 1; // 0x1
+    field public static final int PENDING_JOB_REASON_APP_STANDBY = 2; // 0x2
+    field public static final int PENDING_JOB_REASON_BACKGROUND_RESTRICTION = 3; // 0x3
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_BATTERY_NOT_LOW = 4; // 0x4
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CHARGING = 5; // 0x5
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONNECTIVITY = 6; // 0x6
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_CONTENT_TRIGGER = 7; // 0x7
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_MINIMUM_LATENCY = 9; // 0x9
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_PREFETCH = 10; // 0xa
+    field public static final int PENDING_JOB_REASON_CONSTRAINT_STORAGE_NOT_LOW = 11; // 0xb
+    field public static final int PENDING_JOB_REASON_DEVICE_STATE = 12; // 0xc
+    field public static final int PENDING_JOB_REASON_EXECUTING = -1; // 0xffffffff
+    field public static final int PENDING_JOB_REASON_INVALID_JOB_ID = -2; // 0xfffffffe
+    field public static final int PENDING_JOB_REASON_JOB_SCHEDULER_OPTIMIZATION = 13; // 0xd
+    field public static final int PENDING_JOB_REASON_QUOTA = 14; // 0xe
+    field public static final int PENDING_JOB_REASON_UNDEFINED = 0; // 0x0
+    field public static final int PENDING_JOB_REASON_USER = 15; // 0xf
     field public static final int RESULT_FAILURE = 0; // 0x0
     field public static final int RESULT_SUCCESS = 1; // 0x1
   }
@@ -10347,6 +10368,7 @@
     field @Deprecated @RequiresPermission("android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS") public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
     field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
     field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+    field public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
     field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
     field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
@@ -10595,6 +10617,7 @@
     field public static final String EXTRA_UID = "android.intent.extra.UID";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
+    field public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
     field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -11669,7 +11692,7 @@
 
   public class PackageInstaller {
     method public void abandonSession(int);
-    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
+    method public void checkInstallConstraints(@NonNull java.util.List<java.lang.String>, @NonNull android.content.pm.PackageInstaller.InstallConstraints, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.content.pm.PackageInstaller.InstallConstraintsResult>);
     method public int createSession(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
     method @Deprecated @Nullable public android.content.pm.PackageInstaller.SessionInfo getActiveStagedSession();
     method @NonNull public java.util.List<android.content.pm.PackageInstaller.SessionInfo> getActiveStagedSessions();
@@ -13023,6 +13046,14 @@
 
 package android.credentials {
 
+  public class ClearCredentialStateException extends java.lang.Exception {
+    ctor public ClearCredentialStateException(@NonNull String, @Nullable String);
+    ctor public ClearCredentialStateException(@NonNull String, @Nullable String, @Nullable Throwable);
+    ctor public ClearCredentialStateException(@NonNull String, @Nullable Throwable);
+    ctor public ClearCredentialStateException(@NonNull String);
+    field @NonNull public final String errorType;
+  }
+
   public final class ClearCredentialStateRequest implements android.os.Parcelable {
     ctor public ClearCredentialStateRequest(@NonNull android.os.Bundle);
     method public int describeContents();
@@ -13031,6 +13062,14 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.credentials.ClearCredentialStateRequest> CREATOR;
   }
 
+  public class CreateCredentialException extends java.lang.Exception {
+    ctor public CreateCredentialException(@NonNull String, @Nullable String);
+    ctor public CreateCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
+    ctor public CreateCredentialException(@NonNull String, @Nullable Throwable);
+    ctor public CreateCredentialException(@NonNull String);
+    field @NonNull public final String errorType;
+  }
+
   public final class CreateCredentialRequest implements android.os.Parcelable {
     ctor public CreateCredentialRequest(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
@@ -13060,24 +13099,24 @@
   }
 
   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.CredentialManagerException>);
-    method public void executeCreateCredential(@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.CredentialManagerException>);
-    method public void executeGetCredential(@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.CredentialManagerException>);
+    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 executeCreateCredential(@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 public void executeGetCredential(@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>);
   }
 
-  public class CredentialManagerException extends java.lang.Exception {
-    ctor public CredentialManagerException(int, @Nullable String);
-    ctor public CredentialManagerException(int, @Nullable String, @Nullable Throwable);
-    ctor public CredentialManagerException(int, @Nullable Throwable);
-    ctor public CredentialManagerException(int);
-    field public static final int ERROR_UNKNOWN = 0; // 0x0
-    field public final int errorCode;
+  public class GetCredentialException extends java.lang.Exception {
+    ctor public GetCredentialException(@NonNull String, @Nullable String);
+    ctor public GetCredentialException(@NonNull String, @Nullable String, @Nullable Throwable);
+    ctor public GetCredentialException(@NonNull String, @Nullable Throwable);
+    ctor public GetCredentialException(@NonNull String);
+    field @NonNull public final String errorType;
   }
 
   public final class GetCredentialOption implements android.os.Parcelable {
-    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, boolean);
+    ctor public GetCredentialOption(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.Bundle, boolean);
     method public int describeContents();
-    method @NonNull public android.os.Bundle getData();
+    method @NonNull public android.os.Bundle getCandidateQueryData();
+    method @NonNull public android.os.Bundle getCredentialRetrievalData();
     method @NonNull public String getType();
     method public boolean requireSystemProvider();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -17805,6 +17844,7 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_MAX_ANALOG_SENSITIVITY;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.graphics.Rect[]> SENSOR_OPTICAL_BLACK_REGIONS;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_ORIENTATION;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_READOUT_TIMESTAMP;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> SENSOR_REFERENCE_ILLUMINANT1;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Byte> SENSOR_REFERENCE_ILLUMINANT2;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> SHADING_AVAILABLE_MODES;
@@ -18173,6 +18213,8 @@
     field public static final int SENSOR_INFO_TIMESTAMP_SOURCE_UNKNOWN = 0; // 0x0
     field public static final int SENSOR_PIXEL_MODE_DEFAULT = 0; // 0x0
     field public static final int SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION = 1; // 0x1
+    field public static final int SENSOR_READOUT_TIMESTAMP_HARDWARE = 1; // 0x1
+    field public static final int SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED = 0; // 0x0
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_CLOUDY_WEATHER = 10; // 0xa
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT = 14; // 0xe
     field public static final int SENSOR_REFERENCE_ILLUMINANT1_D50 = 23; // 0x17
@@ -18286,6 +18328,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.CaptureRequest> CREATOR;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EDGE_MODE;
+    field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> EXTENSION_STRENGTH;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> FLASH_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<java.lang.Integer> HOT_PIXEL_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureRequest.Key<android.location.Location> JPEG_GPS_LOCATION;
@@ -18378,6 +18421,8 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> CONTROL_ZOOM_RATIO;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> DISTORTION_CORRECTION_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EDGE_MODE;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_CURRENT_TYPE;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> EXTENSION_STRENGTH;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_MODE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> FLASH_STATE;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> HOT_PIXEL_MODE;
@@ -20181,6 +20226,15 @@
 
 }
 
+package android.location.altitude {
+
+  public final class AltitudeConverter {
+    ctor public AltitudeConverter();
+    method @WorkerThread public void addMslAltitudeToLocation(@NonNull android.content.Context, @NonNull android.location.Location) throws java.io.IOException;
+  }
+
+}
+
 package android.location.provider {
 
   public final class ProviderProperties implements android.os.Parcelable {
@@ -20495,10 +20549,12 @@
     method public int abandonAudioFocusRequest(@NonNull android.media.AudioFocusRequest);
     method public void addOnCommunicationDeviceChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
     method public void addOnModeChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnModeChangedListener);
+    method public void addOnPreferredMixerAttributesChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener);
     method public void adjustStreamVolume(int, int, int);
     method public void adjustSuggestedStreamVolume(int, int, int);
     method public void adjustVolume(int, int);
     method public void clearCommunicationDevice();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean clearPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo);
     method public void dispatchMediaKeyEvent(android.view.KeyEvent);
     method public int generateAudioSessionId();
     method @NonNull public java.util.List<android.media.AudioPlaybackConfiguration> getActivePlaybackConfigurations();
@@ -20516,6 +20572,7 @@
     method public int getMode();
     method public String getParameters(String);
     method @Deprecated public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+    method @Nullable public android.media.AudioMixerAttributes getPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo);
     method public String getProperty(String);
     method public int getRingerMode();
     method @Deprecated public int getRouting(int);
@@ -20524,6 +20581,7 @@
     method public int getStreamMinVolume(int);
     method public int getStreamVolume(int);
     method public float getStreamVolumeDb(int, int, int);
+    method @NonNull public java.util.List<android.media.AudioMixerAttributes> getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo);
     method @Deprecated public int getVibrateSetting(int);
     method @Deprecated public boolean isBluetoothA2dpOn();
     method public boolean isBluetoothScoAvailableOffCall();
@@ -20551,6 +20609,7 @@
     method @Deprecated public boolean registerRemoteController(android.media.RemoteController);
     method public void removeOnCommunicationDeviceChangedListener(@NonNull android.media.AudioManager.OnCommunicationDeviceChangedListener);
     method public void removeOnModeChangedListener(@NonNull android.media.AudioManager.OnModeChangedListener);
+    method public void removeOnPreferredMixerAttributesChangedListener(@NonNull android.media.AudioManager.OnPreferredMixerAttributesChangedListener);
     method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
     method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest);
     method public void setAllowedCapturePolicy(int);
@@ -20561,6 +20620,7 @@
     method public void setMicrophoneMute(boolean);
     method public void setMode(int);
     method public void setParameters(String);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean setPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @NonNull android.media.AudioMixerAttributes);
     method public void setRingerMode(int);
     method @Deprecated public void setRouting(int, int, int);
     method @Deprecated public void setSpeakerphoneOn(boolean);
@@ -20716,6 +20776,10 @@
     method public void onModeChanged(int);
   }
 
+  public static interface AudioManager.OnPreferredMixerAttributesChangedListener {
+    method public void onPreferredMixerAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo, @Nullable android.media.AudioMixerAttributes);
+  }
+
   public final class AudioMetadata {
     method @NonNull public static android.media.AudioMetadataMap createMap();
   }
@@ -20751,6 +20815,21 @@
     method @IntRange(from=0) public int size();
   }
 
+  public final class AudioMixerAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.media.AudioFormat getFormat();
+    method public int getMixerBehavior();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioMixerAttributes> CREATOR;
+    field public static final int MIXER_BEHAVIOR_DEFAULT = 0; // 0x0
+  }
+
+  public static final class AudioMixerAttributes.Builder {
+    ctor public AudioMixerAttributes.Builder(@NonNull android.media.AudioFormat);
+    method @NonNull public android.media.AudioMixerAttributes build();
+    method @NonNull public android.media.AudioMixerAttributes.Builder setMixerBehavior(int);
+  }
+
   public final class AudioPlaybackCaptureConfiguration {
     method @NonNull public int[] getExcludeUids();
     method @NonNull public int[] getExcludeUsages();
@@ -38126,6 +38205,7 @@
     field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
     field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
     field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+    field public static final int RETRY_AFTER_NEXT_REBOOT = 4; // 0x4
     field public static final int RETRY_NEVER = 1; // 0x1
     field public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3; // 0x3
     field public static final int RETRY_WITH_EXPONENTIAL_BACKOFF = 2; // 0x2
@@ -39417,6 +39497,40 @@
     method @NonNull public android.service.credentials.BeginCreateCredentialResponse.Builder setRemoteCreateEntry(@Nullable android.service.credentials.CreateEntry);
   }
 
+  public final class BeginGetCredentialOption implements android.os.Parcelable {
+    ctor public BeginGetCredentialOption(@NonNull String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getCandidateQueryData();
+    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;
+  }
+
+  public final class BeginGetCredentialsRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public java.util.List<android.service.credentials.BeginGetCredentialOption> getBeginGetCredentialOptions();
+    method @NonNull public String getCallingPackage();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginGetCredentialsRequest> CREATOR;
+  }
+
+  public static final class BeginGetCredentialsRequest.Builder {
+    ctor public BeginGetCredentialsRequest.Builder(@NonNull String);
+    method @NonNull public android.service.credentials.BeginGetCredentialsRequest.Builder addBeginGetCredentialOption(@NonNull android.service.credentials.BeginGetCredentialOption);
+    method @NonNull public android.service.credentials.BeginGetCredentialsRequest build();
+    method @NonNull public android.service.credentials.BeginGetCredentialsRequest.Builder setBeginGetCredentialOptions(@NonNull java.util.List<android.service.credentials.BeginGetCredentialOption>);
+  }
+
+  public final class BeginGetCredentialsResponse implements android.os.Parcelable {
+    method @NonNull public static android.service.credentials.BeginGetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
+    method @NonNull public static android.service.credentials.BeginGetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
+    method public int describeContents();
+    method @Nullable public android.service.credentials.Action getAuthenticationAction();
+    method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.BeginGetCredentialsResponse> CREATOR;
+  }
+
   public final class CreateCredentialRequest implements android.os.Parcelable {
     ctor public CreateCredentialRequest(@NonNull String, @NonNull String, @NonNull android.os.Bundle);
     method public int describeContents();
@@ -39438,8 +39552,7 @@
 
   public final class CredentialEntry implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public android.credentials.Credential getCredential();
-    method @Nullable public android.app.PendingIntent getPendingIntent();
+    method @NonNull public android.app.PendingIntent getPendingIntent();
     method @NonNull public android.app.slice.Slice getSlice();
     method @NonNull public String getType();
     method public boolean isAutoSelectAllowed();
@@ -39449,7 +39562,6 @@
 
   public static final class CredentialEntry.Builder {
     ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent);
-    ctor public CredentialEntry.Builder(@NonNull String, @NonNull android.app.slice.Slice, @NonNull android.credentials.Credential);
     method @NonNull public android.service.credentials.CredentialEntry build();
     method @NonNull public android.service.credentials.CredentialEntry.Builder setAutoSelectAllowed(@NonNull boolean);
   }
@@ -39466,14 +39578,16 @@
   public abstract class CredentialProviderService extends android.app.Service {
     ctor public CredentialProviderService();
     method public abstract void onBeginCreateCredential(@NonNull android.service.credentials.BeginCreateCredentialRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginCreateCredentialResponse,android.service.credentials.CredentialProviderException>);
+    method public abstract void onBeginGetCredentials(@NonNull android.service.credentials.BeginGetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.BeginGetCredentialsResponse,android.service.credentials.CredentialProviderException>);
     method @NonNull public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method public abstract void onGetCredentials(@NonNull android.service.credentials.GetCredentialsRequest, @NonNull android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<android.service.credentials.GetCredentialsResponse,android.service.credentials.CredentialProviderException>);
     field public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+    field public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
     field public static final String EXTRA_CREATE_CREDENTIAL_REQUEST = "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
-    field public static final String EXTRA_CREATE_CREDENTIAL_RESULT = "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
-    field public static final String EXTRA_CREDENTIAL_RESULT = "android.service.credentials.extra.CREDENTIAL_RESULT";
-    field public static final String EXTRA_ERROR = "android.service.credentials.extra.ERROR";
-    field public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT = "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+    field public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE = "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
+    field public static final String EXTRA_CREDENTIALS_RESPONSE_CONTENT = "android.service.credentials.extra.CREDENTIALS_RESPONSE_CONTENT";
+    field public static final String EXTRA_GET_CREDENTIAL_EXCEPTION = "android.service.credentials.extra.GET_CREDENTIAL_EXCEPTION";
+    field public static final String EXTRA_GET_CREDENTIAL_REQUEST = "android.service.credentials.extra.GET_CREDENTIAL_REQUEST";
+    field public static final String EXTRA_GET_CREDENTIAL_RESPONSE = "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE";
     field public static final String SERVICE_INTERFACE = "android.service.credentials.CredentialProviderService";
   }
 
@@ -39496,29 +39610,19 @@
     method @NonNull public android.service.credentials.CredentialsResponseContent.Builder setRemoteCredentialEntry(@Nullable android.service.credentials.CredentialEntry);
   }
 
-  public final class GetCredentialsRequest implements android.os.Parcelable {
+  public final class GetCredentialRequest implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public String getCallingPackage();
     method @NonNull public java.util.List<android.credentials.GetCredentialOption> getGetCredentialOptions();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsRequest> CREATOR;
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialRequest> CREATOR;
   }
 
-  public static final class GetCredentialsRequest.Builder {
-    ctor public GetCredentialsRequest.Builder(@NonNull String);
-    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
-    method @NonNull public android.service.credentials.GetCredentialsRequest build();
-    method @NonNull public android.service.credentials.GetCredentialsRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
-  }
-
-  public final class GetCredentialsResponse implements android.os.Parcelable {
-    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithAuthentication(@NonNull android.service.credentials.Action);
-    method @NonNull public static android.service.credentials.GetCredentialsResponse createWithResponseContent(@NonNull android.service.credentials.CredentialsResponseContent);
-    method public int describeContents();
-    method @Nullable public android.service.credentials.Action getAuthenticationAction();
-    method @Nullable public android.service.credentials.CredentialsResponseContent getCredentialsResponseContent();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.service.credentials.GetCredentialsResponse> CREATOR;
+  public static final class GetCredentialRequest.Builder {
+    ctor public GetCredentialRequest.Builder(@NonNull String);
+    method @NonNull public android.service.credentials.GetCredentialRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
+    method @NonNull public android.service.credentials.GetCredentialRequest build();
+    method @NonNull public android.service.credentials.GetCredentialRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
   }
 
 }
@@ -42782,6 +42886,7 @@
     method public int getSsRsrp();
     method public int getSsRsrq();
     method public int getSsSinr();
+    method @IntRange(from=0, to=1282) public int getTimingAdvanceMicros();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.CellSignalStrengthNr> CREATOR;
   }
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 286a800..98c78fe 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -418,11 +418,24 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static java.util.Map<java.lang.String,java.util.List<android.content.ContentValues>> queryRawContactEntity(@NonNull android.content.ContentResolver, long);
   }
 
-  public final class DeviceConfig {
-    field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
-    field public static final String NAMESPACE_APP_CLONING = "app_cloning";
-    field public static final String NAMESPACE_APP_STANDBY = "app_standby";
-    field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
+  public final class Settings {
+    field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
+    field public static final int RESET_MODE_TRUSTED_DEFAULTS = 4; // 0x4
+    field public static final int RESET_MODE_UNTRUSTED_CHANGES = 3; // 0x3
+    field public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2; // 0x2
+  }
+
+  public static final class Settings.Config extends android.provider.Settings.NameValueTable {
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteString(@NonNull String, @NonNull String);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static java.util.Map<java.lang.String,java.lang.String> getStrings(@NonNull String, @NonNull java.util.List<java.lang.String>);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static int getSyncDisabledMode();
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean putString(@NonNull String, @NonNull String, @Nullable String, boolean);
+    method public static void registerContentObserver(@Nullable String, boolean, @NonNull android.database.ContentObserver);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setStrings(@NonNull String, @NonNull java.util.Map<java.lang.String,java.lang.String>) throws android.provider.DeviceConfig.BadConfigException;
+    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void setSyncDisabledMode(int);
+    method public static void unregisterContentObserver(@NonNull android.database.ContentObserver);
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 3d5139b..992e524 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -826,7 +826,6 @@
     method public int describeContents();
     method public int getFpsOverride();
     method public float getScalingFactor();
-    method @NonNull public android.app.GameModeConfiguration.Builder toBuilder();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.GameModeConfiguration> CREATOR;
     field public static final int FPS_OVERRIDE_NONE = 0; // 0x0
@@ -834,6 +833,7 @@
 
   public static final class GameModeConfiguration.Builder {
     ctor public GameModeConfiguration.Builder();
+    ctor public GameModeConfiguration.Builder(@NonNull android.app.GameModeConfiguration);
     method @NonNull public android.app.GameModeConfiguration build();
     method @NonNull public android.app.GameModeConfiguration.Builder setFpsOverride(int);
     method @NonNull public android.app.GameModeConfiguration.Builder setScalingFactor(float);
@@ -845,7 +845,7 @@
     method public int getActiveGameMode();
     method @NonNull public int[] getAvailableGameModes();
     method @Nullable public android.app.GameModeConfiguration getGameModeConfiguration(int);
-    method @NonNull public int[] getOptedInGameModes();
+    method @NonNull public int[] getOverriddenGameModes();
     method public boolean isDownscalingAllowed();
     method public boolean isFpsOverrideAllowed();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -860,7 +860,7 @@
     method @NonNull public android.app.GameModeInfo.Builder setDownscalingAllowed(boolean);
     method @NonNull public android.app.GameModeInfo.Builder setFpsOverrideAllowed(boolean);
     method @NonNull public android.app.GameModeInfo.Builder setGameModeConfiguration(int, @NonNull android.app.GameModeConfiguration);
-    method @NonNull public android.app.GameModeInfo.Builder setOptedInGameModes(@NonNull int[]);
+    method @NonNull public android.app.GameModeInfo.Builder setOverriddenGameModes(@NonNull int[]);
   }
 
   public abstract class InstantAppResolverService extends android.app.Service {
@@ -3010,6 +3010,7 @@
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
     method public int getDefaultActivityPolicy();
     method public int getDefaultNavigationPolicy();
+    method public int getDefaultRecentsPolicy();
     method public int getDevicePolicy(int);
     method public int getLockState();
     method @Nullable public String getName();
@@ -3026,6 +3027,7 @@
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
+    field public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1; // 0x1
   }
 
   public static final class VirtualDeviceParams.Builder {
@@ -3036,6 +3038,7 @@
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String);
@@ -3094,7 +3097,7 @@
   public class VirtualSensor {
     method @NonNull public String getName();
     method public int getType();
-    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendSensorEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
+    method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void sendEvent(@NonNull android.companion.virtual.sensor.VirtualSensorEvent);
   }
 
   public static interface VirtualSensor.SensorStateChangeCallback {
@@ -3769,6 +3772,7 @@
     field @Deprecated public static final int PROTECTION_FLAG_DOCUMENTER = 262144; // 0x40000
     field public static final int PROTECTION_FLAG_INCIDENT_REPORT_APPROVER = 1048576; // 0x100000
     field public static final int PROTECTION_FLAG_KNOWN_SIGNER = 134217728; // 0x8000000
+    field public static final int PROTECTION_FLAG_MODULE = 4194304; // 0x400000
     field public static final int PROTECTION_FLAG_OEM = 16384; // 0x4000
     field public static final int PROTECTION_FLAG_RECENTS = 33554432; // 0x2000000
     field public static final int PROTECTION_FLAG_RETAIL_DEMO = 16777216; // 0x1000000
@@ -5800,8 +5804,9 @@
   }
 
   public final class Country implements android.os.Parcelable {
+    ctor public Country(@NonNull String, int);
     method public int describeContents();
-    method @NonNull public String getCountryIso();
+    method @NonNull public String getCountryCode();
     method public int getSource();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int COUNTRY_SOURCE_LOCALE = 3; // 0x3
@@ -5812,13 +5817,8 @@
   }
 
   public class CountryDetector {
-    method public void addCountryListener(@NonNull android.location.CountryListener, @Nullable android.os.Looper);
-    method @Nullable public android.location.Country detectCountry();
-    method public void removeCountryListener(@NonNull android.location.CountryListener);
-  }
-
-  public interface CountryListener {
-    method public void onCountryDetected(@NonNull android.location.Country);
+    method public void registerCountryDetectorCallback(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.location.Country>);
+    method public void unregisterCountryDetectorCallback(@NonNull java.util.function.Consumer<android.location.Country>);
   }
 
   public final class GnssCapabilities implements android.os.Parcelable {
@@ -10221,7 +10221,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public android.graphics.Bitmap getUserIcon();
     method @Deprecated @android.os.UserManager.UserRestrictionSource @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public int getUserRestrictionSource(String, android.os.UserHandle);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_USERS}) public java.util.List<android.os.UserManager.EnforcingUser> getUserRestrictionSources(String, android.os.UserHandle);
-    method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public int getUserSwitchability();
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int getUserSwitchability();
     method @NonNull @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", "android.permission.MANAGE_USERS"}) public java.util.Set<android.os.UserHandle> getVisibleUsers();
     method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean hasRestrictedProfiles();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -10703,112 +10703,6 @@
     method @RequiresPermission("android.contacts.permission.MANAGE_SIM_ACCOUNTS") public static void removeSimAccounts(@NonNull android.content.ContentResolver, int);
   }
 
-  public final class DeviceConfig {
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static void addOnPropertiesChangedListener(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
-    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean deleteProperty(@NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static boolean getBoolean(@NonNull String, @NonNull String, boolean);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static float getFloat(@NonNull String, @NonNull String, float);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static int getInt(@NonNull String, @NonNull String, int);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static long getLong(@NonNull String, @NonNull String, long);
-    method @NonNull @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static android.provider.DeviceConfig.Properties getProperties(@NonNull String, @NonNull java.lang.String...);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getProperty(@NonNull String, @NonNull String);
-    method @RequiresPermission(android.Manifest.permission.READ_DEVICE_CONFIG) public static String getString(@NonNull String, @NonNull String, @Nullable String);
-    method public static void removeOnPropertiesChangedListener(@NonNull android.provider.DeviceConfig.OnPropertiesChangedListener);
-    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static void resetToDefaults(int, @Nullable String);
-    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperties(@NonNull android.provider.DeviceConfig.Properties) throws android.provider.DeviceConfig.BadConfigException;
-    method @RequiresPermission(android.Manifest.permission.WRITE_DEVICE_CONFIG) public static boolean setProperty(@NonNull String, @NonNull String, @Nullable String, boolean);
-    field public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
-    field public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT = "activity_manager_native_boot";
-    field public static final String NAMESPACE_ADSERVICES = "adservices";
-    field public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE = "ambient_context_manager_service";
-    field public static final String NAMESPACE_APPSEARCH = "appsearch";
-    field public static final String NAMESPACE_APP_COMPAT = "app_compat";
-    field public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
-    field public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
-    field public static final String NAMESPACE_AUTOFILL = "autofill";
-    field public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore";
-    field public static final String NAMESPACE_BATTERY_SAVER = "battery_saver";
-    field public static final String NAMESPACE_BIOMETRICS = "biometrics";
-    field public static final String NAMESPACE_BLOBSTORE = "blobstore";
-    field public static final String NAMESPACE_BLUETOOTH = "bluetooth";
-    field public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
-    field public static final String NAMESPACE_CLIPBOARD = "clipboard";
-    field public static final String NAMESPACE_CONNECTIVITY = "connectivity";
-    field public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
-    field @Deprecated public static final String NAMESPACE_DEX_BOOT = "dex_boot";
-    field public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
-    field public static final String NAMESPACE_GAME_DRIVER = "game_driver";
-    field public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
-    field public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
-    field public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
-    field public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
-    field public static final String NAMESPACE_LOCATION = "location";
-    field public static final String NAMESPACE_MEDIA = "media";
-    field public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
-    field public static final String NAMESPACE_NEARBY = "nearby";
-    field public static final String NAMESPACE_NETD_NATIVE = "netd_native";
-    field public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
-    field public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
-    field public static final String NAMESPACE_OTA = "ota";
-    field public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
-    field public static final String NAMESPACE_PERMISSIONS = "permissions";
-    field public static final String NAMESPACE_PRIVACY = "privacy";
-    field public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot";
-    field public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness";
-    field public static final String NAMESPACE_ROLLBACK = "rollback";
-    field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
-    field public static final String NAMESPACE_RUNTIME = "runtime";
-    field public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
-    field public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
-    field public static final String NAMESPACE_SCHEDULER = "scheduler";
-    field public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
-    field public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-    field public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-    field public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-    field public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-    field @Deprecated public static final String NAMESPACE_STORAGE = "storage";
-    field public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
-    field public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT = "surface_flinger_native_boot";
-    field public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
-    field public static final String NAMESPACE_SYSTEMUI = "systemui";
-    field public static final String NAMESPACE_SYSTEM_TIME = "system_time";
-    field public static final String NAMESPACE_TELEPHONY = "telephony";
-    field public static final String NAMESPACE_TETHERING = "tethering";
-    field public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
-    field public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
-    field public static final String NAMESPACE_UWB = "uwb";
-    field public static final String NAMESPACE_WEARABLE_SENSING = "wearable_sensing";
-    field public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
-  }
-
-  public static class DeviceConfig.BadConfigException extends java.lang.Exception {
-    ctor public DeviceConfig.BadConfigException();
-  }
-
-  public static interface DeviceConfig.OnPropertiesChangedListener {
-    method public void onPropertiesChanged(@NonNull android.provider.DeviceConfig.Properties);
-  }
-
-  public static class DeviceConfig.Properties {
-    method public boolean getBoolean(@NonNull String, boolean);
-    method public float getFloat(@NonNull String, float);
-    method public int getInt(@NonNull String, int);
-    method @NonNull public java.util.Set<java.lang.String> getKeyset();
-    method public long getLong(@NonNull String, long);
-    method @NonNull public String getNamespace();
-    method @Nullable public String getString(@NonNull String, @Nullable String);
-  }
-
-  public static final class DeviceConfig.Properties.Builder {
-    ctor public DeviceConfig.Properties.Builder(@NonNull String);
-    method @NonNull public android.provider.DeviceConfig.Properties build();
-    method @NonNull public android.provider.DeviceConfig.Properties.Builder setBoolean(@NonNull String, boolean);
-    method @NonNull public android.provider.DeviceConfig.Properties.Builder setFloat(@NonNull String, float);
-    method @NonNull public android.provider.DeviceConfig.Properties.Builder setInt(@NonNull String, int);
-    method @NonNull public android.provider.DeviceConfig.Properties.Builder setLong(@NonNull String, long);
-    method @NonNull public android.provider.DeviceConfig.Properties.Builder setString(@NonNull String, @Nullable String);
-  }
-
   public final class DocumentsContract {
     method @NonNull public static android.net.Uri buildDocumentUriAsUser(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isManageMode(@NonNull android.net.Uri);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index 47588d9..025e862 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -3,10 +3,6 @@
     Method should return Collection<CharSequence> (or subclass) instead of raw array; was `java.lang.CharSequence[]`
 
 
-ExecutorRegistration: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
-    Registration methods should have overload that accepts delivery Executor: `addCountryListener`
-
-
 GenericException: android.app.prediction.AppPredictor#finalize():
     Methods must not throw generic exceptions (`java.lang.Throwable`)
 GenericException: android.hardware.location.ContextHubClient#finalize():
@@ -131,8 +127,6 @@
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.PackageItemInfo.dumpFront) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.content.pm.ResolveInfo#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.content.pm.ResolveInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
-SamShouldBeLast: android.location.CountryDetector#addCountryListener(android.location.CountryListener, android.os.Looper):
-    SAM-compatible parameters (such as parameter 1, "listener", in android.location.CountryDetector.addCountryListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.Location#dump(android.util.Printer, String):
     SAM-compatible parameters (such as parameter 1, "pw", in android.location.Location.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.location.LocationManager#addNmeaListener(android.location.OnNmeaMessageListener, android.os.Handler):
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 25c4652..3d30c0f 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -38,6 +38,7 @@
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission.READ_PRIVILEGED_PHONE_STATE";
     field public static final String RECORD_BACKGROUND_AUDIO = "android.permission.RECORD_BACKGROUND_AUDIO";
+    field public static final String REMAP_MODIFIER_KEYS = "android.permission.REMAP_MODIFIER_KEYS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String REQUEST_UNIQUE_ID_ATTESTATION = "android.permission.REQUEST_UNIQUE_ID_ATTESTATION";
     field public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
@@ -1294,8 +1295,11 @@
 
   public final class InputManager {
     method public void addUniqueIdAssociation(@NonNull String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void clearAllModifierKeyRemappings();
     method @Nullable public String getCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier);
     method @NonNull public java.util.List<java.lang.String> getKeyboardLayoutDescriptorsForInputDevice(@NonNull android.view.InputDevice);
+    method @NonNull @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public java.util.Map<java.lang.Integer,java.lang.Integer> getModifierKeyRemapping();
+    method @RequiresPermission(android.Manifest.permission.REMAP_MODIFIER_KEYS) public void remapModifierKey(int, int);
     method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void removeKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
     method public void removeUniqueIdAssociation(@NonNull String);
     method @RequiresPermission(android.Manifest.permission.SET_KEYBOARD_LAYOUT) public void setCurrentKeyboardLayoutForInputDevice(@NonNull android.hardware.input.InputDeviceIdentifier, @NonNull String);
@@ -1511,13 +1515,33 @@
     method public static boolean isEncodingLinearPcm(int);
   }
 
+  public final class AudioHalVersionInfo implements java.lang.Comparable<android.media.AudioHalVersionInfo> android.os.Parcelable {
+    method public int compareTo(@NonNull android.media.AudioHalVersionInfo);
+    method public int describeContents();
+    method public int getHalType();
+    method public int getMajorVersion();
+    method public int getMinorVersion();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.media.AudioHalVersionInfo AIDL_1_0;
+    field public static final int AUDIO_HAL_TYPE_AIDL = 1; // 0x1
+    field public static final int AUDIO_HAL_TYPE_HIDL = 0; // 0x0
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioHalVersionInfo> CREATOR;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_2_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_4_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_5_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_6_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_0;
+    field @NonNull public static final android.media.AudioHalVersionInfo HIDL_7_1;
+    field @NonNull public static final java.util.List<android.media.AudioHalVersionInfo> VERSIONS;
+  }
+
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
     method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
     method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
     method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
-    method @Nullable public static String getHalVersion();
+    method @Nullable public static android.media.AudioHalVersionInfo getHalVersion();
     method public static final int[] getPublicStreamTypes();
     method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
     method public int getStreamMinVolumeInt(int);
@@ -1719,7 +1743,6 @@
   public class Build {
     method public static boolean is64BitAbi(String);
     method public static boolean isDebuggable();
-    method public static boolean isSecure();
     field public static final boolean IS_EMULATOR;
   }
 
@@ -2191,17 +2214,6 @@
     field public static final android.net.Uri CORP_CONTENT_URI;
   }
 
-  public final class DeviceConfig {
-    field public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
-    field public static final String NAMESPACE_ANDROID = "android";
-    field public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
-    field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
-    field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
-    field public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
-    field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
-    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
-  }
-
   public interface InputMethodManagerDeviceConfig {
     field public static final String KEY_HIDE_IME_WHEN_NO_EDITOR_FOCUS = "hide_ime_when_no_editor_focus";
   }
diff --git a/core/java/Android.bp b/core/java/Android.bp
index a4a12d7..738e2de 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -368,6 +368,20 @@
     },
 }
 
+// Build Rust bindings for remote provisioning. Needed by keystore2.
+aidl_interface {
+    name: "android.security.rkp_aidl",
+    unstable: true,
+    srcs: [
+        "android/security/rkp/*.aidl",
+    ],
+    backend: {
+        rust: {
+            enabled: true,
+        },
+    },
+}
+
 aidl_interface {
     name: "android.debug_aidl",
     unstable: true,
@@ -424,6 +438,7 @@
         "android/os/IInterface.java",
         "android/os/Binder.java",
         "android/os/IBinder.java",
+        "android/os/Parcelable.java",
     ],
 }
 
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index a9d14df..8142ee5 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -23,6 +23,7 @@
 import android.content.pm.ActivityInfo.Config;
 import android.content.res.ConstantState;
 import android.os.Build;
+import android.util.LongArray;
 
 import java.util.ArrayList;
 
@@ -559,9 +560,36 @@
     }
 
     /**
-     * Internal use only.
+     * Internal use only. Changes the value of the animator as if currentPlayTime has passed since
+     * the start of the animation. Therefore, currentPlayTime includes the start delay, and any
+     * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
+     * done between the two times.
      */
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {}
+
+    /**
+     * Internal use only. This animates any animation that has ended since lastPlayTime.
+     * If an animation hasn't been finished, no change will be made.
+     */
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {}
+
+    /**
+     * Internal use only. Adds all start times (after delay) to and end times to times.
+     * The value must include offset.
+     */
+    void getStartAndEndTimes(LongArray times, long offset) {
+        long startTime = offset + getStartDelay();
+        if (times.indexOf(startTime) < 0) {
+            times.add(startTime);
+        }
+        long duration = getTotalDuration();
+        if (duration != DURATION_INFINITE) {
+            long endTime = duration + offset;
+            if (times.indexOf(endTime) < 0) {
+                times.add(endTime);
+            }
+        }
+    }
 
     /**
      * <p>An animation listener receives notifications from an animation.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index bc8db02..54aaafc 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -23,9 +23,11 @@
 import android.util.AndroidRuntimeException;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.LongArray;
 import android.view.animation.Animation;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.HashMap;
@@ -181,6 +183,11 @@
      */
     private long mPauseTime = -1;
 
+    /**
+     * The start and stop times of all descendant animators.
+     */
+    private long[] mChildStartAndStopTimes;
+
     // This is to work around a bug in b/34736819. This needs to be removed once app team
     // fixes their side.
     private AnimatorListenerAdapter mAnimationEndListener = new AnimatorListenerAdapter() {
@@ -779,26 +786,25 @@
 
     @Override
     void skipToEndValue(boolean inReverse) {
-        if (!isInitialized()) {
-            throw new UnsupportedOperationException("Children must be initialized.");
-        }
-
         // This makes sure the animation events are sorted an up to date.
         initAnimation();
+        initChildren();
 
         // Calling skip to the end in the sequence that they would be called in a forward/reverse
         // run, such that the sequential animations modifying the same property would have
         // the right value in the end.
         if (inReverse) {
             for (int i = mEvents.size() - 1; i >= 0; i--) {
-                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+                AnimationEvent event = mEvents.get(i);
+                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                    event.mNode.mAnimation.skipToEndValue(true);
                 }
             }
         } else {
             for (int i = 0; i < mEvents.size(); i++) {
-                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
-                    mEvents.get(i).mNode.mAnimation.skipToEndValue(false);
+                AnimationEvent event = mEvents.get(i);
+                if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                    event.mNode.mAnimation.skipToEndValue(false);
                 }
             }
         }
@@ -814,72 +820,181 @@
      * {@link android.view.animation.Animation.AnimationListener#onAnimationRepeat(Animation)},
      * as needed, based on the last play time and current play time.
      */
-    @Override
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
-        if (currentPlayTime < 0 || lastPlayTime < 0) {
+    private void animateBasedOnPlayTime(
+            long currentPlayTime,
+            long lastPlayTime,
+            boolean inReverse
+    ) {
+        if (currentPlayTime < 0 || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
         // TODO: take into account repeat counts and repeat callback when repeat is implemented.
-        // Clamp currentPlayTime and lastPlayTime
 
-        // TODO: Make this more efficient
-
-        // Convert the play times to the forward direction.
         if (inReverse) {
-            if (getTotalDuration() == DURATION_INFINITE) {
-                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
-                        + " duration");
+            long duration = getTotalDuration();
+            if (duration == DURATION_INFINITE) {
+                throw new UnsupportedOperationException(
+                        "Cannot reverse AnimatorSet with infinite duration"
+                );
             }
-            long duration = getTotalDuration() - mStartDelay;
+            // Convert the play times to the forward direction.
             currentPlayTime = Math.min(currentPlayTime, duration);
             currentPlayTime = duration - currentPlayTime;
             lastPlayTime = duration - lastPlayTime;
-            inReverse = false;
         }
 
-        ArrayList<Node> unfinishedNodes = new ArrayList<>();
-        // Assumes forward playing from here on.
-        for (int i = 0; i < mEvents.size(); i++) {
-            AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
-                break;
-            }
+        long[] startEndTimes = ensureChildStartAndEndTimes();
+        int index = findNextIndex(lastPlayTime, startEndTimes);
+        int endIndex = findNextIndex(currentPlayTime, startEndTimes);
 
-            // This animation started prior to the current play time, and won't finish before the
-            // play time, add to the unfinished list.
-            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                if (event.mNode.mEndTime == DURATION_INFINITE
-                        || event.mNode.mEndTime > currentPlayTime) {
-                    unfinishedNodes.add(event.mNode);
+        // Change values at the start/end times so that values are set in the right order.
+        // We don't want an animator that would finish before another to override the value
+        // set by another animator that finishes earlier.
+        if (currentPlayTime >= lastPlayTime) {
+            while (index < endIndex) {
+                long playTime = startEndTimes[index];
+                if (lastPlayTime != playTime) {
+                    animateSkipToEnds(playTime, lastPlayTime);
+                    animateValuesInRange(playTime, lastPlayTime);
+                    lastPlayTime = playTime;
+                }
+                index++;
+            }
+        } else {
+            while (index > endIndex) {
+                index--;
+                long playTime = startEndTimes[index];
+                if (lastPlayTime != playTime) {
+                    animateSkipToEnds(playTime, lastPlayTime);
+                    animateValuesInRange(playTime, lastPlayTime);
+                    lastPlayTime = playTime;
                 }
             }
-            // For animations that do finish before the play time, end them in the sequence that
-            // they would in a normal run.
-            if (event.mEvent == AnimationEvent.ANIMATION_END) {
-                // Skip to the end of the animation.
-                event.mNode.mAnimation.skipToEndValue(false);
+        }
+        if (currentPlayTime != lastPlayTime) {
+            animateSkipToEnds(currentPlayTime, lastPlayTime);
+            animateValuesInRange(currentPlayTime, lastPlayTime);
+        }
+    }
+
+    /**
+     * Looks through startEndTimes for playTime. If it is in startEndTimes, the index after
+     * is returned. Otherwise, it returns the index at which it would be placed if it were
+     * to be inserted.
+     */
+    private int findNextIndex(long playTime, long[] startEndTimes) {
+        int index = Arrays.binarySearch(startEndTimes, playTime);
+        if (index < 0) {
+            index = -index - 1;
+        } else {
+            index++;
+        }
+        return index;
+    }
+
+    @Override
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
+        initAnimation();
+
+        if (lastPlayTime > currentPlayTime) {
+            for (int i = mEvents.size() - 1; i >= 0; i--) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_END
+                        && node.mStartTime != DURATION_INFINITE
+                ) {
+                    Animator animator = node.mAnimation;
+                    long start = node.mStartTime + animator.getStartDelay();
+                    long end = node.mTotalDuration == DURATION_INFINITE
+                            ? Long.MAX_VALUE : node.mEndTime;
+                    if (currentPlayTime <= start && start < lastPlayTime) {
+                        animator.animateSkipToEnds(
+                                start - node.mStartTime,
+                                lastPlayTime - node.mStartTime
+                        );
+                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
+                        animator.animateSkipToEnds(
+                                currentPlayTime - node.mStartTime,
+                                lastPlayTime - node.mStartTime
+                        );
+                    }
+                }
+            }
+        } else {
+            int eventsSize = mEvents.size();
+            for (int i = 0; i < eventsSize; i++) {
+                AnimationEvent event = mEvents.get(i);
+                Node node = event.mNode;
+                if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                        && node.mStartTime != DURATION_INFINITE
+                ) {
+                    Animator animator = node.mAnimation;
+                    long start = node.mStartTime + animator.getStartDelay();
+                    long end = node.mTotalDuration == DURATION_INFINITE
+                            ? Long.MAX_VALUE : node.mEndTime;
+                    if (lastPlayTime < end && end <= currentPlayTime) {
+                        animator.animateSkipToEnds(
+                                end - node.mStartTime,
+                                lastPlayTime - node.mStartTime
+                        );
+                    } else if (start <= currentPlayTime && currentPlayTime <= end) {
+                        animator.animateSkipToEnds(
+                                currentPlayTime - node.mStartTime,
+                                lastPlayTime - node.mStartTime
+                        );
+                    }
+                }
             }
         }
+    }
 
-        // Seek unfinished animation to the right time.
-        for (int i = 0; i < unfinishedNodes.size(); i++) {
-            Node node = unfinishedNodes.get(i);
-            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
-            if (!inReverse) {
-                playTime -= node.mAnimation.getStartDelay();
-            }
-            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
-        }
+    @Override
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
+        initAnimation();
 
-        // Seek not yet started animations.
-        for (int i = 0; i < mEvents.size(); i++) {
+        int eventsSize = mEvents.size();
+        for (int i = 0; i < eventsSize; i++) {
             AnimationEvent event = mEvents.get(i);
-            if (event.getTime() > currentPlayTime
-                    && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                event.mNode.mAnimation.skipToEndValue(true);
+            Node node = event.mNode;
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                    && node.mStartTime != DURATION_INFINITE
+            ) {
+                Animator animator = node.mAnimation;
+                long start = node.mStartTime + animator.getStartDelay();
+                long end = node.mTotalDuration == DURATION_INFINITE
+                        ? Long.MAX_VALUE : node.mEndTime;
+                if (start < currentPlayTime && currentPlayTime < end) {
+                    animator.animateValuesInRange(
+                            currentPlayTime - node.mStartTime,
+                            Math.max(-1, lastPlayTime - node.mStartTime)
+                    );
+                }
             }
         }
+    }
 
+    private long[] ensureChildStartAndEndTimes() {
+        if (mChildStartAndStopTimes == null) {
+            LongArray startAndEndTimes = new LongArray();
+            getStartAndEndTimes(startAndEndTimes, 0);
+            long[] times = startAndEndTimes.toArray();
+            Arrays.sort(times);
+            mChildStartAndStopTimes = times;
+        }
+        return mChildStartAndStopTimes;
+    }
+
+    @Override
+    void getStartAndEndTimes(LongArray times, long offset) {
+        int eventsSize = mEvents.size();
+        for (int i = 0; i < eventsSize; i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED
+                    && event.mNode.mStartTime != DURATION_INFINITE
+            ) {
+                event.mNode.mAnimation.getStartAndEndTimes(times, offset + event.mNode.mStartTime);
+            }
+        }
     }
 
     @Override
@@ -899,10 +1014,6 @@
         return mChildrenInitialized;
     }
 
-    private void skipToStartValue(boolean inReverse) {
-        skipToEndValue(!inReverse);
-    }
-
     /**
      * Sets the position of the animation to the specified point in time. This time should
      * be between 0 and the total duration of the animation, including any repetition. If
@@ -926,23 +1037,25 @@
         if ((getTotalDuration() != DURATION_INFINITE && playTime > getTotalDuration() - mStartDelay)
                 || playTime < 0) {
             throw new UnsupportedOperationException("Error: Play time should always be in between"
-                    + "0 and duration.");
+                    + " 0 and duration.");
         }
 
         initAnimation();
 
         if (!isStarted() || isPaused()) {
-            if (mReversing) {
+            if (mReversing && !isStarted()) {
                 throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
                         + " should not be set when AnimatorSet is not started.");
             }
+            long lastPlayTime = mSeekState.getPlayTime();
             if (!mSeekState.isActive()) {
                 findLatestEventIdForTime(0);
-                // Set all the values to start values.
                 initChildren();
+                // Set all the values to start values.
+                skipToEndValue(!mReversing);
                 mSeekState.setPlayTime(0, mReversing);
             }
-            animateBasedOnPlayTime(playTime, 0, mReversing);
+            animateBasedOnPlayTime(playTime, lastPlayTime, mReversing);
             mSeekState.setPlayTime(playTime, mReversing);
         } else {
             // If the animation is running, just set the seek time and wait until the next frame
@@ -981,10 +1094,16 @@
     private void initChildren() {
         if (!isInitialized()) {
             mChildrenInitialized = true;
-            // Forcefully initialize all children based on their end time, so that if the start
-            // value of a child is dependent on a previous animation, the animation will be
-            // initialized after the the previous animations have been advanced to the end.
-            skipToEndValue(false);
+
+            // We have to initialize all the start values so that they are based on the previous
+            // values.
+            long[] times = ensureChildStartAndEndTimes();
+
+            long previousTime = -1;
+            for (long time : times) {
+                animateBasedOnPlayTime(time, previousTime, false);
+                previousTime = time;
+            }
         }
     }
 
@@ -1058,7 +1177,7 @@
         for (int i = 0; i < mPlayingSet.size(); i++) {
             Node node = mPlayingSet.get(i);
             if (!node.mEnded) {
-                pulseFrame(node, getPlayTimeForNode(unscaledPlayTime, node));
+                pulseFrame(node, getPlayTimeForNodeIncludingDelay(unscaledPlayTime, node));
             }
         }
 
@@ -1129,7 +1248,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED && !node.mEnded) {
                     // end event:
-                    pulseFrame(node, getPlayTimeForNode(playTime, node));
+                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                 }
             }
         } else {
@@ -1150,7 +1269,7 @@
                     pulseFrame(node, 0);
                 } else if (event.mEvent == AnimationEvent.ANIMATION_END && !node.mEnded) {
                     // start event:
-                    pulseFrame(node, getPlayTimeForNode(playTime, node));
+                    pulseFrame(node, getPlayTimeForNodeIncludingDelay(playTime, node));
                 }
             }
         }
@@ -1172,11 +1291,15 @@
         }
     }
 
-    private long getPlayTimeForNode(long overallPlayTime, Node node) {
-        return getPlayTimeForNode(overallPlayTime, node, mReversing);
+    private long getPlayTimeForNodeIncludingDelay(long overallPlayTime, Node node) {
+        return getPlayTimeForNodeIncludingDelay(overallPlayTime, node, mReversing);
     }
 
-    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+    private long getPlayTimeForNodeIncludingDelay(
+            long overallPlayTime,
+            Node node,
+            boolean inReverse
+    ) {
         if (inReverse) {
             overallPlayTime = getTotalDuration() - overallPlayTime;
             return node.mEndTime - overallPlayTime;
@@ -1198,26 +1321,8 @@
         }
         // Set the child animators to the right end:
         if (mShouldResetValuesAtStart) {
-            if (isInitialized()) {
-                skipToEndValue(!mReversing);
-            } else if (mReversing) {
-                // Reversing but haven't initialized all the children yet.
-                initChildren();
-                skipToEndValue(!mReversing);
-            } else {
-                // If not all children are initialized and play direction is forward
-                for (int i = mEvents.size() - 1; i >= 0; i--) {
-                    if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
-                        Animator anim = mEvents.get(i).mNode.mAnimation;
-                        // Only reset the animations that have been initialized to start value,
-                        // so that if they are defined without a start value, they will get the
-                        // values set at the right time (i.e. the next animation run)
-                        if (anim.isInitialized()) {
-                            anim.skipToEndValue(true);
-                        }
-                    }
-                }
-            }
+            initChildren();
+            skipToEndValue(!mReversing);
         }
 
         if (mReversing || mStartDelay == 0 || mSeekState.isActive()) {
@@ -1922,11 +2027,11 @@
         }
 
         void setPlayTime(long playTime, boolean inReverse) {
-            // TODO: This can be simplified.
-
             // Clamp the play time
             if (getTotalDuration() != DURATION_INFINITE) {
                 mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+            } else {
+                mPlayTime = playTime;
             }
             mPlayTime = Math.max(0, mPlayTime);
             mSeekingInReverse = inReverse;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6ab7ae6..d41c03d 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -324,8 +324,9 @@
             listenerCopy = new ArrayList<>(sDurationScaleChangeListeners);
         }
 
-        for (WeakReference<DurationScaleChangeListener> listenerRef : listenerCopy) {
-            final DurationScaleChangeListener listener = listenerRef.get();
+        int listenersSize = listenerCopy.size();
+        for (int i = 0; i < listenersSize; i++) {
+            final DurationScaleChangeListener listener = listenerCopy.get(i).get();
             if (listener != null) {
                 listener.onChanged(durationScale);
             }
@@ -624,7 +625,7 @@
     public void setValues(PropertyValuesHolder... values) {
         int numValues = values.length;
         mValues = values;
-        mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);
+        mValuesMap = new HashMap<>(numValues);
         for (int i = 0; i < numValues; ++i) {
             PropertyValuesHolder valuesHolder = values[i];
             mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
@@ -658,9 +659,11 @@
     @CallSuper
     void initAnimation() {
         if (!mInitialized) {
-            int numValues = mValues.length;
-            for (int i = 0; i < numValues; ++i) {
-                mValues[i].init();
+            if (mValues != null) {
+                int numValues = mValues.length;
+                for (int i = 0; i < numValues; ++i) {
+                    mValues[i].init();
+                }
             }
             mInitialized = true;
         }
@@ -1209,10 +1212,14 @@
                 // If it's not yet running, then start listeners weren't called. Call them now.
                 notifyStartListeners();
             }
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            for (AnimatorListener listener : tmpListeners) {
-                listener.onAnimationCancel(this);
+            int listenersSize = mListeners.size();
+            if (listenersSize > 0) {
+                ArrayList<AnimatorListener> tmpListeners =
+                        (ArrayList<AnimatorListener>) mListeners.clone();
+                for (int i = 0; i < listenersSize; i++) {
+                    AnimatorListener listener = tmpListeners.get(i);
+                    listener.onAnimationCancel(this);
+                }
             }
         }
         endAnimation();
@@ -1452,12 +1459,19 @@
      * will be called.
      */
     @Override
-    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
-        if (currentPlayTime < 0 || lastPlayTime < 0) {
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
+        if (currentPlayTime < mStartDelay || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
 
         initAnimation();
+        long duration = getTotalDuration();
+        if (duration >= 0) {
+            lastPlayTime = Math.min(duration, lastPlayTime);
+        }
+        lastPlayTime -= mStartDelay;
+        currentPlayTime -= mStartDelay;
+
         // Check whether repeat callback is needed only when repeat count is non-zero
         if (mRepeatCount > 0) {
             int iteration = (int) (currentPlayTime / mDuration);
@@ -1478,15 +1492,27 @@
         }
 
         if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
-            skipToEndValue(inReverse);
+            throw new IllegalStateException("Can't animate a value outside of the duration");
         } else {
             // Find the current fraction:
             float fraction = currentPlayTime / (float) mDuration;
-            fraction = getCurrentIterationFraction(fraction, inReverse);
+            fraction = getCurrentIterationFraction(fraction, false);
             animateValue(fraction);
         }
     }
 
+    @Override
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
+        if (currentPlayTime <= mStartDelay && lastPlayTime > mStartDelay) {
+            skipToEndValue(true);
+        } else {
+            long duration = getTotalDuration();
+            if (duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration) {
+                skipToEndValue(false);
+            }
+        }
+    }
+
     /**
      * Internal use only.
      * Skips the animation value to end/start, depending on whether the play direction is forward
@@ -1641,6 +1667,9 @@
             Trace.traceCounter(Trace.TRACE_TAG_VIEW, getNameForTrace() + hashCode(),
                     (int) (fraction * 1000));
         }
+        if (mValues == null) {
+            return;
+        }
         fraction = mInterpolator.getInterpolation(fraction);
         mCurrentFraction = fraction;
         int numValues = mValues.length;
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index f62190a..c51e8ae 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -902,4 +902,9 @@
      */
     public abstract void registerNetworkPolicyUidObserver(@NonNull IUidObserver observer,
             int which, int cutpoint, @NonNull String callingPackage);
+
+    /**
+     * Return all client package names of a service.
+     */
+    public abstract ArraySet<String> getClientPackages(String servicePackageName);
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2dccd4d..a4c9f8c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1020,6 +1020,12 @@
         int flags;
     }
 
+    // A list of receivers and an index into the receiver to be processed next.
+    static final class ReceiverList {
+        List<ReceiverInfo> receivers;
+        int index;
+    }
+
     private class ApplicationThread extends IApplicationThread.Stub {
         private static final String DB_CONNECTION_INFO_HEADER = "  %8s %8s %14s %5s %5s %5s  %s";
         private static final String DB_CONNECTION_INFO_FORMAT = "  %8s %8s %14s %5d %5d %5d  %s";
@@ -1036,6 +1042,21 @@
             sendMessage(H.RECEIVER, r);
         }
 
+        public final void scheduleReceiverList(List<ReceiverInfo> info) throws RemoteException {
+            for (int i = 0; i < info.size(); i++) {
+                ReceiverInfo r = info.get(i);
+                if (r.registered) {
+                    scheduleRegisteredReceiver(r.receiver, r.intent,
+                            r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+                            r.sendingUser, r.processState);
+                } else {
+                    scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+                            r.resultCode, r.data, r.extras, r.sync,
+                            r.sendingUser, r.processState);
+                }
+            }
+        }
+
         public final void scheduleCreateBackupAgent(ApplicationInfo app,
                 int backupMode, int userId, @BackupDestination int backupDestination) {
             CreateBackupAgentData d = new CreateBackupAgentData();
@@ -6717,6 +6738,13 @@
             ii = null;
         }
 
+        final IActivityManager mgr = ActivityManager.getService();
+        try {
+            mgr.finishAttachApplication(mStartSeq);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+
         final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
         mConfigurationController.updateLocaleListFromAppContext(appContext);
 
@@ -6785,13 +6813,6 @@
         final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
         final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
 
-        final IActivityManager mgr = ActivityManager.getService();
-        try {
-            mgr.finishAttachApplication(mStartSeq);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-
         // Wait for debugger after we have notified the system to finish attach application
         if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
             // XXX should have option to change the port.
diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java
index 2f51b17..c6fa064 100644
--- a/core/java/android/app/GameManager.java
+++ b/core/java/android/app/GameManager.java
@@ -287,6 +287,7 @@
      * <p>
      * The caller must have {@link android.Manifest.permission#MANAGE_GAME_MODE}.
      *
+     * @param packageName The package name of the game to update
      * @param gameModeConfig The configuration to use for game mode interventions
      * @hide
      */
diff --git a/core/java/android/app/GameModeConfiguration.java b/core/java/android/app/GameModeConfiguration.java
index b081e82..d8be814 100644
--- a/core/java/android/app/GameModeConfiguration.java
+++ b/core/java/android/app/GameModeConfiguration.java
@@ -62,10 +62,16 @@
      */
     @SystemApi
     public static final class Builder {
-        /** Constructs a new Builder for a game mode’s configuration */
+        /** Constructs a new Builder for a game mode’s configuration. */
         public Builder() {
         }
 
+        /** Constructs a new builder by copying from an existing game mode configuration. */
+        public Builder(@NonNull GameModeConfiguration configuration) {
+            mFpsOverride = configuration.mFpsOverride;
+            mScalingFactor = configuration.mScalingFactor;
+        }
+
         /**
          * Sets the scaling factor used for game resolution downscaling.
          * <br>
@@ -156,16 +162,6 @@
         return mFpsOverride;
     }
 
-    /**
-     * Converts and returns the game mode config as a new builder.
-     */
-    @NonNull
-    public GameModeConfiguration.Builder toBuilder() {
-        return new GameModeConfiguration.Builder()
-                .setFpsOverride(mFpsOverride)
-                .setScalingFactor(mScalingFactor);
-    }
-
     @Override
     public boolean equals(Object obj) {
         if (obj == this) {
diff --git a/core/java/android/app/GameModeInfo.java b/core/java/android/app/GameModeInfo.java
index 31255c2..7dcb3909 100644
--- a/core/java/android/app/GameModeInfo.java
+++ b/core/java/android/app/GameModeInfo.java
@@ -87,12 +87,12 @@
         }
 
         /**
-         * Sets the opted-in game modes.
+         * Sets the overridden game modes.
          */
         @NonNull
-        public GameModeInfo.Builder setOptedInGameModes(
-                @NonNull @GameManager.GameMode int[] optedInGameModes) {
-            mOptedInGameModes = optedInGameModes;
+        public GameModeInfo.Builder setOverriddenGameModes(
+                @NonNull @GameManager.GameMode int[] overriddenGameModes) {
+            mOverriddenGameModes = overriddenGameModes;
             return this;
         }
 
@@ -140,12 +140,12 @@
          */
         @NonNull
         public GameModeInfo build() {
-            return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOptedInGameModes,
+            return new GameModeInfo(mActiveGameMode, mAvailableGameModes, mOverriddenGameModes,
                     mIsDownscalingAllowed, mIsFpsOverrideAllowed, mConfigMap);
         }
 
         private @GameManager.GameMode int[] mAvailableGameModes = new int[]{};
-        private @GameManager.GameMode int[] mOptedInGameModes = new int[]{};
+        private @GameManager.GameMode int[] mOverriddenGameModes = new int[]{};
         private @GameManager.GameMode int mActiveGameMode;
         private boolean mIsDownscalingAllowed;
         private boolean mIsFpsOverrideAllowed;
@@ -164,11 +164,11 @@
 
     private GameModeInfo(@GameManager.GameMode int activeGameMode,
             @NonNull @GameManager.GameMode int[] availableGameModes,
-            @NonNull @GameManager.GameMode int[] optedInGameModes, boolean isDownscalingAllowed,
+            @NonNull @GameManager.GameMode int[] overriddenGameModes, boolean isDownscalingAllowed,
             boolean isFpsOverrideAllowed, @NonNull Map<Integer, GameModeConfiguration> configMap) {
         mActiveGameMode = activeGameMode;
         mAvailableGameModes = Arrays.copyOf(availableGameModes, availableGameModes.length);
-        mOptedInGameModes = Arrays.copyOf(optedInGameModes, optedInGameModes.length);
+        mOverriddenGameModes = Arrays.copyOf(overriddenGameModes, overriddenGameModes.length);
         mIsDownscalingAllowed = isDownscalingAllowed;
         mIsFpsOverrideAllowed = isFpsOverrideAllowed;
         mConfigMap = configMap;
@@ -179,7 +179,7 @@
     public GameModeInfo(Parcel in) {
         mActiveGameMode = in.readInt();
         mAvailableGameModes = in.createIntArray();
-        mOptedInGameModes = in.createIntArray();
+        mOverriddenGameModes = in.createIntArray();
         mIsDownscalingAllowed = in.readBoolean();
         mIsFpsOverrideAllowed = in.readBoolean();
         mConfigMap = new ArrayMap<>();
@@ -198,8 +198,8 @@
      * Gets the collection of {@link GameManager.GameMode} that can be applied to the game.
      * <p>
      * Available games include all game modes that are either supported by the OEM in device
-     * config, or opted in by the game developers in game mode config XML, plus the default enabled
-     * modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
+     * config, or overridden by the game developers in game mode config XML, plus the default
+     * enabled modes for any game including {@link GameManager#GAME_MODE_STANDARD} and
      * {@link GameManager#GAME_MODE_CUSTOM}.
      * <p>
      * Also see {@link GameModeInfo}.
@@ -210,19 +210,19 @@
     }
 
     /**
-     * Gets the collection of {@link GameManager.GameMode} that are opted in by the game.
+     * Gets the collection of {@link GameManager.GameMode} that are overridden by the game.
      * <p>
      * Also see {@link GameModeInfo}.
      */
     @NonNull
-    public @GameManager.GameMode int[] getOptedInGameModes() {
-        return Arrays.copyOf(mOptedInGameModes, mOptedInGameModes.length);
+    public @GameManager.GameMode int[] getOverriddenGameModes() {
+        return Arrays.copyOf(mOverriddenGameModes, mOverriddenGameModes.length);
     }
 
     /**
      * Gets the current game mode configuration of a game mode.
      * <p>
-     * The game mode can be null if it's opted in by the game itself, or not configured in device
+     * The game mode can be null if it's overridden by the game itself, or not configured in device
      * config nor set by the user as custom game mode configuration.
      */
     public @Nullable GameModeConfiguration getGameModeConfiguration(
@@ -250,7 +250,7 @@
 
 
     private final @GameManager.GameMode int[] mAvailableGameModes;
-    private final @GameManager.GameMode int[] mOptedInGameModes;
+    private final @GameManager.GameMode int[] mOverriddenGameModes;
     private final @GameManager.GameMode int mActiveGameMode;
     private final boolean mIsDownscalingAllowed;
     private final boolean mIsFpsOverrideAllowed;
@@ -265,7 +265,7 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mActiveGameMode);
         dest.writeIntArray(mAvailableGameModes);
-        dest.writeIntArray(mOptedInGameModes);
+        dest.writeIntArray(mOverriddenGameModes);
         dest.writeBoolean(mIsDownscalingAllowed);
         dest.writeBoolean(mIsFpsOverrideAllowed);
         dest.writeMap(mConfigMap);
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 595c7f7..3984fee 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -20,6 +20,7 @@
 import android.app.IInstrumentationWatcher;
 import android.app.IUiAutomationConnection;
 import android.app.ProfilerInfo;
+import android.app.ReceiverInfo;
 import android.app.ResultInfo;
 import android.app.servertransaction.ClientTransaction;
 import android.content.AutofillOptions;
@@ -66,6 +67,9 @@
             in CompatibilityInfo compatInfo,
             int resultCode, in String data, in Bundle extras, boolean sync,
             int sendingUser, int processState);
+
+    void scheduleReceiverList(in List<ReceiverInfo> info);
+
     @UnsupportedAppUsage
     void scheduleCreateService(IBinder token, in ServiceInfo info,
             in CompatibilityInfo compatInfo, int processState);
diff --git a/core/java/android/app/ReceiverInfo.aidl b/core/java/android/app/ReceiverInfo.aidl
new file mode 100644
index 0000000..d90eee7
--- /dev/null
+++ b/core/java/android/app/ReceiverInfo.aidl
@@ -0,0 +1,60 @@
+/**
+ * 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;
+
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.os.Bundle;
+
+/**
+ * Collect the information needed for manifest and registered receivers into a single structure
+ * that can be the element of a list.  All fields are already parcelable.
+ * @hide
+ */
+parcelable ReceiverInfo {
+    /**
+     * Fields common to registered and manifest receivers.
+     */
+    Intent intent;
+    String data;
+    Bundle extras;
+    int sendingUser;
+    int processState;
+    int resultCode;
+
+    /**
+     * True if this instance represents a registered receiver and false if this instance
+     * represents a manifest receiver.
+     */
+    boolean registered;
+
+    /**
+     * Fields used only for registered receivers.
+     */
+    IIntentReceiver receiver;
+    boolean ordered;
+    boolean sticky;
+
+    /**
+     * Fields used only for manifest receivers.
+     */
+    ActivityInfo activityInfo;
+    CompatibilityInfo compatInfo;
+    boolean sync;
+}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index d275c83..7e6386e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -41,6 +41,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
+import android.util.IndentingPrintWriter;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -51,7 +52,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -173,6 +173,7 @@
 
     /**
      * The ApkAssets that are being referenced in the wild that we can reuse.
+     * Used as a lock for itself as well.
      */
     private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
 
@@ -286,38 +287,43 @@
      * try as hard as possible to release any open FDs.
      */
     public void invalidatePath(String path) {
+        final List<ResourcesImpl> implsToFlush = new ArrayList<>();
         synchronized (mLock) {
-            int count = 0;
-
             for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
                 final ResourcesKey key = mResourceImpls.keyAt(i);
                 if (key.isPathReferenced(path)) {
-                    ResourcesImpl impl = mResourceImpls.removeAt(i).get();
-                    if (impl != null) {
-                        impl.flushLayoutCache();
+                    ResourcesImpl resImpl = mResourceImpls.removeAt(i).get();
+                    if (resImpl != null) {
+                        implsToFlush.add(resImpl);
                     }
-                    count++;
                 }
             }
-
-            Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
-
+        }
+        for (int i = 0; i < implsToFlush.size(); i++) {
+            implsToFlush.get(i).flushLayoutCache();
+        }
+        final List<ApkAssets> assetsToClose = new ArrayList<>();
+        synchronized (mCachedApkAssets) {
             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
                 final ApkKey key = mCachedApkAssets.keyAt(i);
                 if (key.path.equals(path)) {
-                    WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
-                    if (apkAssetsRef != null && apkAssetsRef.get() != null) {
-                        apkAssetsRef.get().close();
+                    final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.removeAt(i);
+                    final ApkAssets apkAssets = apkAssetsRef != null ? apkAssetsRef.get() : null;
+                    if (apkAssets != null) {
+                        assetsToClose.add(apkAssets);
                     }
                 }
             }
         }
+        for (int i = 0; i < assetsToClose.size(); i++) {
+            assetsToClose.get(i).close();
+        }
+        Log.i(TAG,
+                "Invalidated " + implsToFlush.size() + " asset managers that referenced " + path);
     }
 
     public Configuration getConfiguration() {
-        synchronized (mLock) {
-            return mResConfiguration;
-        }
+        return mResConfiguration;
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -402,14 +408,12 @@
      * @param resources The {@link Resources} backing the display adjustments.
      */
     public Display getAdjustedDisplay(final int displayId, Resources resources) {
-        synchronized (mLock) {
-            final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-            if (dm == null) {
-                // may be null early in system startup
-                return null;
-            }
-            return dm.getCompatibleDisplay(displayId, resources);
+        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+        if (dm == null) {
+            // may be null early in system startup
+            return null;
         }
+        return dm.getCompatibleDisplay(displayId, resources);
     }
 
     /**
@@ -446,16 +450,14 @@
         ApkAssets apkAssets;
 
         // Optimistically check if this ApkAssets exists somewhere else.
-        synchronized (mLock) {
-            final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(key);
-            if (apkAssetsRef != null) {
-                apkAssets = apkAssetsRef.get();
-                if (apkAssets != null && apkAssets.isUpToDate()) {
-                    return apkAssets;
-                } else {
-                    // Clean up the reference.
-                    mCachedApkAssets.remove(key);
-                }
+        final WeakReference<ApkAssets> apkAssetsRef;
+        synchronized (mCachedApkAssets) {
+            apkAssetsRef = mCachedApkAssets.get(key);
+        }
+        if (apkAssetsRef != null) {
+            apkAssets = apkAssetsRef.get();
+            if (apkAssets != null && apkAssets.isUpToDate()) {
+                return apkAssets;
             }
         }
 
@@ -472,7 +474,7 @@
             apkAssets = ApkAssets.loadFromPath(key.path, flags);
         }
 
-        synchronized (mLock) {
+        synchronized (mCachedApkAssets) {
             mCachedApkAssets.put(key, new WeakReference<>(apkAssets));
         }
 
@@ -584,28 +586,33 @@
      * @hide
      */
     public void dump(String prefix, PrintWriter printWriter) {
+        final int references;
+        final int resImpls;
         synchronized (mLock) {
-            IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
-            for (int i = 0; i < prefix.length() / 2; i++) {
-                pw.increaseIndent();
-            }
-
-            pw.println("ResourcesManager:");
-            pw.increaseIndent();
-            pw.print("total apks: ");
-            pw.println(countLiveReferences(mCachedApkAssets.values()));
-
-            pw.print("resources: ");
-
-            int references = countLiveReferences(mResourceReferences);
+            int refs = countLiveReferences(mResourceReferences);
             for (ActivityResources activityResources : mActivityResourceReferences.values()) {
-                references += activityResources.countLiveReferences();
+                refs += activityResources.countLiveReferences();
             }
-            pw.println(references);
-
-            pw.print("resource impls: ");
-            pw.println(countLiveReferences(mResourceImpls.values()));
+            references = refs;
+            resImpls = countLiveReferences(mResourceImpls.values());
         }
+        final int liveAssets;
+        synchronized (mCachedApkAssets) {
+            liveAssets = countLiveReferences(mCachedApkAssets.values());
+        }
+
+        final var pw = new IndentingPrintWriter(printWriter, "  ");
+        for (int i = 0; i < prefix.length() / 2; i++) {
+            pw.increaseIndent();
+        }
+        pw.println("ResourcesManager:");
+        pw.increaseIndent();
+        pw.print("total apks: ");
+        pw.println(liveAssets);
+        pw.print("resources: ");
+        pw.println(references);
+        pw.print("resource impls: ");
+        pw.println(resImpls);
     }
 
     private Configuration generateConfig(@NonNull ResourcesKey key) {
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a035375..f63f406 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -234,7 +234,10 @@
 
     /**
      * Session flag for {@link #registerSessionListener} indicating the listener
-     * is interested in sessions on the keygaurd
+     * is interested in sessions on the keygaurd.
+     * Keyguard Session Boundaries:
+     *     START_SESSION: device starts going to sleep OR the keyguard is newly shown
+     *     END_SESSION: device starts going to sleep OR keyguard is no longer showing
      * @hide
      */
     public static final int SESSION_KEYGUARD = 1 << 0;
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 762ac23..3730a6f 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -51,6 +51,8 @@
 import android.app.usage.UsageStatsManager;
 import android.app.wallpapereffectsgeneration.IWallpaperEffectsGenerationManager;
 import android.app.wallpapereffectsgeneration.WallpaperEffectsGenerationManager;
+import android.app.wearable.IWearableSensingManager;
+import android.app.wearable.WearableSensingManager;
 import android.apphibernation.AppHibernationManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothFrameworkInitializer;
@@ -1544,6 +1546,17 @@
                                 IAmbientContextManager.Stub.asInterface(iBinder);
                         return new AmbientContextManager(ctx.getOuterContext(), manager);
                     }});
+        registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
+                new CachedServiceFetcher<WearableSensingManager>() {
+                    @Override
+                    public WearableSensingManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        IBinder iBinder = ServiceManager.getServiceOrThrow(
+                                Context.WEARABLE_SENSING_SERVICE);
+                        IWearableSensingManager manager =
+                                IWearableSensingManager.Stub.asInterface(iBinder);
+                        return new WearableSensingManager(ctx.getOuterContext(), manager);
+                    }});
 
         sInitializing = true;
         try {
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f5d657c..e880432 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -807,6 +807,7 @@
      *     is not able to access the wallpaper.
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
     public Drawable getDrawable() {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
@@ -819,6 +820,29 @@
     }
 
     /**
+     * Retrieve the requested wallpaper; if
+     * no wallpaper is set, the requested built-in static wallpaper is returned.
+     * This is returned as an
+     * abstract Drawable that you can install in a View to display whatever
+     * wallpaper the user has currently set.
+     * <p>
+     * This method can return null if the requested wallpaper is not available, if
+     * wallpapers are not supported in the current user, or if the calling app is not
+     * permitted to access the requested wallpaper.
+     *
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     *     IllegalArgumentException if an invalid wallpaper is requested.
+     * @return Returns a Drawable object that will draw the requested wallpaper,
+     *     or {@code null} if the requested wallpaper does not exist or if the calling application
+     *     is not able to access the wallpaper.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
+    public Drawable getDrawable(@SetWallpaperFlags int which) {
+        return getDrawable();
+    }
+    /**
      * Obtain a drawable for the built-in static system wallpaper.
      */
     public Drawable getBuiltInDrawable() {
@@ -1037,6 +1061,7 @@
      * @return Returns a Drawable object that will draw the wallpaper or a
      * null pointer if these is none.
      */
+    @Nullable
     public Drawable peekDrawable() {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
@@ -1049,6 +1074,23 @@
     }
 
     /**
+     * Retrieve the requested wallpaper; if there is no wallpaper set,
+     * a null pointer is returned. This is returned as an
+     * abstract Drawable that you can install in a View to display whatever
+     * wallpaper the user has currently set.
+     *
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     *     IllegalArgumentException if an invalid wallpaper is requested.
+     * @return Returns a Drawable object that will draw the wallpaper or a null pointer if these
+     * is none.
+     * @hide
+     */
+    @Nullable
+    public Drawable peekDrawable(@SetWallpaperFlags int which) {
+        return peekDrawable();
+    }
+
+    /**
      * Like {@link #getDrawable()}, but the returned Drawable has a number
      * of limitations to reduce its overhead as much as possible. It will
      * never scale the wallpaper (only centering it if the requested bounds
@@ -1062,6 +1104,7 @@
      * @return Returns a Drawable object that will draw the wallpaper.
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
     public Drawable getFastDrawable() {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, FLAG_SYSTEM, cmProxy);
@@ -1072,6 +1115,28 @@
     }
 
     /**
+     * Like {@link #getFastDrawable(int)}, but the returned Drawable has a number
+     * of limitations to reduce its overhead as much as possible. It will
+     * never scale the wallpaper (only centering it if the requested bounds
+     * do match the bitmap bounds, which should not be typical), doesn't
+     * allow setting an alpha, color filter, or other attributes, etc.  The
+     * bounds of the returned drawable will be initialized to the same bounds
+     * as the wallpaper, so normally you will not need to touch it.  The
+     * drawable also assumes that it will be used in a context running in
+     * the same density as the screen (not in density compatibility mode).
+     *
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     *     IllegalArgumentException if an invalid wallpaper is requested.
+     * @return Returns a Drawable object that will draw the wallpaper.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
+    public Drawable getFastDrawable(@SetWallpaperFlags int which) {
+        return getFastDrawable();
+    }
+
+    /**
      * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
      * a null pointer is returned.
      *
@@ -1079,6 +1144,7 @@
      * wallpaper or a null pointer if these is none.
      */
     @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
     public Drawable peekFastDrawable() {
         final ColorManagementProxy cmProxy = getColorManagementProxy();
         Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, FLAG_SYSTEM, cmProxy);
@@ -1089,6 +1155,22 @@
     }
 
     /**
+     * Like {@link #getFastDrawable()}, but if there is no wallpaper set,
+     * a null pointer is returned.
+     *
+     * @param which The {@code FLAG_*} identifier of a valid wallpaper type.  Throws
+     *     IllegalArgumentException if an invalid wallpaper is requested.
+     * @return Returns an optimized Drawable object that will draw the
+     * wallpaper or a null pointer if these is none.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
+    @Nullable
+    public Drawable peekFastDrawable(@SetWallpaperFlags int which) {
+        return peekFastDrawable();
+    }
+
+    /**
      * Whether the wallpaper supports Wide Color Gamut or not.
      * @param which The wallpaper whose image file is to be retrieved. Must be a single
      *     defined kind of wallpaper, either {@link #FLAG_SYSTEM} or {@link #FLAG_LOCK}.
diff --git a/core/java/android/app/admin/DevicePolicyCache.java b/core/java/android/app/admin/DevicePolicyCache.java
index da62375..4a329a9 100644
--- a/core/java/android/app/admin/DevicePolicyCache.java
+++ b/core/java/android/app/admin/DevicePolicyCache.java
@@ -61,7 +61,6 @@
      */
     public abstract boolean canAdminGrantSensorsPermissionsForUser(@UserIdInt int userHandle);
 
-
     /**
      * Empty implementation.
      */
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index ecea1bb..dd82bc1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -165,6 +165,12 @@
 @SuppressLint("UseIcu")
 public class DevicePolicyManager {
 
+    /** @hide */
+    public static final String DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG =
+            "deprecate_usermanagerinternal_devicepolicy";
+    /** @hide */
+    public static final boolean DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT = false;
+
     private static String TAG = "DevicePolicyManager";
 
     private final Context mContext;
@@ -14449,7 +14455,9 @@
      * @throws SecurityException if {@code admin} is not a profile owner
      *
      * @see #getCrossProfileCalendarPackages(ComponentName)
+     * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
      */
+    @Deprecated
     public void setCrossProfileCalendarPackages(@NonNull ComponentName admin,
             @Nullable Set<String> packageNames) {
         throwIfParentInstance("setCrossProfileCalendarPackages");
@@ -14475,7 +14483,9 @@
      * @throws SecurityException if {@code admin} is not a profile owner
      *
      * @see #setCrossProfileCalendarPackages(ComponentName, Set)
+     * @deprecated Use {@link #setCrossProfilePackages(ComponentName, Set)}.
      */
+    @Deprecated
     public @Nullable Set<String> getCrossProfileCalendarPackages(@NonNull ComponentName admin) {
         throwIfParentInstance("getCrossProfileCalendarPackages");
         if (mService != null) {
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index b6f0916..840f3a3 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -269,4 +269,14 @@
      * {@link #supportsResetOp(int)} is true.
      */
     public abstract void resetOp(int op, String packageName, @UserIdInt int userId);
+
+    /**
+     * Returns whether new "turn off work" behavior is enabled via feature flag.
+     */
+    public abstract boolean isKeepProfilesRunningEnabled();
+
+    /**
+     * True if either the entire device or the user is organization managed.
+     */
+    public abstract boolean isUserOrganizationManaged(@UserIdInt int userId);
 }
diff --git a/core/java/android/app/admin/DeviceStateCache.java b/core/java/android/app/admin/DeviceStateCache.java
index 7619aa2..d1d130d 100644
--- a/core/java/android/app/admin/DeviceStateCache.java
+++ b/core/java/android/app/admin/DeviceStateCache.java
@@ -15,6 +15,8 @@
  */
 package android.app.admin;
 
+import android.annotation.UserIdInt;
+
 import com.android.server.LocalServices;
 
 /**
@@ -43,6 +45,11 @@
     public abstract boolean isDeviceProvisioned();
 
     /**
+     * True if either the entire device or the user is organization managed.
+     */
+    public abstract boolean isUserOrganizationManaged(@UserIdInt int userHandle);
+
+    /**
      * Empty implementation.
      */
     private static class EmptyDeviceStateCache extends DeviceStateCache {
@@ -52,5 +59,10 @@
         public boolean isDeviceProvisioned() {
             return false;
         }
+
+        @Override
+        public boolean isUserOrganizationManaged(int userHandle) {
+            return false;
+        }
     }
 }
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index 904db5f..ca2e97e 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -96,6 +96,9 @@
             TAG_WIFI_DISCONNECTION,
             TAG_BLUETOOTH_CONNECTION,
             TAG_BLUETOOTH_DISCONNECTION,
+            TAG_PACKAGE_INSTALLED,
+            TAG_PACKAGE_UPDATED,
+            TAG_PACKAGE_UNINSTALLED,
     })
     public @interface SecurityLogTag {}
 
@@ -563,6 +566,39 @@
             SecurityLogTags.SECURITY_BLUETOOTH_DISCONNECTION;
 
     /**
+     * Indicates that a package is installed.
+     * The log entry contains the following information about the
+     * event, encapsulated in an {@link Object} array and accessible via
+     * {@link SecurityEvent#getData()}:
+     * <li> [0] Name of the package being installed ({@code String})
+     * <li> [1] Package version code ({@code Long})
+     * <li> [2] UserId of the user that installed this package ({@code Integer})
+     */
+    public static final int TAG_PACKAGE_INSTALLED = SecurityLogTags.SECURITY_PACKAGE_INSTALLED;
+
+    /**
+     * Indicates that a package is updated.
+     * The log entry contains the following information about the
+     * event, encapsulated in an {@link Object} array and accessible via
+     * {@link SecurityEvent#getData()}:
+     * <li> [0] Name of the package being updated ({@code String})
+     * <li> [1] Package version code ({@code Long})
+     * <li> [2] UserId of the user that updated this package ({@code Integer})
+     */
+    public static final int TAG_PACKAGE_UPDATED = SecurityLogTags.SECURITY_PACKAGE_UPDATED;
+
+    /**
+     * Indicates that a package is uninstalled.
+     * The log entry contains the following information about the
+     * event, encapsulated in an {@link Object} array and accessible via
+     * {@link SecurityEvent#getData()}:
+     * <li> [0] Name of the package being uninstalled ({@code String})
+     * <li> [1] Package version code ({@code Long})
+     * <li> [2] UserId of the user that uninstalled this package ({@code Integer})
+     */
+    public static final int TAG_PACKAGE_UNINSTALLED = SecurityLogTags.SECURITY_PACKAGE_UNINSTALLED;
+
+    /**
      * Event severity level indicating that the event corresponds to normal workflow.
      */
     public static final int LEVEL_INFO = 1;
@@ -772,6 +808,9 @@
                     break;
                 case SecurityLog.TAG_CERT_AUTHORITY_INSTALLED:
                 case SecurityLog.TAG_CERT_AUTHORITY_REMOVED:
+                case SecurityLog.TAG_PACKAGE_INSTALLED:
+                case SecurityLog.TAG_PACKAGE_UPDATED:
+                case SecurityLog.TAG_PACKAGE_UNINSTALLED:
                     try {
                         userId = getIntegerData(2);
                     } catch (Exception e) {
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index b06e5a5..e4af8dd 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -44,4 +44,7 @@
 210037 security_wifi_connection                 (bssid|3),(event_type|3),(reason|3)
 210038 security_wifi_disconnection              (bssid|3),(reason|3)
 210039 security_bluetooth_connection            (addr|3),(success|1),(reason|3)
-210040 security_bluetooth_disconnection         (addr|3),(reason|3)
\ No newline at end of file
+210040 security_bluetooth_disconnection         (addr|3),(reason|3)
+210041 security_package_installed               (package_name|3),(version_code|1),(user_id|1)
+210042 security_package_updated                 (package_name|3),(version_code|1),(user_id|1)
+210043 security_package_uninstalled             (package_name|3),(version_code|1),(user_id|1)
\ No newline at end of file
diff --git a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 7d6336a..6e784b2 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -51,6 +51,11 @@
      */
     List<VirtualDevice> getVirtualDevices();
 
+   /**
+     * Returns the ID of the device which owns the display with the given ID.
+     */
+    int getDeviceIdForDisplayId(int displayId);
+
     /**
      * Returns the device policy for the given virtual device and policy type.
      */
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 0e6cfb1..109f4b1 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -231,6 +231,23 @@
     }
 
     /**
+     * Returns the ID of the device which owns the display with the given ID.
+     *
+     * @hide
+     */
+    public int getDeviceIdForDisplayId(int displayId) {
+        if (mService == null) {
+            Log.w(TAG, "Failed to retrieve virtual devices; no virtual device manager service.");
+            return DEFAULT_DEVICE_ID;
+        }
+        try {
+            return mService.getDeviceIdForDisplayId(displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * A virtual device has its own virtual display, audio output, microphone, and camera etc. The
      * creator of a virtual device can take the output from the virtual display and stream it over
      * to another device, and inject input events that are received from the remote device.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 1cbe910..be77f2b 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -147,6 +147,19 @@
      */
     public static final int POLICY_TYPE_SENSORS = 0;
 
+    /** @hide */
+    @IntDef(flag = true, prefix = "RECENTS_POLICY_",
+            value = {RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS})
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
+    public @interface RecentsPolicy {}
+
+    /**
+     * If set, activities launched on this virtual device are allowed to appear in the host device
+     * of the recently launched activities list.
+     */
+    public static final int RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS = 1 << 0;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NonNull private final ArraySet<ComponentName> mAllowedCrossTaskNavigations;
@@ -161,6 +174,8 @@
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
     @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -173,7 +188,8 @@
             @ActivityPolicy int defaultActivityPolicy,
             @Nullable String name,
             @NonNull SparseIntArray devicePolicies,
-            @NonNull List<VirtualSensorConfig> virtualSensorConfigs) {
+            @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
+            @RecentsPolicy int defaultRecentsPolicy) {
         mLockState = lockState;
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -188,6 +204,7 @@
         mName = name;
         mDevicePolicies = Objects.requireNonNull(devicePolicies);
         mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     @SuppressWarnings("unchecked")
@@ -204,6 +221,7 @@
         mDevicePolicies = parcel.readSparseIntArray();
         mVirtualSensorConfigs = new ArrayList<>();
         parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
+        mDefaultRecentsPolicy = parcel.readInt();
     }
 
     /**
@@ -328,6 +346,16 @@
         return mVirtualSensorConfigs;
     }
 
+    /**
+     * Returns the policy of how to handle activities in recents.
+     *
+     * @see RecentsPolicy
+     */
+    @RecentsPolicy
+    public int getDefaultRecentsPolicy() {
+        return mDefaultRecentsPolicy;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -346,6 +374,7 @@
         dest.writeString8(mName);
         dest.writeSparseIntArray(mDevicePolicies);
         dest.writeTypedList(mVirtualSensorConfigs);
+        dest.writeInt(mDefaultRecentsPolicy);
     }
 
     @Override
@@ -377,15 +406,17 @@
                 && Objects.equals(mAllowedActivities, that.mAllowedActivities)
                 && Objects.equals(mBlockedActivities, that.mBlockedActivities)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
-                && Objects.equals(mName, that.mName);
+                && Objects.equals(mName, that.mName)
+                && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
     }
 
     @Override
     public int hashCode() {
         int hashCode = Objects.hash(
                 mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
-                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy,  mAllowedActivities,
-                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies);
+                mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
+                mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
+                mDefaultRecentsPolicy);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -407,6 +438,7 @@
                 + " mDefaultActivityPolicy=" + mDefaultActivityPolicy
                 + " mName=" + mName
                 + " mDevicePolicies=" + mDevicePolicies
+                + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
                 + ")";
     }
 
@@ -442,6 +474,7 @@
         @Nullable private String mName;
         @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
         @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
+        private int mDefaultRecentsPolicy;
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -647,6 +680,17 @@
         }
 
         /**
+         * Sets the policy to indicate how activities are handled in recents.
+         *
+         * @param defaultRecentsPolicy A policy specifying how to handle activities in recents.
+         */
+        @NonNull
+        public Builder setDefaultRecentsPolicy(@RecentsPolicy int defaultRecentsPolicy) {
+            mDefaultRecentsPolicy = defaultRecentsPolicy;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
          *
          * @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -684,7 +728,8 @@
                     mDefaultActivityPolicy,
                     mName,
                     mDevicePolicies,
-                    mVirtualSensorConfigs);
+                    mVirtualSensorConfigs,
+                    mDefaultRecentsPolicy);
         }
     }
 }
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensor.java b/core/java/android/companion/virtual/sensor/VirtualSensor.java
index a184481..58a5387 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensor.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensor.java
@@ -20,6 +20,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.companion.virtual.IVirtualDevice;
+import android.hardware.Sensor;
 import android.os.IBinder;
 import android.os.RemoteException;
 
@@ -68,8 +69,10 @@
     }
 
     /**
-     * Returns the
-     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     * Returns the type of the sensor.
+     *
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
      */
     public int getType() {
         return mType;
@@ -87,7 +90,7 @@
      * Send a sensor event to the system.
      */
     @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
-    public void sendSensorEvent(@NonNull VirtualSensorEvent event) {
+    public void sendEvent(@NonNull VirtualSensorEvent event) {
         try {
             mVirtualDevice.sendSensorEvent(mToken, event);
         } catch (RemoteException e) {
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
index 7982fa5..eb2f9dd 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorConfig.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.hardware.Sensor;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -77,8 +78,10 @@
     }
 
     /**
-     * Returns the
-     * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+     * Returns the type of the sensor.
+     *
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
      */
     public int getType() {
         return mType;
@@ -150,8 +153,7 @@
         /**
          * Creates a new builder.
          *
-         * @param type The
-         * <a href="https://source.android.com/devices/sensors/sensor-types">type</a> of the sensor.
+         * @param type The type of the sensor, matching {@link Sensor#getType}.
          * @param name The name of the sensor. Must be unique among all sensors with the same type
          * that belong to the same virtual device.
          */
diff --git a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
index 8f8860e..01b4975 100644
--- a/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
+++ b/core/java/android/companion/virtual/sensor/VirtualSensorEvent.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
+import android.hardware.Sensor;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -60,9 +61,11 @@
     }
 
     /**
-     * Returns the values of this sensor event. The length and contents depend on the
-     * <a href="https://source.android.com/devices/sensors/sensor-types">sensor type</a>.
+     * Returns the values of this sensor event. The length and contents depend on the sensor type.
+     *
      * @see android.hardware.SensorEvent#values
+     * @see Sensor#getType()
+     * @see <a href="https://source.android.com/devices/sensors/sensor-types">Sensor types</a>
      */
     @NonNull
     public float[] getValues() {
@@ -90,7 +93,9 @@
 
         /**
          * Creates a new builder.
-         * @param values the values of the sensor event. @see android.hardware.SensorEvent#values
+         *
+         * @param values the values of the sensor event.
+         * @see android.hardware.SensorEvent#values
          */
         public Builder(@NonNull float[] values) {
             mValues = values;
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index da486ee..5a153ce 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -21,6 +21,11 @@
 import static android.os.Process.myUserHandle;
 import static android.os.Trace.TRACE_TAG_DATABASE;
 
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_FRAMEWORK_PERMISSION;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -58,6 +63,7 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -300,7 +306,11 @@
             uri = maybeGetUriWithoutUserId(uri);
             traceBegin(TRACE_TAG_DATABASE, "getType: ", uri.getAuthority());
             try {
-                return mInterface.getType(uri);
+                final String type = mInterface.getType(uri);
+                if (type != null) {
+                    logGetTypeData(Binder.getCallingUid(), uri, type);
+                }
+                return type;
             } catch (RemoteException e) {
                 throw e.rethrowAsRuntimeException();
             } finally {
@@ -308,6 +318,48 @@
             }
         }
 
+        // Utility function to log the getTypeData calls
+        private void logGetTypeData(int callingUid, Uri uri, String type) {
+            final int enumFrameworkPermission =
+                    GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_FRAMEWORK_PERMISSION;
+            final int enumCheckUriPermission =
+                    GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_CHECK_URI_PERMISSION;
+            final int enumError = GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__PROVIDER_ERROR;
+
+            try {
+                final AttributionSource attributionSource = new AttributionSource.Builder(
+                        callingUid).build();
+                try {
+                    if (enforceReadPermission(attributionSource, uri)
+                            != PermissionChecker.PERMISSION_GRANTED) {
+                        FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                                enumFrameworkPermission,
+                                callingUid, uri.getAuthority(), type);
+                    } else {
+                        final ProviderInfo cpi = mContext.getPackageManager()
+                                .resolveContentProvider(uri.getAuthority(),
+                                PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
+                        if (cpi.forceUriPermissions
+                                && mInterface.checkUriPermission(uri,
+                                callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                                != PermissionChecker.PERMISSION_GRANTED) {
+                            FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                                    enumCheckUriPermission,
+                                    callingUid, uri.getAuthority(), type);
+                        }
+                    }
+                } catch (SecurityException e) {
+                    FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                            enumFrameworkPermission,
+                            callingUid, uri.getAuthority(), type);
+                }
+            } catch (Exception e) {
+                FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                        enumError,
+                        callingUid, uri.getAuthority(), type);
+            }
+        }
+
         @Override
         public void getTypeAsync(Uri uri, RemoteCallback callback) {
             final Bundle result = new Bundle();
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9f9fd3c..df5a1ed 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5937,6 +5937,14 @@
     public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
 
     /**
+     * Binder service for remote key provisioning.
+     *
+     * @see android.frameworks.rkp.IRemoteProvisioning
+     * @hide
+     */
+    public static final String REMOTE_PROVISIONING_SERVICE = "remote_provisioning";
+
+    /**
      * Use with {@link #getSystemService(String)} to retrieve a
      * {@link android.hardware.lights.LightsManager} for controlling device lights.
      *
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 34c4ef3..86a672f 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5107,6 +5107,21 @@
     public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
 
     /**
+     * Activity Action: Starts a note-taking activity that can be used to create a note. This action
+     * can be used to start an activity on the lock screen. Activity should ensure to appropriately
+     * handle privacy sensitive data and features when launched on the lock screen. See
+     * {@link android.app.KeyguardManager} for lock screen checks.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE";
+
+    /**
+     * A boolean extra used with {@link #ACTION_CREATE_NOTE} indicating whether the launched
+     * note-taking activity should show a UI that is suitable to use with stylus input.
+     */
+    public static final String EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE";
+
+    /**
      * Broadcast Action: Sent to the integrity component when a package
      * needs to be verified. The data contains the package URI along with other relevant
      * information.
diff --git a/core/java/android/content/om/TEST_MAPPING b/core/java/android/content/om/TEST_MAPPING
index 6185cf6..a9aaf1a 100644
--- a/core/java/android/content/om/TEST_MAPPING
+++ b/core/java/android/content/om/TEST_MAPPING
@@ -12,6 +12,9 @@
       "name": "OverlayDeviceTests"
     },
     {
+      "name": "SelfTargetingOverlayDeviceTests"
+    },
+    {
       "name": "OverlayHostTests"
     },
     {
@@ -35,6 +38,9 @@
         },
         {
           "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
         }
       ]
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index c79f99d..1f01ae9 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -867,10 +867,15 @@
      */
     public void checkInstallConstraints(@NonNull List<String> packageNames,
             @NonNull InstallConstraints constraints,
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull Consumer<InstallConstraintsResult> callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
         try {
             var remoteCallback = new RemoteCallback(b -> {
-                callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+                executor.execute(() -> {
+                    callback.accept(b.getParcelable("result", InstallConstraintsResult.class));
+                });
             });
             mInstaller.checkInstallConstraints(
                     mInstallerPackageName, packageNames, constraints, remoteCallback);
@@ -3675,7 +3680,7 @@
     }
 
     /**
-     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}.
+     * The callback result of {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}.
      */
     @DataClass(genParcelable = true, genHiddenConstructor = true)
     public static final class InstallConstraintsResult implements Parcelable {
@@ -3783,7 +3788,7 @@
     /**
      * A class to encapsulate constraints for installation.
      *
-     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Consumer)}, it
+     * When used with {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)}, it
      * specifies the conditions to check against for the packages in question. This can be used
      * by app stores to deliver auto updates without disrupting the user experience (referred as
      * gentle update) - for example, an app store might hold off updates when it find out the
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 88b5e02..ec490d1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3849,7 +3849,10 @@
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
-     * The device supports running activities on secondary displays.
+     * The device supports running activities on secondary displays. Displays here
+     * refers to both physical and virtual displays. Disabling this feature can impact
+     * support for application projection use-cases and support for virtual devices
+     * on the device.
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS
diff --git a/core/java/android/content/pm/PermissionInfo.java b/core/java/android/content/pm/PermissionInfo.java
index 61fc6f6..4fa80d7 100644
--- a/core/java/android/content/pm/PermissionInfo.java
+++ b/core/java/android/content/pm/PermissionInfo.java
@@ -250,6 +250,16 @@
 
     /**
      * Additional flag for {@link #protectionLevel}, corresponding
+     * to the <code>module</code> value of
+     * {@link android.R.attr#protectionLevel}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int PROTECTION_FLAG_MODULE = 0x400000;
+
+    /**
+     * Additional flag for {@link #protectionLevel}, corresponding
      * to the <code>companion</code> value of
      * {@link android.R.attr#protectionLevel}.
      *
@@ -320,6 +330,7 @@
             PROTECTION_FLAG_RECENTS,
             PROTECTION_FLAG_ROLE,
             PROTECTION_FLAG_KNOWN_SIGNER,
+            PROTECTION_FLAG_MODULE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ProtectionFlags {}
@@ -593,6 +604,9 @@
         if ((level & PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER) != 0) {
             protLevel.append("|knownSigner");
         }
+        if ((level & PermissionInfo.PROTECTION_FLAG_MODULE) != 0) {
+            protLevel.append(("|module"));
+        }
         return protLevel.toString();
     }
 
diff --git a/core/java/android/credentials/ClearCredentialStateException.java b/core/java/android/credentials/ClearCredentialStateException.java
new file mode 100644
index 0000000..a6b76a7
--- /dev/null
+++ b/core/java/android/credentials/ClearCredentialStateException.java
@@ -0,0 +1,76 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#clearCredentialState(ClearCredentialStateRequest,
+ * CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class ClearCredentialStateException extends Exception {
+
+    @NonNull
+    public final String errorType;
+
+    /**
+     * Constructs a {@link ClearCredentialStateException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ClearCredentialStateException(@NonNull String errorType, @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link ClearCredentialStateException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ClearCredentialStateException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType = Preconditions.checkStringNotEmpty(errorType,
+                "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link ClearCredentialStateException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ClearCredentialStateException(@NonNull String errorType, @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link ClearCredentialStateException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public ClearCredentialStateException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/core/java/android/credentials/CreateCredentialException.java b/core/java/android/credentials/CreateCredentialException.java
new file mode 100644
index 0000000..87af208
--- /dev/null
+++ b/core/java/android/credentials/CreateCredentialException.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#executeCreateCredential(CreateCredentialRequest,
+ * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class CreateCredentialException extends Exception {
+
+    @NonNull
+    public final String errorType;
+
+    /**
+     * Constructs a {@link CreateCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public CreateCredentialException(@NonNull String errorType, @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public CreateCredentialException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType = Preconditions.checkStringNotEmpty(errorType,
+                "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public CreateCredentialException(@NonNull String errorType, @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link CreateCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public CreateCredentialException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index 4589039..be887a5 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -52,7 +52,7 @@
     private final Bundle mCandidateQueryData;
 
     /**
-     * Determines whether or not the request must only be fulfilled by a system provider.
+     * Determines whether the request must only be fulfilled by a system provider.
      */
     private final boolean mRequireSystemProvider;
 
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 1efac6c..c011949 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -77,7 +77,7 @@
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
-                    GetCredentialResponse, CredentialManagerException> callback) {
+                    GetCredentialResponse, GetCredentialException> callback) {
         requireNonNull(request, "request must not be null");
         requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
@@ -121,7 +121,7 @@
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<
-                    CreateCredentialResponse, CredentialManagerException> callback) {
+                    CreateCredentialResponse, CreateCredentialException> callback) {
         requireNonNull(request, "request must not be null");
         requireNonNull(activity, "activity must not be null");
         requireNonNull(executor, "executor must not be null");
@@ -167,7 +167,7 @@
             @NonNull ClearCredentialStateRequest request,
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
-            @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+            @NonNull OutcomeReceiver<Void, ClearCredentialStateException> callback) {
         requireNonNull(executor, "executor must not be null");
         requireNonNull(callback, "callback must not be null");
 
@@ -196,10 +196,10 @@
         private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
-                GetCredentialResponse, CredentialManagerException> mCallback;
+                GetCredentialResponse, GetCredentialException> mCallback;
 
         private GetCredentialTransport(Activity activity, Executor executor,
-                OutcomeReceiver<GetCredentialResponse, CredentialManagerException> callback) {
+                OutcomeReceiver<GetCredentialResponse, GetCredentialException> callback) {
             mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
@@ -222,9 +222,9 @@
         }
 
         @Override
-        public void onError(int errorCode, String message) {
+        public void onError(String errorType, String message) {
             mExecutor.execute(
-                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+                    () -> mCallback.onError(new GetCredentialException(errorType, message)));
         }
     }
 
@@ -234,10 +234,10 @@
         private final Activity mActivity;
         private final Executor mExecutor;
         private final OutcomeReceiver<
-                CreateCredentialResponse, CredentialManagerException> mCallback;
+                CreateCredentialResponse, CreateCredentialException> mCallback;
 
         private CreateCredentialTransport(Activity activity, Executor executor,
-                OutcomeReceiver<CreateCredentialResponse, CredentialManagerException> callback) {
+                OutcomeReceiver<CreateCredentialResponse, CreateCredentialException> callback) {
             mActivity = activity;
             mExecutor = executor;
             mCallback = callback;
@@ -260,9 +260,9 @@
         }
 
         @Override
-        public void onError(int errorCode, String message) {
+        public void onError(String errorType, String message) {
             mExecutor.execute(
-                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+                    () -> mCallback.onError(new CreateCredentialException(errorType, message)));
         }
     }
 
@@ -271,10 +271,10 @@
         // TODO: listen for cancellation to release callback.
 
         private final Executor mExecutor;
-        private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+        private final OutcomeReceiver<Void, ClearCredentialStateException> mCallback;
 
         private ClearCredentialStateTransport(Executor executor,
-                OutcomeReceiver<Void, CredentialManagerException> callback) {
+                OutcomeReceiver<Void, ClearCredentialStateException> callback) {
             mExecutor = executor;
             mCallback = callback;
         }
@@ -285,9 +285,9 @@
         }
 
         @Override
-        public void onError(int errorCode, String message) {
+        public void onError(String errorType, String message) {
             mExecutor.execute(
-                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+                    () -> mCallback.onError(new ClearCredentialStateException(errorType, message)));
         }
     }
 }
diff --git a/core/java/android/credentials/CredentialManagerException.java b/core/java/android/credentials/CredentialManagerException.java
deleted file mode 100644
index 8369649..0000000
--- a/core/java/android/credentials/CredentialManagerException.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.credentials;
-
-import android.annotation.Nullable;
-
-/** Exception class for CredentialManager operations. */
-public class CredentialManagerException extends Exception {
-    /** Indicates that an unknown error was encountered. */
-    public static final int ERROR_UNKNOWN = 0;
-
-    /**
-     * The given CredentialManager operation is cancelled by the user.
-     *
-     * @hide
-     */
-    public static final int ERROR_USER_CANCELLED = 1;
-
-    /**
-     * No appropriate provider is found to support the target credential type(s).
-     *
-     * @hide
-     */
-    public static final int ERROR_PROVIDER_NOT_FOUND = 2;
-
-    public final int errorCode;
-
-    public CredentialManagerException(int errorCode, @Nullable String message) {
-        super(message);
-        this.errorCode = errorCode;
-    }
-
-    public CredentialManagerException(
-            int errorCode, @Nullable String message, @Nullable Throwable cause) {
-        super(message, cause);
-        this.errorCode = errorCode;
-    }
-
-    public CredentialManagerException(int errorCode, @Nullable Throwable cause) {
-        super(cause);
-        this.errorCode = errorCode;
-    }
-
-    public CredentialManagerException(int errorCode) {
-        super();
-        this.errorCode = errorCode;
-    }
-}
diff --git a/core/java/android/credentials/GetCredentialException.java b/core/java/android/credentials/GetCredentialException.java
new file mode 100644
index 0000000..4d80237
--- /dev/null
+++ b/core/java/android/credentials/GetCredentialException.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.CancellationSignal;
+import android.os.OutcomeReceiver;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Represents an error encountered during the
+ * {@link CredentialManager#executeGetCredential(GetCredentialRequest,
+ * Activity, CancellationSignal, Executor, OutcomeReceiver)} operation.
+ */
+public class GetCredentialException extends Exception {
+
+    @NonNull
+    public final String errorType;
+
+    /**
+     * Constructs a {@link GetCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public GetCredentialException(@NonNull String errorType, @Nullable String message) {
+        this(errorType, message, null);
+    }
+
+    /**
+     * Constructs a {@link GetCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public GetCredentialException(
+            @NonNull String errorType, @Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+        this.errorType = Preconditions.checkStringNotEmpty(errorType,
+                "errorType must not be empty");
+    }
+
+    /**
+     * Constructs a {@link GetCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public GetCredentialException(@NonNull String errorType, @Nullable Throwable cause) {
+        this(errorType, null, cause);
+    }
+
+    /**
+     * Constructs a {@link GetCredentialException}.
+     *
+     * @throws IllegalArgumentException If errorType is empty.
+     */
+    public GetCredentialException(@NonNull String errorType) {
+        this(errorType, null, null);
+    }
+}
diff --git a/core/java/android/credentials/GetCredentialOption.java b/core/java/android/credentials/GetCredentialOption.java
index ed93dae..47731dd 100644
--- a/core/java/android/credentials/GetCredentialOption.java
+++ b/core/java/android/credentials/GetCredentialOption.java
@@ -38,13 +38,20 @@
     private final String mType;
 
     /**
-     * The request data.
+     * The full request data.
      */
     @NonNull
-    private final Bundle mData;
+    private final Bundle mCredentialRetrievalData;
 
     /**
-     * Determines whether or not the request must only be fulfilled by a system provider.
+     * The partial request data that will be sent to the provider during the initial credential
+     * candidate query stage.
+     */
+    @NonNull
+    private final Bundle mCandidateQueryData;
+
+    /**
+     * Determines whether the request must only be fulfilled by a system provider.
      */
     private final boolean mRequireSystemProvider;
 
@@ -57,11 +64,27 @@
     }
 
     /**
-     * Returns the request data.
+     * Returns the full request data.
      */
     @NonNull
-    public Bundle getData() {
-        return mData;
+    public Bundle getCredentialRetrievalData() {
+        return mCredentialRetrievalData;
+    }
+
+    /**
+     * Returns the partial request data that will be sent to the provider during the initial
+     * credential candidate query stage.
+     *
+     * For security reason, a provider will receive the request data in two stages. First it gets
+     * this partial request that do not contain sensitive user information; it uses this
+     * information to provide credential candidates that the [@code CredentialManager] will show to
+     * the user. Next, the full request data, {@link #getCredentialRetrievalData()}, will be sent to
+     * a provider only if the user further grants the consent by choosing a candidate from the
+     * provider.
+     */
+    @NonNull
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
     }
 
     /**
@@ -75,7 +98,8 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString8(mType);
-        dest.writeBundle(mData);
+        dest.writeBundle(mCredentialRetrievalData);
+        dest.writeBundle(mCandidateQueryData);
         dest.writeBoolean(mRequireSystemProvider);
     }
 
@@ -88,7 +112,8 @@
     public String toString() {
         return "GetCredentialOption {"
                 + "type=" + mType
-                + ", data=" + mData
+                + ", requestData=" + mCredentialRetrievalData
+                + ", candidateQueryData=" + mCandidateQueryData
                 + ", requireSystemProvider=" + mRequireSystemProvider
                 + "}";
     }
@@ -96,44 +121,52 @@
     /**
      * Constructs a {@link GetCredentialOption}.
      *
-     * @param type the requested credential type
-     * @param data the request data
-     * @param requireSystemProvider whether or not the request must only be fulfilled by a system
-     *                              provider
-     *
+     * @param type                    the requested credential type
+     * @param credentialRetrievalData the request data
+     * @param candidateQueryData      the partial request data that will be sent to the provider
+     *                                during the initial credential candidate query stage
+     * @param requireSystemProvider   whether the request must only be fulfilled by a system
+     *                                provider
      * @throws IllegalArgumentException If type is empty.
      */
     public GetCredentialOption(
             @NonNull String type,
-            @NonNull Bundle data,
+            @NonNull Bundle credentialRetrievalData,
+            @NonNull Bundle candidateQueryData,
             boolean requireSystemProvider) {
         mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
-        mData = requireNonNull(data, "data must not be null");
+        mCredentialRetrievalData = requireNonNull(credentialRetrievalData,
+                "requestData must not be null");
+        mCandidateQueryData = requireNonNull(candidateQueryData,
+                "candidateQueryData must not be null");
         mRequireSystemProvider = requireSystemProvider;
     }
 
     private GetCredentialOption(@NonNull Parcel in) {
         String type = in.readString8();
         Bundle data = in.readBundle();
+        Bundle candidateQueryData = in.readBundle();
         boolean requireSystemProvider = in.readBoolean();
 
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
-        mData = data;
-        AnnotationValidations.validate(NonNull.class, null, mData);
+        mCredentialRetrievalData = data;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialRetrievalData);
+        mCandidateQueryData = candidateQueryData;
+        AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
         mRequireSystemProvider = requireSystemProvider;
     }
 
     public static final @NonNull Parcelable.Creator<GetCredentialOption> CREATOR =
             new Parcelable.Creator<GetCredentialOption>() {
-        @Override
-        public GetCredentialOption[] newArray(int size) {
-            return new GetCredentialOption[size];
-        }
+                @Override
+                public GetCredentialOption[] newArray(int size) {
+                    return new GetCredentialOption[size];
+                }
 
-        @Override
-        public GetCredentialOption createFromParcel(@NonNull Parcel in) {
-            return new GetCredentialOption(in);
-        }
-    };
+                @Override
+                public GetCredentialOption createFromParcel(@NonNull Parcel in) {
+                    return new GetCredentialOption(in);
+                }
+            };
 }
diff --git a/core/java/android/credentials/IClearCredentialStateCallback.aidl b/core/java/android/credentials/IClearCredentialStateCallback.aidl
index f8b7ae44..e18e0871 100644
--- a/core/java/android/credentials/IClearCredentialStateCallback.aidl
+++ b/core/java/android/credentials/IClearCredentialStateCallback.aidl
@@ -23,5 +23,5 @@
  */
 interface IClearCredentialStateCallback {
     oneway void onSuccess();
-    oneway void onError(int errorCode, String message);
+    oneway void onError(String errorType, String message);
 }
\ No newline at end of file
diff --git a/core/java/android/credentials/ICreateCredentialCallback.aidl b/core/java/android/credentials/ICreateCredentialCallback.aidl
index 87fd36f..f6890a9 100644
--- a/core/java/android/credentials/ICreateCredentialCallback.aidl
+++ b/core/java/android/credentials/ICreateCredentialCallback.aidl
@@ -27,5 +27,5 @@
 interface ICreateCredentialCallback {
     oneway void onPendingIntent(in PendingIntent pendingIntent);
     oneway void onResponse(in CreateCredentialResponse response);
-    oneway void onError(int errorCode, String message);
+    oneway void onError(String errorType, String message);
 }
\ No newline at end of file
diff --git a/core/java/android/credentials/IGetCredentialCallback.aidl b/core/java/android/credentials/IGetCredentialCallback.aidl
index da152ba..6d1182a 100644
--- a/core/java/android/credentials/IGetCredentialCallback.aidl
+++ b/core/java/android/credentials/IGetCredentialCallback.aidl
@@ -27,5 +27,5 @@
 interface IGetCredentialCallback {
     oneway void onPendingIntent(in PendingIntent pendingIntent);
     oneway void onResponse(in GetCredentialResponse response);
-    oneway void onError(int errorCode, String message);
+    oneway void onError(String errorType, String message);
 }
\ No newline at end of file
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 18118f5..161b1b7 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -135,7 +135,7 @@
     private final boolean mIsPackageDebuggable;
     private final Context mContext;
     private final long mNativeInstance;
-    private final VirtualDeviceManager mVdm;
+    private VirtualDeviceManager mVdm;
 
     private Optional<Boolean> mHasHighSamplingRateSensorsPermission = Optional.empty();
 
@@ -154,7 +154,6 @@
         mContext = context;
         mNativeInstance = nativeCreate(context.getOpPackageName());
         mIsPackageDebuggable = (0 != (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE));
-        mVdm = mContext.getSystemService(VirtualDeviceManager.class);
 
         // initialize the sensor list
         for (int index = 0;; ++index) {
@@ -170,8 +169,7 @@
     @Override
     public List<Sensor> getSensorList(int type) {
         final int deviceId = mContext.getDeviceId();
-        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
-                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+        if (isDeviceSensorPolicyDefault(deviceId)) {
             return super.getSensorList(type);
         }
 
@@ -207,8 +205,7 @@
     @Override
     protected List<Sensor> getFullSensorList() {
         final int deviceId = mContext.getDeviceId();
-        if (deviceId == DEFAULT_DEVICE_ID || mVdm == null
-                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT) {
+        if (isDeviceSensorPolicyDefault(deviceId)) {
             return mFullSensorsList;
         }
 
@@ -1136,6 +1133,17 @@
                 parameter.type, parameter.floatValues, parameter.intValues) == 0;
     }
 
+    private boolean isDeviceSensorPolicyDefault(int deviceId) {
+        if (deviceId == DEFAULT_DEVICE_ID) {
+            return true;
+        }
+        if (mVdm == null) {
+            mVdm = mContext.getSystemService(VirtualDeviceManager.class);
+        }
+        return mVdm == null
+                || mVdm.getDevicePolicy(deviceId, POLICY_TYPE_SENSORS) == DEVICE_POLICY_DEFAULT;
+    }
+
     /**
      * Checks if a sensor should be capped according to HIGH_SAMPLING_RATE_SENSORS
      * permission.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 1ee2423..6e72b5f 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -4613,7 +4613,6 @@
      * <p>This key is available on all devices.</p>
      * @see #SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED
      * @see #SENSOR_READOUT_TIMESTAMP_HARDWARE
-     * @hide
      */
     @PublicKey
     @NonNull
@@ -5572,4 +5571,6 @@
 
 
 
+
+
 }
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 44f8b1b..b2428b1 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1695,7 +1695,6 @@
      * <p>This camera device doesn't support readout timestamp and onReadoutStarted
      * callback.</p>
      * @see CameraCharacteristics#SENSOR_READOUT_TIMESTAMP
-     * @hide
      */
     public static final int SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED = 0;
 
@@ -1705,7 +1704,6 @@
      * readout timestamp is generated by the camera hardware and it has the same accuracy
      * and timing characteristics of the start-of-exposure time.</p>
      * @see CameraCharacteristics#SENSOR_READOUT_TIMESTAMP
-     * @hide
      */
     public static final int SENSOR_READOUT_TIMESTAMP_HARDWARE = 1;
 
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index ea3e4a8..43bfdcc 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4154,6 +4154,38 @@
     public static final Key<Integer> DISTORTION_CORRECTION_MODE =
             new Key<Integer>("android.distortionCorrection.mode", int.class);
 
+    /**
+     * <p>Strength of the extension post-processing effect</p>
+     * <p>This control allows Camera extension clients to configure the strength of the applied
+     * extension effect. Strength equal to 0 means that the extension must not apply any
+     * post-processing and return a regular captured frame. Strength equal to 100 is the
+     * default level of post-processing applied when the control is not supported or not set
+     * by the client. Values between 0 and 100 will have different effect depending on the
+     * extension type as described below:</p>
+     * <ul>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
+     * the strength is expected to control the amount of blur.</li>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR} and
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} -
+     * the strength can control the amount of images fused and the brightness of the final image.</li>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_FACE_RETOUCH FACE_RETOUCH} -
+     * the strength value will control the amount of cosmetic enhancement and skin
+     * smoothing.</li>
+     * </ul>
+     * <p>The control will be supported if the capture request key is part of the list generated by
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
+     * The control is only defined and available to clients sending capture requests via
+     * {@link android.hardware.camera2.CameraExtensionSession }.
+     * The default value is 100.</p>
+     * <p><b>Range of valid values:</b><br>
+     * 0 - 100</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> EXTENSION_STRENGTH =
+            new Key<Integer>("android.extension.strength", int.class);
+
     /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * End generated code
      *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
@@ -4163,4 +4195,6 @@
 
 
 
+
+
 }
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 285c933..fb52cc6 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5576,6 +5576,62 @@
     public static final Key<Integer> DISTORTION_CORRECTION_MODE =
             new Key<Integer>("android.distortionCorrection.mode", int.class);
 
+    /**
+     * <p>Contains the extension type of the currently active extension</p>
+     * <p>The capture result will only be supported and included by camera extension
+     * {@link android.hardware.camera2.CameraExtensionSession sessions}.
+     * In case the extension session was configured to use
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO},
+     * then the extension type value will indicate the currently active extension like
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR},
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} etc.
+     * , and will never return
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO}.
+     * In case the extension session was configured to use an extension different from
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_AUTOMATIC AUTO},
+     * then the result type will always match with the configured extension type.</p>
+     * <p><b>Range of valid values:</b><br>
+     * Extension type value listed in
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics }</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> EXTENSION_CURRENT_TYPE =
+            new Key<Integer>("android.extension.currentType", int.class);
+
+    /**
+     * <p>Strength of the extension post-processing effect</p>
+     * <p>This control allows Camera extension clients to configure the strength of the applied
+     * extension effect. Strength equal to 0 means that the extension must not apply any
+     * post-processing and return a regular captured frame. Strength equal to 100 is the
+     * default level of post-processing applied when the control is not supported or not set
+     * by the client. Values between 0 and 100 will have different effect depending on the
+     * extension type as described below:</p>
+     * <ul>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
+     * the strength is expected to control the amount of blur.</li>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_HDR HDR} and
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_NIGHT NIGHT} -
+     * the strength can control the amount of images fused and the brightness of the final image.</li>
+     * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_FACE_RETOUCH FACE_RETOUCH} -
+     * the strength value will control the amount of cosmetic enhancement and skin
+     * smoothing.</li>
+     * </ul>
+     * <p>The control will be supported if the capture request key is part of the list generated by
+     * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
+     * The control is only defined and available to clients sending capture requests via
+     * {@link android.hardware.camera2.CameraExtensionSession }.
+     * The default value is 100.</p>
+     * <p><b>Range of valid values:</b><br>
+     * 0 - 100</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<Integer> EXTENSION_STRENGTH =
+            new Key<Integer>("android.extension.strength", int.class);
+
     /*~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~
      * End generated code
      *~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~@~O@*/
@@ -5585,4 +5641,6 @@
 
 
 
+
+
 }
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 8c71b36..47541ca 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -40,6 +40,7 @@
     private static final String TAG = "AmbientDisplayConfig";
     private final Context mContext;
     private final boolean mAlwaysOnByDefault;
+    private final boolean mPickupGestureEnabledByDefault;
 
     /** Copied from android.provider.Settings.Secure since these keys are hidden. */
     private static final String[] DOZE_SETTINGS = {
@@ -65,6 +66,8 @@
     public AmbientDisplayConfiguration(Context context) {
         mContext = context;
         mAlwaysOnByDefault = mContext.getResources().getBoolean(R.bool.config_dozeAlwaysOnEnabled);
+        mPickupGestureEnabledByDefault =
+                mContext.getResources().getBoolean(R.bool.config_dozePickupGestureEnabled);
     }
 
     /** @hide */
@@ -95,7 +98,8 @@
 
     /** @hide */
     public boolean pickupGestureEnabled(int user) {
-        return boolSettingDefaultOn(Settings.Secure.DOZE_PICK_UP_GESTURE, user)
+        return boolSetting(Settings.Secure.DOZE_PICK_UP_GESTURE, user,
+                mPickupGestureEnabledByDefault ? 1 : 0)
                 && dozePickupSensorAvailable();
     }
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index b26c0a2..eef0f42 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -123,6 +123,22 @@
     String[] getKeyboardLayoutListForInputDevice(in InputDeviceIdentifier identifier, int userId,
             in InputMethodInfo imeInfo, in InputMethodSubtype imeSubtype);
 
+    // Modifier key remapping APIs.
+    @EnforcePermission("REMAP_MODIFIER_KEYS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+    void remapModifierKey(int fromKey, int toKey);
+
+    @EnforcePermission("REMAP_MODIFIER_KEYS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+    void clearAllModifierKeyRemappings();
+
+    @EnforcePermission("REMAP_MODIFIER_KEYS")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.REMAP_MODIFIER_KEYS)")
+    Map getModifierKeyRemapping();
+
     // Registers an input devices changed listener.
     void registerInputDevicesChangedListener(IInputDevicesChangedListener listener);
 
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index cea3fa1..a616014 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -78,6 +78,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -253,6 +254,31 @@
     })
     public @interface SwitchState {}
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "REMAPPABLE_MODIFIER_KEY_" }, value = {
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_LEFT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_LEFT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_META_RIGHT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_LEFT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_ALT_RIGHT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT,
+            RemappableModifierKey.REMAPPABLE_MODIFIER_KEY_CAPS_LOCK,
+    })
+    public @interface RemappableModifierKey {
+        int REMAPPABLE_MODIFIER_KEY_CTRL_LEFT = KeyEvent.KEYCODE_CTRL_LEFT;
+        int REMAPPABLE_MODIFIER_KEY_CTRL_RIGHT = KeyEvent.KEYCODE_CTRL_RIGHT;
+        int REMAPPABLE_MODIFIER_KEY_META_LEFT = KeyEvent.KEYCODE_META_LEFT;
+        int REMAPPABLE_MODIFIER_KEY_META_RIGHT = KeyEvent.KEYCODE_META_RIGHT;
+        int REMAPPABLE_MODIFIER_KEY_ALT_LEFT = KeyEvent.KEYCODE_ALT_LEFT;
+        int REMAPPABLE_MODIFIER_KEY_ALT_RIGHT = KeyEvent.KEYCODE_ALT_RIGHT;
+        int REMAPPABLE_MODIFIER_KEY_SHIFT_LEFT = KeyEvent.KEYCODE_SHIFT_LEFT;
+        int REMAPPABLE_MODIFIER_KEY_SHIFT_RIGHT = KeyEvent.KEYCODE_SHIFT_RIGHT;
+        int REMAPPABLE_MODIFIER_KEY_CAPS_LOCK = KeyEvent.KEYCODE_CAPS_LOCK;
+    }
+
     /**
      * Switch State: Unknown.
      *
@@ -854,6 +880,60 @@
     }
 
     /**
+     * Remaps modifier keys. Remapping a modifier key to itself will clear any previous remappings
+     * for that key.
+     *
+     * @param fromKey The modifier key getting remapped.
+     * @param toKey The modifier key that it is remapped to.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    public void remapModifierKey(@RemappableModifierKey int fromKey,
+            @RemappableModifierKey int toKey) {
+        try {
+            mIm.remapModifierKey(fromKey, toKey);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears all existing modifier key remappings
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    public void clearAllModifierKeyRemappings() {
+        try {
+            mIm.clearAllModifierKeyRemappings();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Provides the current modifier key remapping
+     *
+     * @return a {fromKey, toKey} map that contains the existing modifier key remappings..
+     * {@link RemappableModifierKey}
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    @RequiresPermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    public Map<Integer, Integer> getModifierKeyRemapping() {
+        try {
+            return mIm.getModifierKeyRemapping();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the TouchCalibration applied to the specified input device's coordinates.
      *
      * @param inputDeviceDescriptor The input device descriptor.
@@ -1964,6 +2044,217 @@
     }
 
     /**
+     * Whether there is a gesture-compatible touchpad connected to the device.
+     * @hide
+     */
+    public boolean areTouchpadGesturesAvailable(@NonNull Context context) {
+        // TODO: implement the right logic
+        return true;
+    }
+
+    /**
+     * Gets the touchpad pointer speed.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+     * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+     *
+     * @hide
+     */
+    public int getTouchpadPointerSpeed(@NonNull Context context) {
+        int speed = DEFAULT_POINTER_SPEED;
+        // TODO: obtain the actual speed from the settings
+        return speed;
+    }
+
+    /**
+     * Sets the touchpad pointer speed, and saves it in the settings.
+     *
+     * The new speed will only apply to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+     * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+     *
+     * @hide
+     */
+    public void setTouchpadPointerSpeed(@NonNull Context context, int speed) {
+        if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
+            throw new IllegalArgumentException("speed out of range");
+        }
+
+        // TODO: set the right setting
+    }
+
+    /**
+     * Changes the touchpad pointer speed temporarily, but does not save the setting.
+     *
+     * The new speed will only apply to gesture-compatible touchpads.
+     * Requires {@link android.Manifest.permission.SET_POINTER_SPEED}.
+     *
+     * @param speed The pointer speed as a value between {@link #MIN_POINTER_SPEED} and
+     * {@link #MAX_POINTER_SPEED}, or the default value {@link #DEFAULT_POINTER_SPEED}.
+     *
+     * @hide
+     */
+    public void tryTouchpadPointerSpeed(int speed) {
+        if (speed < MIN_POINTER_SPEED || speed > MAX_POINTER_SPEED) {
+            throw new IllegalArgumentException("speed out of range");
+        }
+
+        // TODO: set the touchpad pointer speed on the gesture library
+    }
+
+    /**
+     * Returns true if the touchpad should use pointer acceleration.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether the touchpad should use pointer acceleration.
+     *
+     * @hide
+     */
+    public boolean useTouchpadPointerAcceleration(@NonNull Context context) {
+        // TODO: obtain the actual behavior from the settings
+        return true;
+    }
+
+    /**
+     * Sets the pointer acceleration behavior for the touchpad.
+     *
+     * The new behavior is only applied to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @param enabled Will enable pointer acceleration if true, disable it if false
+     *
+     * @hide
+     */
+    public void setTouchpadPointerAcceleration(@NonNull Context context, boolean enabled) {
+        // TODO: set the right setting
+    }
+
+    /**
+     * Returns true if moving two fingers upwards on the touchpad should
+     * scroll down, which is known as natural scrolling.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether the touchpad should use natural scrolling.
+     *
+     * @hide
+     */
+    public boolean useTouchpadNaturalScrolling(@NonNull Context context) {
+        // TODO: obtain the actual behavior from the settings
+        return true;
+    }
+
+    /**
+     * Sets the natural scroll behavior for the touchpad.
+     *
+     * If natural scrolling is enabled, moving two fingers upwards on the
+     * touchpad will scroll down.
+     *
+     * @param context The application context.
+     * @param enabled Will enable natural scroll if true, disable it if false
+     *
+     * @hide
+     */
+    public void setTouchpadNaturalScrolling(@NonNull Context context, boolean enabled) {
+        // TODO: set the right setting
+    }
+
+    /**
+     * Returns true if the touchpad should use tap to click.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether the touchpad should use tap to click.
+     *
+     * @hide
+     */
+    public boolean useTouchpadTapToClick(@NonNull Context context) {
+        // TODO: obtain the actual behavior from the settings
+        return true;
+    }
+
+    /**
+     * Sets the tap to click behavior for the touchpad.
+     *
+     * The new behavior is only applied to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @param enabled Will enable tap to click if true, disable it if false
+     *
+     * @hide
+     */
+    public void setTouchpadTapToClick(@NonNull Context context, boolean enabled) {
+        // TODO: set the right setting
+    }
+
+    /**
+     * Returns true if the touchpad should use tap dragging.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether the touchpad should use tap dragging.
+     *
+     * @hide
+     */
+    public boolean useTouchpadTapDragging(@NonNull Context context) {
+        // TODO: obtain the actual behavior from the settings
+        return true;
+    }
+
+    /**
+     * Sets the tap dragging behavior for the touchpad.
+     *
+     * The new behavior is only applied to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @param enabled Will enable tap dragging if true, disable it if false
+     *
+     * @hide
+     */
+    public void setTouchpadTapDragging(@NonNull Context context, boolean enabled) {
+        // TODO: set the right setting
+    }
+
+    /**
+     * Returns true if the touchpad should use the right click zone.
+     *
+     * The returned value only applies to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @return Whether the touchpad should use the right click zone.
+     *
+     * @hide
+     */
+    public boolean useTouchpadRightClickZone(@NonNull Context context) {
+        // TODO: obtain the actual behavior from the settings
+        return true;
+    }
+
+    /**
+     * Sets the right click zone behavior for the touchpad.
+     *
+     * The new behavior is only applied to gesture-compatible touchpads.
+     *
+     * @param context The application context.
+     * @param enabled Will enable the right click zone if true, disable it if false
+     *
+     * @hide
+     */
+    public void setTouchpadRightClickZone(@NonNull Context context, boolean enabled) {
+        // TODO: set the right setting
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
diff --git a/core/java/android/hardware/input/VirtualDpad.java b/core/java/android/hardware/input/VirtualDpad.java
index 4d61553..8133472 100644
--- a/core/java/android/hardware/input/VirtualDpad.java
+++ b/core/java/android/hardware/input/VirtualDpad.java
@@ -32,8 +32,8 @@
 /**
  * A virtual dpad representing a key input mechanism on a remote device.
  *
- * This registers an InputDevice that is interpreted like a physically-connected device and
- * dispatches received events to it.
+ * <p>This registers an InputDevice that is interpreted like a physically-connected device and
+ * dispatches received events to it.</p>
  *
  * @hide
  */
@@ -44,6 +44,7 @@
             Collections.unmodifiableSet(
                     new HashSet<>(
                             Arrays.asList(
+                                    KeyEvent.KEYCODE_BACK,
                                     KeyEvent.KEYCODE_DPAD_UP,
                                     KeyEvent.KEYCODE_DPAD_DOWN,
                                     KeyEvent.KEYCODE_DPAD_LEFT,
@@ -58,8 +59,15 @@
     /**
      * Sends a key event to the system.
      *
-     * Supported key codes are KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT,
-     * KEYCODE_DPAD_RIGHT and KEYCODE_DPAD_CENTER,
+     * <p>Supported key codes are:
+     * <ul>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_UP}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_DOWN}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_LEFT}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_RIGHT}</li>
+     *     <li>{@link KeyEvent.KEYCODE_DPAD_CENTER}</li>
+     *     <li>{@link KeyEvent.KEYCODE_BACK}</li>
+     * </ul>
      *
      * @param event the event to send
      */
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 7a55a5c..e915d98 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -650,8 +650,11 @@
             return Uid.PROCESS_STATE_NONEXISTENT;
         } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
             return Uid.PROCESS_STATE_TOP;
-        } else if (ActivityManager.isForegroundService(procState)) {
-            // State when app has put itself in the foreground.
+        } else if (procState == ActivityManager.PROCESS_STATE_BOUND_TOP) {
+            return Uid.PROCESS_STATE_BACKGROUND;
+        } else if (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+        } else if (procState == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
             return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
         } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
             // Persistent and other foreground states go here.
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 4c9ae37..def0cbd 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -366,7 +366,7 @@
      */
     public static final int getCallingUidOrWtf() {
         if (!isDirectlyHandlingTransaction() && !hasExplicitIdentity()) {
-            Log.wtf(TAG,
+            Log.wtfStack(TAG,
                     "Thread is not in a binder transaction, "
                             + "and the calling identity has not been "
                             + "explicitly set with clearCallingIdentity");
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 44a1fa5..249f486 100755
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -1446,28 +1446,6 @@
         return IS_DEBUGGABLE;
     }
 
-
-    /**
-     * Returns true if the device is running a secure build, such as "user" or "userdebug".
-     *
-     * Secure builds drop adbd privileges by default, though debuggable builds still allow users
-     * to gain root access via local shell. See should_drop_privileges() in adb for details.
-     * @hide
-     */
-    private static final boolean IS_SECURE =
-            SystemProperties.getBoolean("ro.secure", true);
-    /**
-     * Returns true if the device is running a secure build, such as "user" or "userdebug".
-     *
-     * Secure builds drop adbd privileges by default, though debuggable builds still allow users
-     * to gain root access via local shell. See should_drop_privileges() in adb for details.
-     * @hide
-     */
-    @TestApi
-    public static boolean isSecure() {
-        return IS_SECURE;
-    }
-
     /** {@hide} */
     public static final boolean IS_ENG = "eng".equals(TYPE);
     /** {@hide} */
diff --git a/core/java/android/os/CoolingDevice.java b/core/java/android/os/CoolingDevice.java
index 4babd4b..4ddcd9d 100644
--- a/core/java/android/os/CoolingDevice.java
+++ b/core/java/android/os/CoolingDevice.java
@@ -19,7 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.thermal.V2_0.CoolingType;
+import android.hardware.thermal.CoolingType;
 
 import com.android.internal.util.Preconditions;
 
@@ -52,11 +52,16 @@
             TYPE_MODEM,
             TYPE_NPU,
             TYPE_COMPONENT,
+            TYPE_TPU,
+            TYPE_POWER_AMPLIFIER,
+            TYPE_DISPLAY,
+            TYPE_SPEAKER
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
 
-    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    /** Keep in sync with hardware/interfaces/thermal/aidl/android/hardware/thermal
+     * /ThrottlingSeverity.aidl */
     /** Fan for active cooling */
     public static final int TYPE_FAN = CoolingType.FAN;
     /** Battery charging cooling deivice */
@@ -67,10 +72,18 @@
     public static final int TYPE_GPU = CoolingType.GPU;
     /** Modem cooling deivice */
     public static final int TYPE_MODEM = CoolingType.MODEM;
-    /** NPU/TPU cooling deivice */
+    /** NPU cooling deivice */
     public static final int TYPE_NPU = CoolingType.NPU;
     /** Generic passive cooling deivice */
     public static final int TYPE_COMPONENT = CoolingType.COMPONENT;
+    /** TPU cooling deivice */
+    public static final int TYPE_TPU = CoolingType.TPU;
+    /** Power amplifier cooling device */
+    public static final int TYPE_POWER_AMPLIFIER = CoolingType.POWER_AMPLIFIER;
+    /** Display cooling device */
+    public static final int TYPE_DISPLAY = CoolingType.DISPLAY;
+    /** Speaker cooling device */
+    public static final int TYPE_SPEAKER = CoolingType.SPEAKER;
 
     /**
      * Verify a valid cooling device type.
@@ -78,7 +91,7 @@
      * @return true if a cooling device type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_FAN && type <= TYPE_COMPONENT;
+        return type >= TYPE_FAN && type <= TYPE_SPEAKER;
     }
 
     public CoolingDevice(long value, @Type int type, @NonNull String name) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index d31540a..c2ddf45 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,6 +79,7 @@
     String getUserAccount(int userId);
     void setUserAccount(int userId, String accountName);
     long getUserCreationTime(int userId);
+    int getUserSwitchability(int userId);
     boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable, int mUserId);
     boolean isRestricted(int userId);
     boolean canHaveRestrictedProfile(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 5c5af2a..e9a3254 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -71,6 +71,9 @@
 # Tracing
 per-file Trace.java = file:/TRACE_OWNERS
 
+# PatternMatcher
+per-file PatternMatcher* = file:/PACKAGE_MANAGER_OWNERS
+
 # PermissionEnforcer
 per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
 
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 9bc7ffd..dc62f8c 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -533,10 +533,7 @@
             BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM,
             BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT,
             BRIGHTNESS_CONSTRAINT_TYPE_DIM,
-            BRIGHTNESS_CONSTRAINT_TYPE_DOZE,
-            BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR,
-            BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR,
-            BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR
+            BRIGHTNESS_CONSTRAINT_TYPE_DOZE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BrightnessConstraint{}
@@ -571,24 +568,6 @@
     public static final int BRIGHTNESS_CONSTRAINT_TYPE_DOZE = 4;
 
     /**
-     * Brightness constraint type: minimum allowed value.
-     * @hide
-     */
-    public static final int BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR = 5;
-
-    /**
-     * Brightness constraint type: minimum allowed value.
-     * @hide
-     */
-    public static final int BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR = 6;
-
-    /**
-     * Brightness constraint type: minimum allowed value.
-     * @hide
-     */
-    public static final int BRIGHTNESS_CONSTRAINT_TYPE_DEFAULT_VR = 7;
-
-    /**
      * @hide
      */
     @IntDef(prefix = { "WAKE_REASON_" }, value = {
@@ -1211,35 +1190,6 @@
     }
 
     /**
-     * Gets the minimum supported screen brightness setting for VR Mode.
-     * @hide
-     */
-    public int getMinimumScreenBrightnessForVrSetting() {
-        return mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessForVrSettingMinimum);
-    }
-
-    /**
-     * Gets the maximum supported screen brightness setting for VR Mode.
-     * The screen may be allowed to become dimmer than this value but
-     * this is the maximum value that can be set by the user.
-     * @hide
-     */
-    public int getMaximumScreenBrightnessForVrSetting() {
-        return mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessForVrSettingMaximum);
-    }
-
-    /**
-     * Gets the default screen brightness for VR setting.
-     * @hide
-     */
-    public int getDefaultScreenBrightnessForVrSetting() {
-        return mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault);
-    }
-
-    /**
      * Gets a float screen brightness setting.
      * @hide
      */
diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java
index 55785f3..a138431 100644
--- a/core/java/android/os/Temperature.java
+++ b/core/java/android/os/Temperature.java
@@ -19,8 +19,8 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.thermal.V2_0.TemperatureType;
-import android.hardware.thermal.V2_0.ThrottlingSeverity;
+import android.hardware.thermal.TemperatureType;
+import android.hardware.thermal.ThrottlingSeverity;
 
 import com.android.internal.util.Preconditions;
 
@@ -54,7 +54,8 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface ThrottlingStatus {}
 
-    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    /** Keep in sync with hardware/interfaces/thermal/aidl/android/hardware/thermal
+     * /ThrottlingSeverity.aidl */
     public static final int THROTTLING_NONE = ThrottlingSeverity.NONE;
     public static final int THROTTLING_LIGHT = ThrottlingSeverity.LIGHT;
     public static final int THROTTLING_MODERATE = ThrottlingSeverity.MODERATE;
@@ -75,11 +76,16 @@
             TYPE_BCL_CURRENT,
             TYPE_BCL_PERCENTAGE,
             TYPE_NPU,
+            TYPE_TPU,
+            TYPE_DISPLAY,
+            TYPE_MODEM,
+            TYPE_SOC
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Type {}
 
-    /** Keep in sync with hardware/interfaces/thermal/2.0/types.hal */
+    /** Keep in sync with hardware/interfaces/thermal/aidl/android/hardware/thermal
+     * /TemperatureType.aidl */
     public static final int TYPE_UNKNOWN = TemperatureType.UNKNOWN;
     public static final int TYPE_CPU = TemperatureType.CPU;
     public static final int TYPE_GPU = TemperatureType.GPU;
@@ -91,6 +97,10 @@
     public static final int TYPE_BCL_CURRENT = TemperatureType.BCL_CURRENT;
     public static final int TYPE_BCL_PERCENTAGE = TemperatureType.BCL_PERCENTAGE;
     public static final int TYPE_NPU = TemperatureType.NPU;
+    public static final int TYPE_TPU = TemperatureType.TPU;
+    public static final int TYPE_DISPLAY = TemperatureType.DISPLAY;
+    public static final int TYPE_MODEM = TemperatureType.MODEM;
+    public static final int TYPE_SOC = TemperatureType.SOC;
 
     /**
      * Verify a valid Temperature type.
@@ -98,7 +108,7 @@
      * @return true if a Temperature type is valid otherwise false.
      */
     public static boolean isValidType(@Type int type) {
-        return type >= TYPE_UNKNOWN && type <= TYPE_NPU;
+        return type >= TYPE_UNKNOWN && type <= TYPE_SOC;
     }
 
     /**
diff --git a/core/java/android/os/UidBatteryConsumer.java b/core/java/android/os/UidBatteryConsumer.java
index 4a6772d..103452d 100644
--- a/core/java/android/os/UidBatteryConsumer.java
+++ b/core/java/android/os/UidBatteryConsumer.java
@@ -50,8 +50,7 @@
     }
 
     /**
-     * The state of an application when it is either running a foreground (top) activity
-     * or a foreground service.
+     * The state of an application when it is either running a foreground (top) activity.
      */
     public static final int STATE_FOREGROUND = 0;
 
@@ -63,7 +62,8 @@
      * {@link android.app.ActivityManager#PROCESS_STATE_TRANSIENT_BACKGROUND},
      * {@link android.app.ActivityManager#PROCESS_STATE_BACKUP},
      * {@link android.app.ActivityManager#PROCESS_STATE_SERVICE},
-     * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER}.
+     * {@link android.app.ActivityManager#PROCESS_STATE_RECEIVER},
+     * {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}.
      */
     public static final int STATE_BACKGROUND = 1;
 
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 07c4b44..5f2f710 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -59,7 +59,6 @@
 import android.graphics.drawable.Drawable;
 import android.location.LocationManager;
 import android.provider.Settings;
-import android.telecom.TelecomManager;
 import android.util.AndroidException;
 import android.util.ArraySet;
 import android.util.Log;
@@ -1748,7 +1747,7 @@
     public static final int SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED = 1 << 2;
 
     /**
-     * Result returned in {@link #getUserSwitchability()} indicating user swichability.
+     * Result returned in {@link #getUserSwitchability()} indicating user switchability.
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -2128,20 +2127,16 @@
      * @hide
      */
     @Deprecated
-    @RequiresPermission(allOf = {
-            Manifest.permission.READ_PHONE_STATE,
-            Manifest.permission.MANAGE_USERS}, // Can be INTERACT_ACROSS_USERS instead.
-            conditional = true)
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @UserHandleAware
     public boolean canSwitchUsers() {
-        boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
-                mContext.getContentResolver(),
-                Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
-        boolean isSystemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-        boolean isUserSwitchDisallowed = hasUserRestrictionForUser(DISALLOW_USER_SWITCH, mUserId);
-        return (allowUserSwitchingWhenSystemUserLocked || isSystemUserUnlocked) && !inCall()
-                && !isUserSwitchDisallowed;
+        try {
+            return mService.getUserSwitchability(mUserId) == SWITCHABILITY_STATUS_OK;
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -2156,9 +2151,8 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
     @UserHandleAware(enabledSinceTargetSdkVersion = Build.VERSION_CODES.TIRAMISU)
     public @UserSwitchabilityResult int getUserSwitchability() {
         return getUserSwitchability(UserHandle.of(getContextUserIfAppropriate()));
@@ -2175,31 +2169,14 @@
      * @return A {@link UserSwitchabilityResult} flag indicating if the user is switchable.
      * @hide
      */
-    @RequiresPermission(allOf = {Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.MANAGE_USERS,
-            android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
+    @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+            android.Manifest.permission.INTERACT_ACROSS_USERS})
     public @UserSwitchabilityResult int getUserSwitchability(UserHandle userHandle) {
-        int flags = SWITCHABILITY_STATUS_OK;
-        if (inCall()) {
-            flags |= SWITCHABILITY_STATUS_USER_IN_CALL;
+        try {
+            return mService.getUserSwitchability(userHandle.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
         }
-        if (hasUserRestrictionForUser(DISALLOW_USER_SWITCH, userHandle)) {
-            flags |= SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
-        }
-
-        // System User is always unlocked in Headless System User Mode, so ignore this flag
-        if (!isHeadlessSystemUserMode()) {
-            final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
-            final boolean systemUserUnlocked = isUserUnlocked(UserHandle.SYSTEM);
-
-            if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
-                flags |= SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
-            }
-        }
-
-        return flags;
     }
 
     /**
@@ -5614,11 +5591,6 @@
         }
     }
 
-    private boolean inCall() {
-        final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
-        return telecomManager != null && telecomManager.isInCall();
-    }
-
     /* Cache key for anything that assumes that userIds cannot be re-used without rebooting. */
     private static final String CACHE_KEY_STATIC_USER_PROPERTIES = "cache_key.static_user_props";
 
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 7899420..5527c69 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -17,18 +17,7 @@
 package android.os.storage;
 
 import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE;
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
-import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.OP_LEGACY_STORAGE;
-import static android.app.AppOpsManager.OP_MANAGE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_READ_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_READ_MEDIA_AUDIO;
-import static android.app.AppOpsManager.OP_READ_MEDIA_IMAGES;
-import static android.app.AppOpsManager.OP_READ_MEDIA_VIDEO;
-import static android.app.AppOpsManager.OP_WRITE_EXTERNAL_STORAGE;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_AUDIO;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_IMAGES;
-import static android.app.AppOpsManager.OP_WRITE_MEDIA_VIDEO;
 import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.UserHandle.PER_USER_RANGE;
@@ -1869,88 +1858,17 @@
     // handle obscure cases like when an app targets Q but was installed on
     // a device that was originally running on P before being upgraded to Q.
 
-    /** {@hide} */
-    public boolean checkPermissionReadAudio(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_READ_MEDIA_AUDIO);
-    }
-
-    /** {@hide} */
-    public boolean checkPermissionWriteAudio(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_WRITE_MEDIA_AUDIO);
-    }
-
-    /** {@hide} */
-    public boolean checkPermissionReadVideo(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_READ_MEDIA_VIDEO);
-    }
-
-    /** {@hide} */
-    public boolean checkPermissionWriteVideo(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_WRITE_MEDIA_VIDEO);
-    }
-
-    /** {@hide} */
+    /**
+     * @deprecated This method always returns false and should not be used.
+     * Clients should check the appropriate permissions directly instead
+     * (e.g. READ_MEDIA_IMAGES).
+     *
+     * {@hide}
+     */
+    @Deprecated
     public boolean checkPermissionReadImages(boolean enforce,
             int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_READ_MEDIA_IMAGES);
-    }
-
-    /** {@hide} */
-    public boolean checkPermissionWriteImages(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId) {
-        if (!checkExternalStoragePermissionAndAppOp(enforce, pid, uid, packageName, featureId,
-                WRITE_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE)) {
-            return false;
-        }
-        return noteAppOpAllowingLegacy(enforce, pid, uid, packageName, featureId,
-                OP_WRITE_MEDIA_IMAGES);
-    }
-
-    private boolean checkExternalStoragePermissionAndAppOp(boolean enforce,
-            int pid, int uid, String packageName, @Nullable String featureId, String permission,
-            int op) {
-        // First check if app has MANAGE_EXTERNAL_STORAGE.
-        final int mode = mAppOps.noteOpNoThrow(OP_MANAGE_EXTERNAL_STORAGE, uid, packageName,
-                featureId, null);
-        if (mode == AppOpsManager.MODE_ALLOWED) {
-            return true;
-        }
-        if (mode == AppOpsManager.MODE_DEFAULT && mContext.checkPermission(
-                  MANAGE_EXTERNAL_STORAGE, pid, uid) == PERMISSION_GRANTED) {
-            return true;
-        }
-        // If app doesn't have MANAGE_EXTERNAL_STORAGE, then check if it has requested granular
-        // permission.
-        return checkPermissionAndAppOp(enforce, pid, uid, packageName, featureId, permission, op);
+        return false;
     }
 
     /** {@hide} */
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
deleted file mode 100644
index ca88ae3..0000000
--- a/core/java/android/provider/DeviceConfig.java
+++ /dev/null
@@ -1,1568 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.provider;
-
-import static android.Manifest.permission.READ_DEVICE_CONFIG;
-import static android.Manifest.permission.WRITE_DEVICE_CONFIG;
-
-import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.annotation.TestApi;
-import android.content.pm.PackageManager;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.provider.Settings.Config.SyncDisabledMode;
-import android.provider.Settings.ResetMode;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executor;
-
-/**
- * Device level configuration parameters which can be tuned by a separate configuration service.
- * Namespaces that end in "_native" such as {@link #NAMESPACE_NETD_NATIVE} are intended to be used
- * by native code and should be pushed to system properties to make them accessible.
- *
- * @hide
- */
-@SystemApi
-public final class DeviceConfig {
-    /**
-     * The content:// style URL for the config table.
-     *
-     * @hide
-     */
-    public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
-
-    /**
-     * Namespace for activity manager related features. These features will be applied
-     * immediately upon change.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ACTIVITY_MANAGER = "activity_manager";
-
-    /**
-     * Namespace for activity manager, specific to the "component alias" feature. We needed a
-     * different namespace in order to avoid phonetype from resetting it.
-     * @hide
-     */
-    public static final String NAMESPACE_ACTIVITY_MANAGER_COMPONENT_ALIAS = "activity_manager_ca";
-
-    /**
-     * Namespace for all activity manager related features that are used at the native level.
-     * These features are applied at reboot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT =
-            "activity_manager_native_boot";
-
-    /**
-     * Namespace for AlarmManager configurations.
-     *
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
-    public static final String NAMESPACE_ALARM_MANAGER = "alarm_manager";
-
-    /**
-     * Namespace for all app compat related features.  These features will be applied
-     * immediately upon change.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_APP_COMPAT = "app_compat";
-
-    /**
-     * Namespace for all app hibernation related features.
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_APP_HIBERNATION = "app_hibernation";
-
-    /**
-     * Namespace for all AppSearch related features.
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_APPSEARCH = "appsearch";
-
-    /**
-     * Namespace for app standby configurations.
-     *
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public static final String NAMESPACE_APP_STANDBY = "app_standby";
-
-    /**
-     * Namespace for all App Cloning related features.
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    public static final String NAMESPACE_APP_CLONING = "app_cloning";
-
-    /**
-     * Namespace for AttentionManagerService related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ATTENTION_MANAGER_SERVICE = "attention_manager_service";
-
-    /**
-     * Namespace for autofill feature that provides suggestions across all apps when
-     * the user interacts with input fields.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_AUTOFILL = "autofill";
-
-    /**
-     * Namespace for battery saver feature.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_BATTERY_SAVER = "battery_saver";
-
-    /**
-     * Namespace for blobstore feature that allows apps to share data blobs.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_BLOBSTORE = "blobstore";
-
-    /**
-     * Namespace for all Bluetooth related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_BLUETOOTH = "bluetooth";
-
-    /**
-     * Namespace for features relating to clipboard.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_CLIPBOARD = "clipboard";
-
-    /**
-     * Namespace for all networking connectivity related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_CONNECTIVITY = "connectivity";
-
-    /**
-     * Namespace for CaptivePortalLogin module.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_CAPTIVEPORTALLOGIN = "captive_portal_login";
-
-    /**
-     * Namespace for Tethering module.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_TETHERING = "tethering";
-
-
-    /**
-     * Namespace for Nearby module.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_NEARBY = "nearby";
-
-    /**
-     * Namespace for content capture feature used by on-device machine intelligence
-     * to provide suggestions in a privacy-safe manner.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_CONTENT_CAPTURE = "content_capture";
-
-    /**
-     * Namespace for device idle configurations.
-     *
-     * @hide
-     */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
-    @TestApi
-    public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
-
-    /**
-     * Namespace for how dex runs. The feature requires a reboot to reach a clean state.
-     *
-     * @deprecated No longer used
-     * @hide
-     */
-    @Deprecated
-    @SystemApi
-    public static final String NAMESPACE_DEX_BOOT = "dex_boot";
-
-    /**
-     * Namespace for display manager related features. The names to access the properties in this
-     * namespace should be defined in {@link android.hardware.display.DisplayManager}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
-
-    /**
-     * Namespace for all Game Driver features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_GAME_DRIVER = "game_driver";
-
-    /**
-     * Namespace for all HDMI Control features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_HDMI_CONTROL = "hdmi_control";
-
-    /**
-     * Namespace for all input-related features that are used at the native level.
-     * These features are applied at reboot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
-
-    /**
-     * Namespace for attention-based features provided by on-device machine intelligence.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_INTELLIGENCE_ATTENTION = "intelligence_attention";
-
-    /**
-     * Definitions for properties related to Content Suggestions.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_INTELLIGENCE_CONTENT_SUGGESTIONS =
-            "intelligence_content_suggestions";
-
-    /**
-     * Namespace for JobScheduler configurations.
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
-
-    /**
-     * Namespace for all lmkd related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_LMKD_NATIVE = "lmkd_native";
-
-    /**
-     * Namespace for all location related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_LOCATION = "location";
-
-    /**
-     * Namespace for all media related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_MEDIA = "media";
-
-    /**
-     * Namespace for all media native related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_MEDIA_NATIVE = "media_native";
-
-    /**
-     * Namespace for all Kernel Multi-Gen LRU feature.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_MGLRU_NATIVE = "mglru_native";
-
-    /**
-     * Namespace for all netd related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_NETD_NATIVE = "netd_native";
-
-    /**
-     * Namespace for all Android NNAPI related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_NNAPI_NATIVE = "nnapi_native";
-
-    /**
-     * Namespace for all OnDevicePersonalization related feature.
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ON_DEVICE_PERSONALIZATION = "on_device_personalization";
-
-    /**
-     * Namespace for features related to the Package Manager Service.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_PACKAGE_MANAGER_SERVICE = "package_manager_service";
-
-    /**
-     * Namespace for features related to the Profcollect native Service.
-     * These features are applied at reboot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_PROFCOLLECT_NATIVE_BOOT = "profcollect_native_boot";
-
-    /**
-     * Namespace for features related to Reboot Readiness detection.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_REBOOT_READINESS = "reboot_readiness";
-
-    /**
-     * Namespace for Remote Key Provisioning related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_REMOTE_KEY_PROVISIONING_NATIVE =
-            "remote_key_provisioning_native";
-
-    /**
-     * Namespace for Rollback flags that are applied immediately.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ROLLBACK = "rollback";
-
-    /**
-     * Namespace for Rollback flags that are applied after a reboot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot";
-
-    /**
-     * Namespace for Rotation Resolver Manager Service.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_ROTATION_RESOLVER = "rotation_resolver";
-
-    /**
-     * Namespace for all runtime related features that don't require a reboot to become active.
-     * There are no feature flags using NAMESPACE_RUNTIME.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_RUNTIME = "runtime";
-
-    /**
-     * Namespace for all runtime related features that require system properties for accessing
-     * the feature flags from C++ or Java language code. One example is the app image startup
-     * cache feature use_app_image_startup_cache.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_RUNTIME_NATIVE = "runtime_native";
-
-    /**
-     * Namespace for all runtime native boot related features. Boot in this case refers to the
-     * fact that the properties only take affect after rebooting the device.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_RUNTIME_NATIVE_BOOT = "runtime_native_boot";
-
-    /**
-     * Namespace for system scheduler related features. These features will be applied
-     * immediately upon change.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SCHEDULER = "scheduler";
-
-    /**
-     * Namespace for all SdkSandbox related features.
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SDK_SANDBOX = "sdk_sandbox";
-
-    /**
-     * Namespace for settings statistics features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_SETTINGS_STATS = "settings_stats";
-
-    /**
-     * Namespace for all statsd java features that can be applied immediately.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_JAVA = "statsd_java";
-
-    /**
-     * Namespace for all statsd java features that are applied on boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_JAVA_BOOT = "statsd_java_boot";
-
-    /**
-     * Namespace for all statsd native features that can be applied immediately.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_NATIVE = "statsd_native";
-
-    /**
-     * Namespace for all statsd native features that are applied on boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STATSD_NATIVE_BOOT = "statsd_native_boot";
-
-    /**
-     * Namespace for storage-related features.
-     *
-     * @deprecated Replace storage namespace with storage_native_boot.
-     * @hide
-     */
-    @Deprecated
-    @SystemApi
-    public static final String NAMESPACE_STORAGE = "storage";
-
-    /**
-     * Namespace for storage-related features, including native and boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_STORAGE_NATIVE_BOOT = "storage_native_boot";
-
-    /**
-     * Namespace for all AdServices related features.
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_ADSERVICES = "adservices";
-
-    /**
-     * Namespace for all SurfaceFlinger features that are used at the native level.
-     * These features are applied on boot or after reboot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT =
-            "surface_flinger_native_boot";
-
-    /**
-     * Namespace for swcodec native related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SWCODEC_NATIVE = "swcodec_native";
-
-
-    /**
-     * Namespace for System UI related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SYSTEMUI = "systemui";
-
-    /**
-     * Namespace for system time and time zone detection related features / behavior.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_SYSTEM_TIME = "system_time";
-
-    /**
-     * Namespace for TARE configurations.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_TARE = "tare";
-
-    /**
-     * Telephony related properties.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_TELEPHONY = "telephony";
-
-    /**
-     * Namespace for TextClassifier related features.
-     *
-     * @hide
-     * @see android.provider.Settings.Global.TEXT_CLASSIFIER_CONSTANTS
-     */
-    @SystemApi
-    public static final String NAMESPACE_TEXTCLASSIFIER = "textclassifier";
-
-    /**
-     * Namespace for contacts provider related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_CONTACTS_PROVIDER = "contacts_provider";
-
-    /**
-     * Namespace for settings ui related features
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_SETTINGS_UI = "settings_ui";
-
-    /**
-     * Namespace for android related features, i.e. for flags that affect not just a single
-     * component, but the entire system.
-     *
-     * The keys for this namespace are defined in {@link AndroidDeviceConfig}.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_ANDROID = "android";
-
-    /**
-     * Namespace for window manager related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_WINDOW_MANAGER = "window_manager";
-
-    /**
-     * Namespace for window manager features accessible by native code and
-     * loaded once per boot.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT = "window_manager_native_boot";
-
-    /**
-     * Definitions for selection toolbar related functions.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
-
-    /**
-     * Definitions for voice interaction related functions.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_VOICE_INTERACTION = "voice_interaction";
-
-    /**
-     * Namespace for DevicePolicyManager related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_DEVICE_POLICY_MANAGER =
-            "device_policy_manager";
-
-    /**
-     * List of namespaces which can be read without READ_DEVICE_CONFIG permission
-     *
-     * @hide
-     */
-    @NonNull
-    private static final List<String> PUBLIC_NAMESPACES =
-            Arrays.asList(NAMESPACE_TEXTCLASSIFIER, NAMESPACE_RUNTIME, NAMESPACE_STATSD_JAVA,
-                    NAMESPACE_STATSD_JAVA_BOOT, NAMESPACE_SELECTION_TOOLBAR, NAMESPACE_AUTOFILL,
-                    NAMESPACE_DEVICE_POLICY_MANAGER, NAMESPACE_CONTENT_CAPTURE);
-    /**
-     * Privacy related properties definitions.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_PRIVACY = "privacy";
-
-    /**
-     * Namespace for biometrics related features
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_BIOMETRICS = "biometrics";
-
-    /**
-     * Permission related properties definitions.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_PERMISSIONS = "permissions";
-
-    /**
-     * Namespace for ota related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_OTA = "ota";
-
-    /**
-     * Namespace for all widget related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_WIDGET = "widget";
-
-    /**
-     * Namespace for connectivity thermal power manager features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_CONNECTIVITY_THERMAL_POWER_MANAGER =
-            "connectivity_thermal_power_manager";
-
-    /**
-     * Namespace for configuration related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_CONFIGURATION = "configuration";
-
-    /**
-     * LatencyTracker properties definitions.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_LATENCY_TRACKER = "latency_tracker";
-
-    /**
-     * InteractionJankMonitor properties definitions.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_INTERACTION_JANK_MONITOR = "interaction_jank_monitor";
-
-    /**
-     * Namespace for game overlay related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_GAME_OVERLAY = "game_overlay";
-
-    /**
-     * Namespace for Android Virtualization Framework related features accessible by native code.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE =
-            "virtualization_framework_native";
-
-    /**
-     * Namespace for Constrain Display APIs related features.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
-
-    /**
-     * Namespace for App Compat Overrides related features.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_APP_COMPAT_OVERRIDES = "app_compat_overrides";
-
-    /**
-     * Namespace for all ultra wideband (uwb) related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_UWB = "uwb";
-
-    /**
-     * Namespace for AmbientContextEventManagerService related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE =
-            "ambient_context_manager_service";
-
-    /**
-     * Namespace for WearableSensingManagerService related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_WEARABLE_SENSING =
-            "wearable_sensing";
-
-    /**
-     * Namespace for Vendor System Native related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native";
-
-    /**
-     * Namespace for Vendor System Native Boot related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT = "vendor_system_native_boot";
-
-    /**
-     * Namespace for memory safety related features (e.g. MTE)
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_MEMORY_SAFETY_NATIVE = "memory_safety_native";
-
-    /**
-     * Namespace for wear OS platform features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_WEAR = "wear";
-
-    /**
-     * Namespace for features relating to MBA transparency metadata.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_TRANSPARENCY_METADATA = "transparency_metadata";
-
-    /**
-     * Namespace for the input method manager platform features.
-     *
-     * @hide
-     */
-    @TestApi
-    public static final String NAMESPACE_INPUT_METHOD_MANAGER = "input_method_manager";
-
-    /**
-     * Namespace for backup and restore service related features.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String NAMESPACE_BACKUP_AND_RESTORE = "backup_and_restore";
-
-    /**
-     * Namespace for ARC App Compat related features.
-     *
-     * @hide
-     */
-    public static final String NAMESPACE_ARC_APP_COMPAT = "arc_app_compat";
-
-    private static final Object sLock = new Object();
-    @GuardedBy("sLock")
-    private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners =
-            new ArrayMap<>();
-    @GuardedBy("sLock")
-    private static Map<String, Pair<ContentObserver, Integer>> sNamespaces = new HashMap<>();
-    private static final String TAG = "DeviceConfig";
-
-    // Should never be invoked
-    private DeviceConfig() {
-    }
-
-    /**
-     * Look up the value of a property for a particular namespace.
-     *
-     * @param namespace The namespace containing the property to look up.
-     * @param name      The name of the property to look up.
-     * @return the corresponding value, or null if not present.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static String getProperty(@NonNull String namespace, @NonNull String name) {
-        // Fetch all properties for the namespace at once and cache them in the local process, so we
-        // incur the cost of the IPC less often. Lookups happen much more frequently than updates,
-        // and we want to optimize the former.
-        return getProperties(namespace, name).getString(name, null);
-    }
-
-    /**
-     * Look up the values of multiple properties for a particular namespace. The lookup is atomic,
-     * such that the values of these properties cannot change between the time when the first is
-     * fetched and the time when the last is fetched.
-     * <p>
-     * Each call to {@link #setProperties(Properties)} is also atomic and ensures that either none
-     * or all of the change is picked up here, but never only part of it.
-     *
-     * @param namespace The namespace containing the properties to look up.
-     * @param names     The names of properties to look up, or empty to fetch all properties for the
-     *                  given namespace.
-     * @return {@link Properties} object containing the requested properties. This reflects the
-     *     state of these properties at the time of the lookup, and is not updated to reflect any
-     *     future changes. The keyset of this Properties object will contain only the intersection
-     *     of properties already set and properties requested via the names parameter. Properties
-     *     that are already set but were not requested will not be contained here. Properties that
-     *     are not set, but were requested will not be contained here either.
-     * @hide
-     */
-    @SystemApi
-    @NonNull
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static Properties getProperties(@NonNull String namespace, @NonNull String ... names) {
-        return new Properties(namespace,
-                Settings.Config.getStrings(namespace, Arrays.asList(names)));
-    }
-
-    /**
-     * Look up the String value of a property for a particular namespace.
-     *
-     * @param namespace    The namespace containing the property to look up.
-     * @param name         The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist or has no non-null
-     *                     value.
-     * @return the corresponding value, or defaultValue if none exists.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static String getString(@NonNull String namespace, @NonNull String name,
-            @Nullable String defaultValue) {
-        String value = getProperty(namespace, name);
-        return value != null ? value : defaultValue;
-    }
-
-    /**
-     * Look up the boolean value of a property for a particular namespace.
-     *
-     * @param namespace The namespace containing the property to look up.
-     * @param name      The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist or has no non-null
-     *                     value.
-     * @return the corresponding value, or defaultValue if none exists.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static boolean getBoolean(@NonNull String namespace, @NonNull String name,
-            boolean defaultValue) {
-        String value = getProperty(namespace, name);
-        return value != null ? Boolean.parseBoolean(value) : defaultValue;
-    }
-
-    /**
-     * Look up the int value of a property for a particular namespace.
-     *
-     * @param namespace The namespace containing the property to look up.
-     * @param name      The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist, has no non-null
-     *                     value, or fails to parse into an int.
-     * @return the corresponding value, or defaultValue if either none exists or it does not parse.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static int getInt(@NonNull String namespace, @NonNull String name, int defaultValue) {
-        String value = getProperty(namespace, name);
-        if (value == null) {
-            return defaultValue;
-        }
-        try {
-            return Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Parsing integer failed for " + namespace + ":" + name);
-            return defaultValue;
-        }
-    }
-
-    /**
-     * Look up the long value of a property for a particular namespace.
-     *
-     * @param namespace The namespace containing the property to look up.
-     * @param name      The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist, has no non-null
-     *                     value, or fails to parse into a long.
-     * @return the corresponding value, or defaultValue if either none exists or it does not parse.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static long getLong(@NonNull String namespace, @NonNull String name, long defaultValue) {
-        String value = getProperty(namespace, name);
-        if (value == null) {
-            return defaultValue;
-        }
-        try {
-            return Long.parseLong(value);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Parsing long failed for " + namespace + ":" + name);
-            return defaultValue;
-        }
-    }
-
-    /**
-     * Look up the float value of a property for a particular namespace.
-     *
-     * @param namespace The namespace containing the property to look up.
-     * @param name      The name of the property to look up.
-     * @param defaultValue The value to return if the property does not exist, has no non-null
-     *                     value, or fails to parse into a float.
-     * @return the corresponding value, or defaultValue if either none exists or it does not parse.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static float getFloat(@NonNull String namespace, @NonNull String name,
-            float defaultValue) {
-        String value = getProperty(namespace, name);
-        if (value == null) {
-            return defaultValue;
-        }
-        try {
-            return Float.parseFloat(value);
-        } catch (NumberFormatException e) {
-            Log.e(TAG, "Parsing float failed for " + namespace + ":" + name);
-            return defaultValue;
-        }
-    }
-
-    /**
-     * Create a new property with the provided name and value in the provided namespace, or
-     * update the value of such a property if it already exists. The same name can exist in multiple
-     * namespaces and might have different values in any or all namespaces.
-     * <p>
-     * The method takes an argument indicating whether to make the value the default for this
-     * property.
-     * <p>
-     * All properties stored for a particular scope can be reverted to their default values
-     * by passing the namespace to {@link #resetToDefaults(int, String)}.
-     *
-     * @param namespace   The namespace containing the property to create or update.
-     * @param name        The name of the property to create or update.
-     * @param value       The value to store for the property.
-     * @param makeDefault Whether to make the new value the default one.
-     * @return {@code true} if the value was set, {@code false} if the storage implementation throws
-     * errors.
-     * @hide
-     * @see #resetToDefaults(int, String).
-     */
-    @SystemApi
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static boolean setProperty(@NonNull String namespace, @NonNull String name,
-            @Nullable String value, boolean makeDefault) {
-        return Settings.Config.putString(namespace, name, value, makeDefault);
-    }
-
-    /**
-     * Set all of the properties for a specific namespace. Pre-existing properties will be updated
-     * and new properties will be added if necessary. Any pre-existing properties for the specific
-     * namespace which are not part of the provided {@link Properties} object will be deleted from
-     * the namespace. These changes are all applied atomically, such that no calls to read or reset
-     * these properties can happen in the middle of this update.
-     * <p>
-     * Each call to {@link #getProperties(String, String...)} is also atomic and ensures that either
-     * none or all of this update is picked up, but never only part of it.
-     *
-     * @param properties the complete set of properties to set for a specific namespace.
-     * @throws BadConfigException if the provided properties are banned by RescueParty.
-     * @return {@code true} if the values were set, {@code false} otherwise.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static boolean setProperties(@NonNull Properties properties) throws BadConfigException {
-        return Settings.Config.setStrings(properties.getNamespace(),
-                properties.mMap);
-    }
-
-    /**
-     * Delete a property with the provided name and value in the provided namespace
-     *
-     * @param namespace   The namespace containing the property to delete.
-     * @param name        The name of the property to delete.
-     * @return {@code true} if the property was deleted or it did not exist in the first place.
-     * Return {@code false} if the storage implementation throws errors.
-     * @hide
-     */
-    @SystemApi
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static boolean deleteProperty(@NonNull String namespace, @NonNull String name) {
-        return Settings.Config.deleteString(namespace, name);
-    }
-
-    /**
-     * Reset properties to their default values by removing the underlying values.
-     * <p>
-     * The method accepts an optional namespace parameter. If provided, only properties set within
-     * that namespace will be reset. Otherwise, all properties will be reset.
-     * <p>
-     * Note: This method should only be used by {@link com.android.server.RescueParty}. It was
-     * designed to be used in the event of boot or crash loops caused by flag changes. It does not
-     * revert flag values to defaults - instead it removes the property entirely which causes the
-     * consumer of the flag to use hardcoded defaults upon retrieval.
-     * <p>
-     * To clear values for a namespace without removing the underlying properties, construct a
-     * {@link Properties} object with the caller's namespace and either an empty flag map, or some
-     * snapshot of flag values. Then use {@link #setProperties(Properties)} to remove all flags
-     * under the namespace, or set them to the values in the snapshot.
-     * <p>
-     * To revert values for testing, one should mock DeviceConfig using
-     * {@link com.android.server.testables.TestableDeviceConfig} where possible. Otherwise, fallback
-     * to using {@link #setProperties(Properties)} as outlined above.
-     *
-     * @param resetMode The reset mode to use.
-     * @param namespace Optionally, the specific namespace which resets will be limited to.
-     * @hide
-     * @see #setProperty(String, String, String, boolean)
-     */
-    @SystemApi
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static void resetToDefaults(@ResetMode int resetMode, @Nullable String namespace) {
-        Settings.Config.resetToDefaults(resetMode, namespace);
-    }
-
-    /**
-     * Disables or re-enables bulk modifications ({@link #setProperties(Properties)}) to device
-     * config values. This is intended for use during tests to prevent a sync operation clearing
-     * config values which could influence the outcome of the tests, i.e. by changing behavior.
-     *
-     * @param syncDisabledMode the mode to use, see {@link Settings.Config#SYNC_DISABLED_MODE_NONE},
-     *     {@link Settings.Config#SYNC_DISABLED_MODE_PERSISTENT} and {@link
-     *     Settings.Config#SYNC_DISABLED_MODE_UNTIL_REBOOT}
-     *
-     * @see #getSyncDisabledMode()
-     * @hide
-     */
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static void setSyncDisabledMode(@SyncDisabledMode int syncDisabledMode) {
-        Settings.Config.setSyncDisabledMode(syncDisabledMode);
-    }
-
-    /**
-     * Returns the current mode of sync disabling.
-     *
-     * @see #setSyncDisabledMode(int)
-     * @hide
-     */
-    @RequiresPermission(WRITE_DEVICE_CONFIG)
-    public static @SyncDisabledMode int getSyncDisabledMode() {
-        return Settings.Config.getSyncDisabledMode();
-    }
-
-    /**
-     * Add a listener for property changes.
-     * <p>
-     * This listener will be called whenever properties in the specified namespace change. Callbacks
-     * will be made on the specified executor. Future calls to this method with the same listener
-     * will replace the old namespace and executor. Remove the listener entirely by calling
-     * {@link #removeOnPropertiesChangedListener(OnPropertiesChangedListener)}.
-     *
-     * @param namespace                   The namespace containing properties to monitor.
-     * @param executor                    The executor which will be used to run callbacks.
-     * @param onPropertiesChangedListener The listener to add.
-     * @hide
-     * @see #removeOnPropertiesChangedListener(OnPropertiesChangedListener)
-     */
-    @SystemApi
-    @RequiresPermission(READ_DEVICE_CONFIG)
-    public static void addOnPropertiesChangedListener(
-            @NonNull String namespace,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
-        enforceReadPermission(namespace);
-        synchronized (sLock) {
-            Pair<String, Executor> oldNamespace = sListeners.get(onPropertiesChangedListener);
-            if (oldNamespace == null) {
-                // Brand new listener, add it to the list.
-                sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
-                incrementNamespace(namespace);
-            } else if (namespace.equals(oldNamespace.first)) {
-                // Listener is already registered for this namespace, update executor just in case.
-                sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
-            } else {
-                // Update this listener from an old namespace to the new one.
-                decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
-                sListeners.put(onPropertiesChangedListener, new Pair<>(namespace, executor));
-                incrementNamespace(namespace);
-            }
-        }
-    }
-
-    /**
-     * Remove a listener for property changes. The listener will receive no further notification of
-     * property changes.
-     *
-     * @param onPropertiesChangedListener The listener to remove.
-     * @hide
-     * @see #addOnPropertiesChangedListener(String, Executor, OnPropertiesChangedListener)
-     */
-    @SystemApi
-    public static void removeOnPropertiesChangedListener(
-            @NonNull OnPropertiesChangedListener onPropertiesChangedListener) {
-        Preconditions.checkNotNull(onPropertiesChangedListener);
-        synchronized (sLock) {
-            if (sListeners.containsKey(onPropertiesChangedListener)) {
-                decrementNamespace(sListeners.get(onPropertiesChangedListener).first);
-                sListeners.remove(onPropertiesChangedListener);
-            }
-        }
-    }
-
-    private static Uri createNamespaceUri(@NonNull String namespace) {
-        Preconditions.checkNotNull(namespace);
-        return CONTENT_URI.buildUpon().appendPath(namespace).build();
-    }
-
-    /**
-     * Increment the count used to represent the number of listeners subscribed to the given
-     * namespace. If this is the first (i.e. incrementing from 0 to 1) for the given namespace, a
-     * ContentObserver is registered.
-     *
-     * @param namespace The namespace to increment the count for.
-     */
-    @GuardedBy("sLock")
-    private static void incrementNamespace(@NonNull String namespace) {
-        Preconditions.checkNotNull(namespace);
-        Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
-        if (namespaceCount != null) {
-            sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second + 1));
-        } else {
-            // This is a new namespace, register a ContentObserver for it.
-            ContentObserver contentObserver = new ContentObserver(null) {
-                @Override
-                public void onChange(boolean selfChange, Uri uri) {
-                    if (uri != null) {
-                        handleChange(uri);
-                    }
-                }
-            };
-            Settings.Config
-                    .registerContentObserver(createNamespaceUri(namespace), true, contentObserver);
-            sNamespaces.put(namespace, new Pair<>(contentObserver, 1));
-        }
-    }
-
-    /**
-     * Decrement the count used to represent the number of listeners subscribed to the given
-     * namespace. If this is the final decrement call (i.e. decrementing from 1 to 0) for the given
-     * namespace, the ContentObserver that had been tracking it will be removed.
-     *
-     * @param namespace The namespace to decrement the count for.
-     */
-    @GuardedBy("sLock")
-    private static void decrementNamespace(@NonNull String namespace) {
-        Preconditions.checkNotNull(namespace);
-        Pair<ContentObserver, Integer> namespaceCount = sNamespaces.get(namespace);
-        if (namespaceCount == null) {
-            // This namespace is not registered and does not need to be decremented
-            return;
-        } else if (namespaceCount.second > 1) {
-            sNamespaces.put(namespace, new Pair<>(namespaceCount.first, namespaceCount.second - 1));
-        } else {
-            // Decrementing a namespace to zero means we no longer need its ContentObserver.
-            Settings.Config.unregisterContentObserver(namespaceCount.first);
-            sNamespaces.remove(namespace);
-        }
-    }
-
-    private static void handleChange(@NonNull Uri uri) {
-        Preconditions.checkNotNull(uri);
-        List<String> pathSegments = uri.getPathSegments();
-        // pathSegments(0) is "config"
-        final String namespace = pathSegments.get(1);
-        Properties.Builder propBuilder = new Properties.Builder(namespace);
-        try {
-            Properties allProperties = getProperties(namespace);
-            for (int i = 2; i < pathSegments.size(); ++i) {
-                String key = pathSegments.get(i);
-                propBuilder.setString(key, allProperties.getString(key, null));
-            }
-        } catch (SecurityException e) {
-            // Silently failing to not crash binder or listener threads.
-            Log.e(TAG, "OnPropertyChangedListener update failed: permission violation.");
-            return;
-        }
-        Properties properties = propBuilder.build();
-
-        synchronized (sLock) {
-            for (int i = 0; i < sListeners.size(); i++) {
-                if (namespace.equals(sListeners.valueAt(i).first)) {
-                    final OnPropertiesChangedListener listener = sListeners.keyAt(i);
-                    sListeners.valueAt(i).second.execute(() -> {
-                        listener.onPropertiesChanged(properties);
-                    });
-                }
-            }
-        }
-    }
-
-    /**
-     * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
-     * @hide
-     */
-    public static void enforceReadPermission(String namespace) {
-        if (Settings.Config.checkCallingOrSelfPermission(READ_DEVICE_CONFIG)
-                != PackageManager.PERMISSION_GRANTED) {
-            if (!PUBLIC_NAMESPACES.contains(namespace)) {
-                throw new SecurityException("Permission denial: reading from settings requires:"
-                        + READ_DEVICE_CONFIG);
-            }
-        }
-    }
-
-    /**
-     * Returns list of namespaces that can be read without READ_DEVICE_CONFIG_PERMISSION;
-     * @hide
-     */
-    public static @NonNull List<String> getPublicNamespaces() {
-        return PUBLIC_NAMESPACES;
-    }
-
-    /**
-     * Interface for monitoring changes to properties. Implementations will receive callbacks when
-     * properties change, including a {@link Properties} object which contains a single namespace
-     * and all of the properties which changed for that namespace. This includes properties which
-     * were added, updated, or deleted. This is not necessarily a complete list of all properties
-     * belonging to the namespace, as properties which don't change are omitted.
-     * <p>
-     * Override {@link #onPropertiesChanged(Properties)} to handle callbacks for changes.
-     *
-     * @hide
-     */
-    @SystemApi
-    public interface OnPropertiesChangedListener {
-        /**
-         * Called when one or more properties have changed, providing a Properties object with all
-         * of the changed properties. This object will contain only properties which have changed,
-         * not the complete set of all properties belonging to the namespace.
-         *
-         * @param properties Contains the complete collection of properties which have changed for a
-         *                   single namespace. This includes only those which were added, updated,
-         *                   or deleted.
-         */
-        void onPropertiesChanged(@NonNull Properties properties);
-    }
-
-    /**
-     * Thrown by {@link #setProperties(Properties)} when a configuration is rejected. This
-     * happens if RescueParty has identified a bad configuration and reset the namespace.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static class BadConfigException extends Exception {}
-
-    /**
-     * A mapping of properties to values, as well as a single namespace which they all belong to.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static class Properties {
-        private final String mNamespace;
-        private final HashMap<String, String> mMap;
-        private Set<String> mKeyset;
-
-        /**
-         * Create a mapping of properties to values and the namespace they belong to.
-         *
-         * @param namespace The namespace these properties belong to.
-         * @param keyValueMap A map between property names and property values.
-         * @hide
-         */
-        public Properties(@NonNull String namespace, @Nullable Map<String, String> keyValueMap) {
-            Preconditions.checkNotNull(namespace);
-            mNamespace = namespace;
-            mMap = new HashMap();
-            if (keyValueMap != null) {
-                mMap.putAll(keyValueMap);
-            }
-        }
-
-        /**
-         * @return the namespace all properties within this instance belong to.
-         */
-        @NonNull
-        public String getNamespace() {
-            return mNamespace;
-        }
-
-        /**
-         * @return the non-null set of property names.
-         */
-        @NonNull
-        public Set<String> getKeyset() {
-            if (mKeyset == null) {
-                mKeyset = Collections.unmodifiableSet(mMap.keySet());
-            }
-            return mKeyset;
-        }
-
-        /**
-         * Look up the String value of a property.
-         *
-         * @param name         The name of the property to look up.
-         * @param defaultValue The value to return if the property has not been defined.
-         * @return the corresponding value, or defaultValue if none exists.
-         */
-        @Nullable
-        public String getString(@NonNull String name, @Nullable String defaultValue) {
-            Preconditions.checkNotNull(name);
-            String value = mMap.get(name);
-            return value != null ? value : defaultValue;
-        }
-
-        /**
-         * Look up the boolean value of a property.
-         *
-         * @param name         The name of the property to look up.
-         * @param defaultValue The value to return if the property has not been defined.
-         * @return the corresponding value, or defaultValue if none exists.
-         */
-        public boolean getBoolean(@NonNull String name, boolean defaultValue) {
-            Preconditions.checkNotNull(name);
-            String value = mMap.get(name);
-            return value != null ? Boolean.parseBoolean(value) : defaultValue;
-        }
-
-        /**
-         * Look up the int value of a property.
-         *
-         * @param name         The name of the property to look up.
-         * @param defaultValue The value to return if the property has not been defined or fails to
-         *                     parse into an int.
-         * @return the corresponding value, or defaultValue if no valid int is available.
-         */
-        public int getInt(@NonNull String name, int defaultValue) {
-            Preconditions.checkNotNull(name);
-            String value = mMap.get(name);
-            if (value == null) {
-                return defaultValue;
-            }
-            try {
-                return Integer.parseInt(value);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Parsing int failed for " + name);
-                return defaultValue;
-            }
-        }
-
-        /**
-         * Look up the long value of a property.
-         *
-         * @param name         The name of the property to look up.
-         * @param defaultValue The value to return if the property has not been defined. or fails to
-         *                     parse into a long.
-         * @return the corresponding value, or defaultValue if no valid long is available.
-         */
-        public long getLong(@NonNull String name, long defaultValue) {
-            Preconditions.checkNotNull(name);
-            String value = mMap.get(name);
-            if (value == null) {
-                return defaultValue;
-            }
-            try {
-                return Long.parseLong(value);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Parsing long failed for " + name);
-                return defaultValue;
-            }
-        }
-
-        /**
-         * Look up the int value of a property.
-         *
-         * @param name         The name of the property to look up.
-         * @param defaultValue The value to return if the property has not been defined. or fails to
-         *                     parse into a float.
-         * @return the corresponding value, or defaultValue if no valid float is available.
-         */
-        public float getFloat(@NonNull String name, float defaultValue) {
-            Preconditions.checkNotNull(name);
-            String value = mMap.get(name);
-            if (value == null) {
-                return defaultValue;
-            }
-            try {
-                return Float.parseFloat(value);
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "Parsing float failed for " + name);
-                return defaultValue;
-            }
-        }
-
-        /**
-         * Builder class for the construction of {@link Properties} objects.
-         */
-        public static final class Builder {
-            @NonNull
-            private final String mNamespace;
-            @NonNull
-            private final Map<String, String> mKeyValues = new HashMap<>();
-
-            /**
-             * Create a new Builders for the specified namespace.
-             * @param namespace non null namespace.
-             */
-            public Builder(@NonNull String namespace) {
-                mNamespace = namespace;
-            }
-
-            /**
-             * Add a new property with the specified key and value.
-             * @param name non null name of the property.
-             * @param value nullable string value of the property.
-             * @return this Builder object
-             */
-            @NonNull
-            public Builder setString(@NonNull String name, @Nullable String value) {
-                mKeyValues.put(name, value);
-                return this;
-            }
-
-            /**
-             * Add a new property with the specified key and value.
-             * @param name non null name of the property.
-             * @param value nullable string value of the property.
-             * @return this Builder object
-             */
-            @NonNull
-            public Builder setBoolean(@NonNull String name, boolean value) {
-                mKeyValues.put(name, Boolean.toString(value));
-                return this;
-            }
-
-            /**
-             * Add a new property with the specified key and value.
-             * @param name non null name of the property.
-             * @param value int value of the property.
-             * @return this Builder object
-             */
-            @NonNull
-            public Builder setInt(@NonNull String name, int value) {
-                mKeyValues.put(name, Integer.toString(value));
-                return this;
-            }
-
-            /**
-             * Add a new property with the specified key and value.
-             * @param name non null name of the property.
-             * @param value long value of the property.
-             * @return this Builder object
-             */
-            @NonNull
-            public Builder setLong(@NonNull String name, long value) {
-                mKeyValues.put(name, Long.toString(value));
-                return this;
-            }
-
-            /**
-             * Add a new property with the specified key and value.
-             * @param name non null name of the property.
-             * @param value float value of the property.
-             * @return this Builder object
-             */
-            @NonNull
-            public Builder setFloat(@NonNull String name, float value) {
-                mKeyValues.put(name, Float.toString(value));
-                return this;
-            }
-
-            /**
-             * Create a new {@link Properties} object.
-             * @return non null Properties.
-             */
-            @NonNull
-            public Properties build() {
-                return new Properties(mNamespace, mKeyValues);
-            }
-        }
-    }
-
-}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f12bff2..e90ba6e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -2819,6 +2819,7 @@
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     @TestApi
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_PACKAGE_DEFAULTS = 1;
 
     /**
@@ -2828,6 +2829,7 @@
      * the setting will be deleted. This mode is only available to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_UNTRUSTED_DEFAULTS = 2;
 
     /**
@@ -2838,6 +2840,7 @@
      * This mode is only available to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_UNTRUSTED_CHANGES = 3;
 
     /**
@@ -2849,6 +2852,7 @@
      * to the system.
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final int RESET_MODE_TRUSTED_DEFAULTS = 4;
 
     /** @hide */
@@ -3362,7 +3366,7 @@
         public ArrayMap<String, String> getStringsForPrefix(ContentResolver cr, String prefix,
                 List<String> names) {
             String namespace = prefix.substring(0, prefix.length() - 1);
-            DeviceConfig.enforceReadPermission(namespace);
+            Config.enforceReadPermission(namespace);
             ArrayMap<String, String> keyValues = new ArrayMap<>();
             int currentGeneration = -1;
 
@@ -4634,21 +4638,6 @@
         public static final String SCREEN_BRIGHTNESS = "screen_brightness";
 
         /**
-         * The screen backlight brightness between 0 and 255.
-         * @hide
-         */
-        @Readable
-        public static final String SCREEN_BRIGHTNESS_FOR_VR = "screen_brightness_for_vr";
-
-        /**
-         * The screen backlight brightness between 0.0f and 1.0f.
-         * @hide
-         */
-        @Readable
-        public static final String SCREEN_BRIGHTNESS_FOR_VR_FLOAT =
-                "screen_brightness_for_vr_float";
-
-        /**
          * The screen backlight brightness between 0.0f and 1.0f.
          * @hide
          */
@@ -5633,8 +5622,6 @@
             PUBLIC_SETTINGS.add(SCREEN_OFF_TIMEOUT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FLOAT);
-            PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR);
-            PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_FOR_VR_FLOAT);
             PUBLIC_SETTINGS.add(SCREEN_BRIGHTNESS_MODE);
             PUBLIC_SETTINGS.add(MODE_RINGER_STREAMS_AFFECTED);
             PUBLIC_SETTINGS.add(MUTE_STREAMS_AFFECTED);
@@ -9366,6 +9353,14 @@
         public static final int DOCK_SETUP_PAUSED = 2;
 
         /**
+         * Indicates that the user has been prompted to start dock setup.
+         * One of the possible states for {@link #DOCK_SETUP_STATE}.
+         *
+         * @hide
+         */
+        public static final int DOCK_SETUP_PROMPTED = 3;
+
+        /**
          * Indicates that the user has completed dock setup.
          * One of the possible states for {@link #DOCK_SETUP_STATE}.
          *
@@ -9379,6 +9374,7 @@
                 DOCK_SETUP_NOT_STARTED,
                 DOCK_SETUP_STARTED,
                 DOCK_SETUP_PAUSED,
+                DOCK_SETUP_PROMPTED,
                 DOCK_SETUP_COMPLETED
         })
         public @interface DockSetupState {
@@ -18001,6 +17997,7 @@
      *
      * @hide
      */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public static final class Config extends NameValueTable {
 
         /**
@@ -18035,12 +18032,19 @@
          */
         public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2;
 
+        /**
+         * The content:// style URL for the config table.
+         *
+         * @hide
+         */
+        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/config");
+
         private static final ContentProviderHolder sProviderHolder =
-                new ContentProviderHolder(DeviceConfig.CONTENT_URI);
+                new ContentProviderHolder(CONTENT_URI);
 
         // Populated lazily, guarded by class object:
         private static final NameValueCache sNameValueCache = new NameValueCache(
-                DeviceConfig.CONTENT_URI,
+                CONTENT_URI,
                 CALL_METHOD_GET_CONFIG,
                 CALL_METHOD_PUT_CONFIG,
                 CALL_METHOD_DELETE_CONFIG,
@@ -18049,6 +18053,10 @@
                 sProviderHolder,
                 Config.class);
 
+        // Should never be invoked
+        private Config() {
+        }
+
         /**
          * Look up a name in the database.
          * @param name to look up in the table
@@ -18056,8 +18064,10 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @Nullable
         @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
-        static String getString(String name) {
+        public static String getString(@NonNull String name) {
             ContentResolver resolver = getContentResolver();
             return sNameValueCache.getStringForUser(resolver, name, resolver.getUserId());
         }
@@ -18072,6 +18082,8 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @NonNull
         @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
         public static Map<String, String> getStrings(@NonNull String namespace,
                 @NonNull List<String> names) {
@@ -18128,6 +18140,7 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
         public static boolean putString(@NonNull String namespace,
                 @NonNull String name, @Nullable String value, boolean makeDefault) {
@@ -18147,6 +18160,7 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
         public static boolean setStrings(@NonNull String namespace,
                 @NonNull Map<String, String> keyValues)
@@ -18197,8 +18211,9 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static boolean deleteString(@NonNull String namespace,
+        public static boolean deleteString(@NonNull String namespace,
                 @NonNull String name) {
             ContentResolver resolver = getContentResolver();
             return sNameValueCache.deleteStringForUser(resolver,
@@ -18218,8 +18233,9 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static void resetToDefaults(@ResetMode int resetMode,
+        public static void resetToDefaults(@ResetMode int resetMode,
                 @Nullable String namespace) {
             try {
                 ContentResolver resolver = getContentResolver();
@@ -18233,7 +18249,7 @@
                 cp.call(resolver.getAttributionSource(),
                         sProviderHolder.mUri.getAuthority(), CALL_METHOD_RESET_CONFIG, null, arg);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't reset to defaults for " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't reset to defaults for " + CONTENT_URI, e);
             }
         }
 
@@ -18243,9 +18259,10 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
+        public static void setSyncDisabledMode(@SyncDisabledMode int disableSyncMode) {
             try {
                 ContentResolver resolver = getContentResolver();
                 Bundle args = new Bundle();
@@ -18254,7 +18271,7 @@
                 cp.call(resolver.getAttributionSource(), sProviderHolder.mUri.getAuthority(),
                         CALL_METHOD_SET_SYNC_DISABLED_MODE_CONFIG, null, args);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't set sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't set sync disabled mode " + CONTENT_URI, e);
             }
         }
 
@@ -18264,9 +18281,10 @@
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @SuppressLint("AndroidFrameworkRequiresPermission")
         @RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
-        static int getSyncDisabledMode() {
+        public static int getSyncDisabledMode() {
             try {
                 ContentResolver resolver = getContentResolver();
                 Bundle args = Bundle.EMPTY;
@@ -18277,7 +18295,7 @@
                         null, args);
                 return bundle.getInt(KEY_CONFIG_GET_SYNC_DISABLED_MODE_RETURN);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't query sync disabled mode " + DeviceConfig.CONTENT_URI, e);
+                Log.w(TAG, "Can't query sync disabled mode " + CONTENT_URI, e);
             }
             return -1;
         }
@@ -18297,21 +18315,26 @@
 
 
         /**
-         * Register a content observer
+         * Register a content observer.
          *
          * @hide
          */
-        public static void registerContentObserver(@NonNull Uri uri, boolean notifyForDescendants,
-                @NonNull ContentObserver observer) {
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        public static void registerContentObserver(@Nullable String namespace,
+                boolean notifyForDescendants, @NonNull ContentObserver observer) {
             ActivityThread.currentApplication().getContentResolver()
-               .registerContentObserver(uri, notifyForDescendants, observer);
+                    .registerContentObserver(createNamespaceUri(namespace),
+                         notifyForDescendants, observer);
         }
 
         /**
-         * Unregister a content observer
+         * Unregister a content observer.
+         * this may only be used with content observers registered through
+         * {@link Config#registerContentObserver}
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         public static void unregisterContentObserver(@NonNull ContentObserver observer) {
             ActivityThread.currentApplication().getContentResolver()
               .unregisterContentObserver(observer);
@@ -18339,6 +18362,21 @@
                .getApplicationContext().checkCallingOrSelfPermission(permission);
         }
 
+        /**
+         * Enforces READ_DEVICE_CONFIG permission if namespace is not one of public namespaces.
+         * @hide
+         */
+        public static void enforceReadPermission(String namespace) {
+            if (ActivityThread.currentApplication().getApplicationContext()
+                    .checkCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG)
+                    != PackageManager.PERMISSION_GRANTED) {
+                if (!DeviceConfig.getPublicNamespaces().contains(namespace)) {
+                    throw new SecurityException("Permission denial: reading from settings requires:"
+                        + Manifest.permission.READ_DEVICE_CONFIG);
+                }
+            }
+        }
+
         private static void registerMonitorCallbackAsUser(
                 @NonNull ContentResolver resolver, @UserIdInt int userHandle,
                 @NonNull RemoteCallback callback) {
@@ -18372,6 +18410,11 @@
             return namespace + "/";
         }
 
+        private static Uri createNamespaceUri(@NonNull String namespace) {
+            Preconditions.checkNotNull(namespace);
+            return CONTENT_URI.buildUpon().appendPath(namespace).build();
+        }
+
         private static ContentResolver getContentResolver() {
             return ActivityThread.currentApplication().getContentResolver();
         }
diff --git a/core/java/android/security/rkp/IGetKeyCallback.aidl b/core/java/android/security/rkp/IGetKeyCallback.aidl
new file mode 100644
index 0000000..85ceae62
--- /dev/null
+++ b/core/java/android/security/rkp/IGetKeyCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.security.rkp;
+
+import android.security.rkp.RemotelyProvisionedKey;
+
+/**
+ * Callback interface for receiving remotely provisioned keys from a
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetKeyCallback {
+    /**
+     * Called in response to {@link IRegistration.getKey}, indicating
+     * a remotely-provisioned key is available.
+     *
+     * @param key The key that was received from the remote provisioning service.
+     */
+    void onSuccess(in RemotelyProvisionedKey key);
+
+    /**
+     * Called when the key request has been successfully cancelled.
+     * @see IRegistration.cancelGetKey
+     */
+    void onCancel();
+
+    /**
+     * Called when an error has occurred while trying to get a remotely provisioned key.
+     *
+     * @param error A description of what failed, suitable for logging.
+     */
+    void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IGetRegistrationCallback.aidl b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
new file mode 100644
index 0000000..e375a6f
--- /dev/null
+++ b/core/java/android/security/rkp/IGetRegistrationCallback.aidl
@@ -0,0 +1,49 @@
+/*
+ * 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.security.rkp;
+
+import android.security.rkp.IRegistration;
+
+/**
+ * Callback interface for receiving a remote provisioning registration.
+ * {@link IRegistration}.
+ *
+ * @hide
+ */
+oneway interface IGetRegistrationCallback {
+    /**
+     * Called in response to {@link IRemoteProvisioning.getRegistration}.
+     *
+     * @param registration an IRegistration that is used to fetch remotely
+     * provisioned keys for the given IRemotelyProvisionedComponent.
+     */
+    void onSuccess(in IRegistration registration);
+
+    /**
+     * Called when the get registration request has been successfully cancelled.
+     * @see IRemoteProvisioning.cancelGetRegistration
+     */
+    void onCancel();
+
+    /**
+     * Called when an error has occurred while trying to get a registration.
+     *
+     * @param error A description of what failed, suitable for logging.
+     */
+    void onError(String error);
+}
+
diff --git a/core/java/android/security/rkp/IRegistration.aidl b/core/java/android/security/rkp/IRegistration.aidl
new file mode 100644
index 0000000..6522a45
--- /dev/null
+++ b/core/java/android/security/rkp/IRegistration.aidl
@@ -0,0 +1,85 @@
+/*
+ * 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.security.rkp;
+
+import android.security.rkp.IGetKeyCallback;
+
+/**
+ * This interface is associated with the registration of an
+ * IRemotelyProvisionedComponent. Each component has a unique set of keys
+ * and certificates that are provisioned to the device for attestation. An
+ * IRegistration binder is created by calling
+ * {@link IRemoteProvisioning#getRegistration()}.
+ *
+ * This interface is used to query for available keys and certificates for the
+ * registered component.
+ *
+ * @hide
+ */
+oneway interface IRegistration {
+    /**
+     * Fetch a remotely provisioned key for the given keyId. Keys are unique
+     * per caller/keyId/registration tuple. This ensures that no two
+     * applications are able to correlate keys to uniquely identify a
+     * device/user. Callers receive their key via {@code callback}.
+     *
+     * If a key is available, this call immediately invokes {@code callback}.
+     *
+     * If no keys are immediately available, then this function contacts the
+     * remote provisioning server to provision a key. After provisioning is
+     * completed, the key is passed to {@code callback}.
+     *
+     * @param keyId This is a client-chosen key identifier, used to
+     * differentiate between keys for varying client-specific use-cases. For
+     * example, keystore2 passes the UID of the applications that call it as
+     * the keyId value here, so that each of keystore2's clients gets a unique
+     * key.
+     * @param callback Receives the result of the call. A callback must only
+     * be used with one {@code getKey} call at a time.
+     */
+    void getKey(int keyId, IGetKeyCallback callback);
+
+    /**
+     * Cancel an active request for a remotely provisioned key, as initiated via
+     * {@link getKey}. Upon cancellation, {@code callback.onCancel} will be invoked.
+     */
+    void cancelGetKey(IGetKeyCallback callback);
+
+    /**
+     * Replace an obsolete key blob with an upgraded key blob.
+     * In certain cases, such as security patch level upgrade, keys become "old".
+     * In these cases, the component which supports operations with the remotely
+     * provisioned key blobs must support upgrading the blobs to make them "new"
+     * and usable on the updated system.
+     *
+     * For an example of a remotely provisioned component that has an upgrade
+     * mechanism, see the documentation for IKeyMintDevice.upgradeKey.
+     *
+     * Once a key has been upgraded, the IRegistration where the key is stored
+     * needs to be told about the new blob. After calling storeUpgradedKey,
+     * getKey will return the new key blob instead of the old one.
+     *
+     * Note that this function does NOT extend the lifetime of key blobs. The
+     * certificate for the key is unchanged, and the key will still expire at
+     * the same time it would have if storeUpgradedKey had never been called.
+     *
+     * @param oldKeyBlob The old key blob to be replaced by {@code newKeyBlob}.
+     *
+     * @param newKeyblob The new blob to replace {@code oldKeyBlob}.
+     */
+    void storeUpgradedKey(in byte[] oldKeyBlob, in byte[] newKeyBlob);
+}
diff --git a/core/java/android/security/rkp/IRemoteProvisioning.aidl b/core/java/android/security/rkp/IRemoteProvisioning.aidl
new file mode 100644
index 0000000..23d8159
--- /dev/null
+++ b/core/java/android/security/rkp/IRemoteProvisioning.aidl
@@ -0,0 +1,68 @@
+/*
+ * 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.security.rkp;
+
+import android.security.rkp.IRegistration;
+import android.security.rkp.IGetRegistrationCallback;
+
+/**
+ * {@link IRemoteProvisioning} is the interface provided to use the remote key
+ * provisioning functionality from the Remote Key Provisioning Daemon (RKPD).
+ * This would be the first service that RKPD clients would interact with. The
+ * intent is for the clients to get the {@link IRegistration} object from this
+ * interface and use it for actual remote provisioning work.
+ *
+ * @hide
+ */
+oneway interface IRemoteProvisioning {
+    /**
+     * Takes a remotely provisioned component service name and gets a
+     * registration bound to that service and the caller's UID.
+     *
+     * @param irpcName The name of the {@code IRemotelyProvisionedComponent}
+     * for which remotely provisioned keys should be managed.
+     * @param callback Receives the result of the call. A callback must only
+     * be used with one {@code getRegistration} call at a time.
+     *
+     * Notes:
+     * - This function will attempt to get the service named by irpcName. This
+     *   implies that a lazy/dynamic aidl service will be instantiated, and this
+     *   function blocks until the service is up. Upon return, any binder tokens
+     *   are dropped, allowing the lazy/dynamic service to shutdown.
+     * - The created registration object is unique per caller. If two different
+     *   UIDs call getRegistration with the same irpcName, they will receive
+     *   different registrations. This prevents two different applications from
+     *   being able to see the same keys.
+     * - This function is idempotent per calling UID. Additional calls to
+     *   getRegistration with the same parameters, from the same caller, will have
+     *   no side effects.
+     * - A callback may only be associated with one getRegistration call at a time.
+     *   If the callback is used multiple times, this API will return an error.
+     *
+     * @see IRegistration#getKey()
+     * @see IRemotelyProvisionedComponent
+     *
+     */
+    void getRegistration(String irpcName, IGetRegistrationCallback callback);
+
+    /**
+     * Cancel any active {@link getRegistration} call associated with the given
+     * callback. If no getRegistration call is currently active, this function is
+     * a noop.
+     */
+    void cancelGetRegistration(IGetRegistrationCallback callback);
+}
diff --git a/core/java/android/security/rkp/OWNERS b/core/java/android/security/rkp/OWNERS
new file mode 100644
index 0000000..fd43089
--- /dev/null
+++ b/core/java/android/security/rkp/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 1084908
+
+jbires@google.com
+sethmo@google.com
+vikramgaur@google.com
diff --git a/core/java/android/security/rkp/RemotelyProvisionedKey.aidl b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
new file mode 100644
index 0000000..207f18f
--- /dev/null
+++ b/core/java/android/security/rkp/RemotelyProvisionedKey.aidl
@@ -0,0 +1,44 @@
+/*
+ * 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.security.rkp;
+
+/**
+ * A {@link RemotelyProvisionedKey} holds an attestation key and the
+ * corresponding remotely provisioned certificate chain.
+ *
+ * @hide
+ */
+@RustDerive(Eq=true, PartialEq=true)
+parcelable RemotelyProvisionedKey {
+    /**
+     * The remotely-provisioned key that may be used to sign attestations. The
+     * format of this key is opaque, and need only be understood by the
+     * IRemotelyProvisionedComponent that generated it.
+     *
+     * Any private key material contained within this blob must be encrypted.
+     *
+     * @see IRemotelyProvisionedComponent
+     */
+    byte[] keyBlob;
+
+    /**
+     * Sequence of DER-encoded X.509 certificates that make up the attestation
+     * key's certificate chain. This is the binary encoding for a chain that is
+     * supported by Java's CertificateFactory.generateCertificates API.
+     */
+    byte[] encodedCertChain;
+}
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index d2a4ae2..9396a88 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -69,6 +69,18 @@
             "android.service.controls.META_DATA_PANEL_ACTIVITY";
 
     /**
+     * Boolean extra containing the value of
+     * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+     *
+     * This is passed with the intent when the panel specified by {@link #META_DATA_PANEL_ACTIVITY}
+     * is launched.
+     *
+     * @hide
+     */
+    public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS =
+            "android.service.controls.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+
+    /**
      * @hide
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
index 7757081..42dd528 100644
--- a/core/java/android/service/credentials/Action.java
+++ b/core/java/android/service/credentials/Action.java
@@ -42,7 +42,7 @@
      * level authentication before displaying any content etc.
      *
      * <p> See details on usage of {@code Action} for various actionable entries in
-     * {@link BeginCreateCredentialResponse} and {@link GetCredentialsResponse}.
+     * {@link BeginCreateCredentialResponse} and {@link BeginGetCredentialsResponse}.
      *
      * @param slice the display content to be displayed on the UI, along with this action
      * @param pendingIntent the intent to be invoked when the user selects this action
diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
index 022678e..8ca3a1a 100644
--- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java
+++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java
@@ -127,7 +127,7 @@
          *
          * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
          * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
-         * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESULT} key should be populated
+         * {@link CredentialProviderService#EXTRA_CREATE_CREDENTIAL_RESPONSE} key should be populated
          * with a {@link android.credentials.CreateCredentialResponse} object.
          */
         public @NonNull Builder setRemoteCreateEntry(@Nullable CreateEntry remoteCreateEntry) {
diff --git a/core/java/android/service/credentials/BeginGetCredentialOption.java b/core/java/android/service/credentials/BeginGetCredentialOption.java
new file mode 100644
index 0000000..c82b445
--- /dev/null
+++ b/core/java/android/service/credentials/BeginGetCredentialOption.java
@@ -0,0 +1,129 @@
+/*
+ * 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.service.credentials;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+/**
+ * A specific type of credential request to be sent to the provider during the query phase of
+ * a get flow. This request contains limited parameters needed to populate a list of
+ * {@link CredentialEntry} on the {@link BeginGetCredentialsResponse}.
+ */
+public final class BeginGetCredentialOption implements Parcelable {
+
+    /**
+     * The requested credential type.
+     */
+    @NonNull
+    private final String mType;
+
+    /**
+     * The request candidateQueryData.
+     */
+    @NonNull
+    private final Bundle mCandidateQueryData;
+
+    /**
+     * Returns the requested credential type.
+     */
+    @NonNull
+    public String getType() {
+        return mType;
+    }
+
+    /**
+     * Returns the request candidate query data, denoting a set of parameters
+     * that can be used to populate a candidate list of credentials, as
+     * {@link CredentialEntry} on {@link BeginGetCredentialsResponse}. This list
+     * of entries is then presented to the user on a selector.
+     *
+     * <p>This data does not contain any sensitive parameters, and will be sent
+     * to all eligible providers.
+     * The complete set of parameters will only be set on the {@link android.app.PendingIntent}
+     * set on the {@link CredentialEntry} that is selected by the user.
+     */
+    @NonNull
+    public Bundle getCandidateQueryData() {
+        return mCandidateQueryData;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mType);
+        dest.writeBundle(mCandidateQueryData);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public String toString() {
+        return "GetCredentialOption {"
+                + "type=" + mType
+                + ", candidateQueryData=" + mCandidateQueryData
+                + "}";
+    }
+
+    /**
+     * Constructs a {@link BeginGetCredentialOption}.
+     *
+     * @param type the requested credential type
+     * @param candidateQueryData the request candidateQueryData
+     *
+     * @throws IllegalArgumentException If type is empty.
+     */
+    public BeginGetCredentialOption(
+            @NonNull String type,
+            @NonNull Bundle candidateQueryData) {
+        mType = Preconditions.checkStringNotEmpty(type, "type must not be empty");
+        mCandidateQueryData = requireNonNull(
+                candidateQueryData, "candidateQueryData must not be null");
+    }
+
+    private BeginGetCredentialOption(@NonNull Parcel in) {
+        String type = in.readString8();
+        Bundle candidateQueryData = in.readBundle();
+
+        mType = type;
+        AnnotationValidations.validate(NonNull.class, null, mType);
+        mCandidateQueryData = candidateQueryData;
+        AnnotationValidations.validate(NonNull.class, null, mCandidateQueryData);
+    }
+
+    public static final @NonNull Creator<BeginGetCredentialOption> CREATOR =
+            new Creator<BeginGetCredentialOption>() {
+                @Override
+                public BeginGetCredentialOption[] newArray(int size) {
+                    return new BeginGetCredentialOption[size];
+                }
+
+                @Override
+                public BeginGetCredentialOption createFromParcel(@NonNull Parcel in) {
+                    return new BeginGetCredentialOption(in);
+                }
+            };
+}
diff --git a/core/java/android/service/credentials/BeginGetCredentialsRequest.aidl b/core/java/android/service/credentials/BeginGetCredentialsRequest.aidl
new file mode 100644
index 0000000..5e1fe8ab
--- /dev/null
+++ b/core/java/android/service/credentials/BeginGetCredentialsRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginGetCredentialsRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/BeginGetCredentialsRequest.java b/core/java/android/service/credentials/BeginGetCredentialsRequest.java
new file mode 100644
index 0000000..795840b
--- /dev/null
+++ b/core/java/android/service/credentials/BeginGetCredentialsRequest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.service.credentials;
+
+import android.annotation.NonNull;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.credentials.GetCredentialOption;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Query stage request for getting user's credentials from a given credential provider.
+ *
+ * <p>This request contains a list of {@link GetCredentialOption} that have parameters
+ * to be used to query credentials, and return a list of {@link CredentialEntry} to be set
+ * on the {@link BeginGetCredentialsResponse}. This list is then shown to the user on a selector.
+ *
+ * If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that
+ * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual
+ * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired
+ * through the {@link PendingIntent}.
+ */
+public final class BeginGetCredentialsRequest implements Parcelable {
+    /** Calling package of the app requesting for credentials. */
+    @NonNull private final String mCallingPackage;
+
+    /**
+     * List of credential options. Each {@link BeginGetCredentialOption} object holds parameters to
+     * be used for populating a list of {@link CredentialEntry} for a specific type of credential.
+     *
+     * This request does not reveal sensitive parameters. Complete list of parameters
+     * is retrieved through the {@link PendingIntent} set on each {@link CredentialEntry}
+     * on {@link CredentialsResponseContent} set on {@link BeginGetCredentialsResponse},
+     * when the user selects one of these entries.
+     */
+    @NonNull private final List<BeginGetCredentialOption> mBeginGetCredentialOptions;
+
+    private BeginGetCredentialsRequest(@NonNull String callingPackage,
+            @NonNull List<BeginGetCredentialOption> getBeginCredentialOptions) {
+        this.mCallingPackage = callingPackage;
+        this.mBeginGetCredentialOptions = getBeginCredentialOptions;
+    }
+
+    private BeginGetCredentialsRequest(@NonNull Parcel in) {
+        mCallingPackage = in.readString8();
+        List<BeginGetCredentialOption> getBeginCredentialOptions = new ArrayList<>();
+        in.readTypedList(getBeginCredentialOptions, BeginGetCredentialOption.CREATOR);
+        mBeginGetCredentialOptions = getBeginCredentialOptions;
+        AnnotationValidations.validate(NonNull.class, null, mBeginGetCredentialOptions);
+    }
+
+    public static final @NonNull Creator<BeginGetCredentialsRequest> CREATOR =
+            new Creator<BeginGetCredentialsRequest>() {
+                @Override
+                public BeginGetCredentialsRequest createFromParcel(Parcel in) {
+                    return new BeginGetCredentialsRequest(in);
+                }
+
+                @Override
+                public BeginGetCredentialsRequest[] newArray(int size) {
+                    return new BeginGetCredentialsRequest[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mCallingPackage);
+        dest.writeTypedList(mBeginGetCredentialOptions);
+    }
+
+    /**
+     * Returns the calling package of the app requesting credentials.
+     */
+    public @NonNull String getCallingPackage() {
+        return mCallingPackage;
+    }
+
+    /**
+     * Returns the list of type specific credential options to list credentials for in
+     * {@link BeginGetCredentialsResponse}.
+     */
+    public @NonNull List<BeginGetCredentialOption> getBeginGetCredentialOptions() {
+        return mBeginGetCredentialOptions;
+    }
+
+    /**
+     * Builder for {@link BeginGetCredentialsRequest}.
+     */
+    public static final class Builder {
+        private String mCallingPackage;
+        private List<BeginGetCredentialOption> mBeginGetCredentialOptions = new ArrayList<>();
+
+        /**
+         * Creates a new builder.
+         * @param callingPackage the calling package of the app requesting credentials
+         *
+         * @throws IllegalArgumentException If {@code callingPackage} is null or empty.
+         */
+        public Builder(@NonNull String callingPackage) {
+            mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage);
+        }
+
+        /**
+         * Sets the list of credential options.
+         *
+         * @throws NullPointerException If {@code getBeginCredentialOptions} itself or any of its
+         * elements is null.
+         * @throws IllegalArgumentException If {@code getBeginCredentialOptions} is empty.
+         */
+        public @NonNull Builder setBeginGetCredentialOptions(
+                @NonNull List<BeginGetCredentialOption> getBeginCredentialOptions) {
+            Preconditions.checkCollectionNotEmpty(getBeginCredentialOptions,
+                    "getBeginCredentialOptions");
+            Preconditions.checkCollectionElementsNotNull(getBeginCredentialOptions,
+                    "getBeginCredentialOptions");
+            mBeginGetCredentialOptions = getBeginCredentialOptions;
+            return this;
+        }
+
+        /**
+         * Adds a single {@link BeginGetCredentialOption} object to the list of credential options.
+         *
+         * @throws NullPointerException If {@code beginGetCredentialOption} is null.
+         */
+        public @NonNull Builder addBeginGetCredentialOption(
+                @NonNull BeginGetCredentialOption beginGetCredentialOption) {
+            Objects.requireNonNull(beginGetCredentialOption,
+                    "beginGetCredentialOption must not be null");
+            mBeginGetCredentialOptions.add(beginGetCredentialOption);
+            return this;
+        }
+
+        /**
+         * Builds a new {@link BeginGetCredentialsRequest} instance.
+         *
+         * @throws NullPointerException If {@code beginGetCredentialOptions} is null.
+         * @throws IllegalArgumentException If {@code beginGetCredentialOptions} is empty, or if
+         * {@code callingPackage} is null or empty.
+         */
+        public @NonNull BeginGetCredentialsRequest build() {
+            Preconditions.checkStringNotEmpty(mCallingPackage,
+                    "Must set the calling package");
+            Preconditions.checkCollectionNotEmpty(mBeginGetCredentialOptions,
+                    "beginGetCredentialOptions");
+            return new BeginGetCredentialsRequest(mCallingPackage, mBeginGetCredentialOptions);
+        }
+    }
+}
diff --git a/core/java/android/service/credentials/BeginGetCredentialsResponse.aidl b/core/java/android/service/credentials/BeginGetCredentialsResponse.aidl
new file mode 100644
index 0000000..ca69bca
--- /dev/null
+++ b/core/java/android/service/credentials/BeginGetCredentialsResponse.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable BeginGetCredentialsResponse;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/BeginGetCredentialsResponse.java
similarity index 74%
rename from core/java/android/service/credentials/GetCredentialsResponse.java
rename to core/java/android/service/credentials/BeginGetCredentialsResponse.java
index 5263141..2cda560 100644
--- a/core/java/android/service/credentials/GetCredentialsResponse.java
+++ b/core/java/android/service/credentials/BeginGetCredentialsResponse.java
@@ -27,7 +27,7 @@
  * Response from a credential provider, containing credential entries and other associated
  * data to be shown on the account selector UI.
  */
-public final class GetCredentialsResponse implements Parcelable {
+public final class BeginGetCredentialsResponse implements Parcelable {
     /** Content to be used for the UI. */
     private final @Nullable CredentialsResponseContent mCredentialsResponseContent;
 
@@ -38,14 +38,15 @@
     private final @Nullable Action mAuthenticationAction;
 
     /**
-     * Creates a {@link GetCredentialsResponse} instance with an authentication {@link Action} set.
-     * Providers must use this method when no content can be shown before authentication.
+     * Creates a {@link BeginGetCredentialsResponse} instance with an authentication
+     * {@link Action} set. Providers must use this method when no content can be shown
+     * before authentication.
      *
      * <p> When the user selects this {@code authenticationAction}, the system invokes the
      * corresponding {@code pendingIntent}. Once the authentication flow is complete,
      * the {@link android.app.Activity} result should be set
      * to {@link android.app.Activity#RESULT_OK} and the
-     * {@link CredentialProviderService#EXTRA_GET_CREDENTIALS_CONTENT_RESULT} extra should be set
+     * {@link CredentialProviderService#EXTRA_CREDENTIALS_RESPONSE_CONTENT} extra should be set
      * with a fully populated {@link CredentialsResponseContent} object.
      * the authentication action activity is launched, and the user is authenticated, providers
      * should create another response with {@link CredentialsResponseContent} using
@@ -54,48 +55,49 @@
      *
      * @throws NullPointerException If {@code authenticationAction} is null.
      */
-    public static @NonNull GetCredentialsResponse createWithAuthentication(
+    public static @NonNull BeginGetCredentialsResponse createWithAuthentication(
             @NonNull Action authenticationAction) {
         Objects.requireNonNull(authenticationAction,
                 "authenticationAction must not be null");
-        return new GetCredentialsResponse(null, authenticationAction);
+        return new BeginGetCredentialsResponse(null, authenticationAction);
     }
 
     /**
-     * Creates a {@link GetCredentialsRequest} instance with content to be shown on the UI.
+     * Creates a {@link BeginGetCredentialsRequest} instance with content to be shown on the UI.
      * Providers must use this method when there is content to be shown without top level
      * authentication required, including credential entries, action entries or a remote entry,
      *
      * @throws NullPointerException If {@code credentialsResponseContent} is null.
      */
-    public static @NonNull GetCredentialsResponse createWithResponseContent(
+    public static @NonNull BeginGetCredentialsResponse createWithResponseContent(
             @NonNull CredentialsResponseContent credentialsResponseContent) {
         Objects.requireNonNull(credentialsResponseContent,
                 "credentialsResponseContent must not be null");
-        return new GetCredentialsResponse(credentialsResponseContent, null);
+        return new BeginGetCredentialsResponse(credentialsResponseContent, null);
     }
 
-    private GetCredentialsResponse(@Nullable CredentialsResponseContent credentialsResponseContent,
+    private BeginGetCredentialsResponse(@Nullable CredentialsResponseContent
+            credentialsResponseContent,
             @Nullable Action authenticationAction) {
         mCredentialsResponseContent = credentialsResponseContent;
         mAuthenticationAction = authenticationAction;
     }
 
-    private GetCredentialsResponse(@NonNull Parcel in) {
+    private BeginGetCredentialsResponse(@NonNull Parcel in) {
         mCredentialsResponseContent = in.readTypedObject(CredentialsResponseContent.CREATOR);
         mAuthenticationAction = in.readTypedObject(Action.CREATOR);
     }
 
-    public static final @NonNull Creator<GetCredentialsResponse> CREATOR =
-            new Creator<GetCredentialsResponse>() {
+    public static final @NonNull Creator<BeginGetCredentialsResponse> CREATOR =
+            new Creator<BeginGetCredentialsResponse>() {
                 @Override
-                public GetCredentialsResponse createFromParcel(Parcel in) {
-                    return new GetCredentialsResponse(in);
+                public BeginGetCredentialsResponse createFromParcel(Parcel in) {
+                    return new BeginGetCredentialsResponse(in);
                 }
 
                 @Override
-                public GetCredentialsResponse[] newArray(int size) {
-                    return new GetCredentialsResponse[size];
+                public BeginGetCredentialsResponse[] newArray(int size) {
+                    return new BeginGetCredentialsResponse[size];
                 }
             };
 
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 941db02b..3c399d2 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -17,10 +17,9 @@
 package android.service.credentials;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.slice.Slice;
-import android.credentials.Credential;
+import android.credentials.GetCredentialResponse;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -41,32 +40,23 @@
     private final @NonNull Slice mSlice;
 
     /** The pending intent to be invoked when this credential entry is selected. */
-    private final @Nullable PendingIntent mPendingIntent;
-
-    /**
-     * The underlying credential to be returned to the app when the user selects
-     * this credential entry.
-     */
-    private final @Nullable Credential mCredential;
+    private final @NonNull PendingIntent mPendingIntent;
 
     /** A flag denoting whether auto-select is enabled for this entry. */
     private final @NonNull boolean mAutoSelectAllowed;
 
     private CredentialEntry(@NonNull String type, @NonNull Slice slice,
-            @Nullable PendingIntent pendingIntent, @Nullable Credential credential,
-            @NonNull boolean autoSeletAllowed) {
+            @NonNull PendingIntent pendingIntent, @NonNull boolean autoSelectAllowed) {
         mType = type;
         mSlice = slice;
         mPendingIntent = pendingIntent;
-        mCredential = credential;
-        mAutoSelectAllowed = autoSeletAllowed;
+        mAutoSelectAllowed = autoSelectAllowed;
     }
 
     private CredentialEntry(@NonNull Parcel in) {
         mType = in.readString8();
         mSlice = in.readTypedObject(Slice.CREATOR);
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
-        mCredential = in.readTypedObject(Credential.CREATOR);
         mAutoSelectAllowed = in.readBoolean();
     }
 
@@ -93,7 +83,6 @@
         dest.writeString8(mType);
         dest.writeTypedObject(mSlice, flags);
         dest.writeTypedObject(mPendingIntent, flags);
-        dest.writeTypedObject(mCredential, flags);
         dest.writeBoolean(mAutoSelectAllowed);
     }
 
@@ -114,18 +103,11 @@
     /**
      * Returns the pending intent to be invoked if the user selects this entry.
      */
-    public @Nullable PendingIntent getPendingIntent() {
+    public @NonNull PendingIntent getPendingIntent() {
         return mPendingIntent;
     }
 
     /**
-     * Returns the credential associated with this entry.
-     */
-    public @Nullable Credential getCredential() {
-        return mCredential;
-    }
-
-    /**
      * Returns whether this entry can be auto selected if it is the only option for the user.
      */
     public boolean isAutoSelectAllowed() {
@@ -138,8 +120,7 @@
     public static final class Builder {
         private String mType;
         private Slice mSlice;
-        private PendingIntent mPendingIntent = null;
-        private Credential mCredential = null;
+        private PendingIntent mPendingIntent;
         private boolean mAutoSelectAllowed = false;
 
         /**
@@ -152,8 +133,8 @@
          * Once the activity fulfills the required user engagement, the
          * {@link android.app.Activity} result should be set to
          * {@link android.app.Activity#RESULT_OK}, and the
-         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} must be set with a
-         * {@link Credential} object.
+         * {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} must be set with a
+         * {@link GetCredentialResponse} object.
          *
          * @param type the type of credential underlying this credential entry
          * @param slice the content to be displayed with this entry on the UI
@@ -179,26 +160,6 @@
         }
 
         /**
-         * Creates a builder for a {@link CredentialEntry} that contains a {@link Credential},
-         * and does not require further action.
-         * @param type the type of credential underlying this credential entry
-         * @param slice the content to be displayed with this entry on the UI
-         * @param credential the credential to be returned to the client app, when this entry is
-         *                   selected by the user
-         *
-         * @throws IllegalArgumentException If {@code type} is null or empty.
-         * @throws NullPointerException If {@code slice}, or {@code credential} is null.
-         */
-        public Builder(@NonNull String type, @NonNull Slice slice, @NonNull Credential credential) {
-            mType = Preconditions.checkStringNotEmpty(type, "type must not be "
-                    + "null, or empty");
-            mSlice = Objects.requireNonNull(slice,
-                    "slice must not be null");
-            mCredential = Objects.requireNonNull(credential,
-                    "credential must not be null");
-        }
-
-        /**
          * Sets whether the entry is allowed to be auto selected by the framework.
          * The default value is set to false.
          *
@@ -219,12 +180,9 @@
          * is set, or if both are set.
          */
         public @NonNull CredentialEntry build() {
-            Preconditions.checkState(((mPendingIntent != null && mCredential == null)
-                            || (mPendingIntent == null && mCredential != null)),
-                    "Either pendingIntent or credential must be set, and both cannot"
-                            + "be set at the same time");
-            return new CredentialEntry(mType, mSlice, mPendingIntent,
-                    mCredential, mAutoSelectAllowed);
+            Preconditions.checkState(mPendingIntent != null,
+                    "pendingIntent must not be null");
+            return new CredentialEntry(mType, mSlice, mPendingIntent, mAutoSelectAllowed);
         }
     }
 }
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 32646e6..416ddf1 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -21,6 +21,7 @@
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Intent;
 import android.os.CancellationSignal;
@@ -45,12 +46,23 @@
      * returned as part of the {@link BeginCreateCredentialResponse}
      *
      * <p>
-     * Type: {@link android.credentials.CreateCredentialRequest}
+     * Type: {@link android.service.credentials.CreateCredentialRequest}
      */
     public static final String EXTRA_CREATE_CREDENTIAL_REQUEST =
             "android.service.credentials.extra.CREATE_CREDENTIAL_REQUEST";
 
     /**
+     * Intent extra: The {@link GetCredentialRequest} attached with
+     * the {@code pendingIntent} that is invoked when the user selects a {@link CredentialEntry}
+     * returned as part of the {@link BeginGetCredentialsResponse}
+     *
+     * <p>
+     * Type: {@link GetCredentialRequest}
+     */
+    public static final String EXTRA_GET_CREDENTIAL_REQUEST =
+            "android.service.credentials.extra.GET_CREDENTIAL_REQUEST";
+
+    /**
      * Intent extra: The result of a create flow operation, to be set on finish of the
      * {@link android.app.Activity} invoked through the {@code pendingIntent} set on
      * a {@link CreateEntry}.
@@ -58,8 +70,8 @@
      * <p>
      * Type: {@link android.credentials.CreateCredentialResponse}
      */
-    public static final String EXTRA_CREATE_CREDENTIAL_RESULT =
-            "android.service.credentials.extra.CREATE_CREDENTIAL_RESULT";
+    public static final String EXTRA_CREATE_CREDENTIAL_RESPONSE =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_RESPONSE";
 
     /**
      * Intent extra: The result of a get credential flow operation, to be set on finish of the
@@ -67,33 +79,48 @@
      * a {@link CredentialEntry}.
      *
      * <p>
-     * Type: {@link android.credentials.Credential}
+     * Type: {@link android.credentials.GetCredentialResponse}
      */
-    public static final String EXTRA_CREDENTIAL_RESULT =
-            "android.service.credentials.extra.CREDENTIAL_RESULT";
+    public static final String EXTRA_GET_CREDENTIAL_RESPONSE =
+            "android.service.credentials.extra.GET_CREDENTIAL_RESPONSE";
 
     /**
      * Intent extra: The result of an authentication flow, to be set on finish of the
      * {@link android.app.Activity} invoked through the {@link android.app.PendingIntent} set on
-     * a {@link GetCredentialsResponse}. This result should contain the actual content, including
-     * credential entries and action entries, to be shown on the selector.
+     * a {@link BeginGetCredentialsResponse}. This result should contain the actual content,
+     * including credential entries and action entries, to be shown on the selector.
      *
      * <p>
      * Type: {@link CredentialsResponseContent}
      */
-    public static final String EXTRA_GET_CREDENTIALS_CONTENT_RESULT =
-            "android.service.credentials.extra.GET_CREDENTIALS_CONTENT_RESULT";
+    public static final String EXTRA_CREDENTIALS_RESPONSE_CONTENT =
+            "android.service.credentials.extra.CREDENTIALS_RESPONSE_CONTENT";
 
     /**
-     * Intent extra: The error result of any {@link android.app.PendingIntent} flow, to be set
-     * on finish of the corresponding {@link android.app.Activity}. This result should contain an
-     * error code, representing the error encountered by the provider.
+     * Intent extra: The failure exception set at the final stage of a get flow.
+     * This exception is set at the finishing result of the {@link android.app.Activity}
+     * invoked by the {@link PendingIntent} , when a user selects the {@link CredentialEntry}
+     * that contained the {@link PendingIntent} in question.
+     *
+     * <p>The result must be set through {@link android.app.Activity#setResult} as an intent extra
      *
      * <p>
-     * Type: {@link String}
+     * Type: {@link android.credentials.GetCredentialException}
      */
-    public static final String EXTRA_ERROR =
-            "android.service.credentials.extra.ERROR";
+    public static final String EXTRA_GET_CREDENTIAL_EXCEPTION =
+            "android.service.credentials.extra.GET_CREDENTIAL_EXCEPTION";
+
+    /**
+     * Intent extra: The failure exception set at the final stage of a create flow.
+     * This exception is set at the finishing result of the {@link android.app.Activity}
+     * invoked by the {@link PendingIntent} , when a user selects the {@link CreateEntry}
+     * that contained the {@link PendingIntent} in question.
+     *
+     * <p>
+     * Type: {@link android.credentials.CreateCredentialException}
+     */
+    public static final String EXTRA_CREATE_CREDENTIAL_EXCEPTION =
+            "android.service.credentials.extra.CREATE_CREDENTIAL_EXCEPTION";
 
     private static final String TAG = "CredProviderService";
 
@@ -128,20 +155,21 @@
 
     private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
         @Override
-        public ICancellationSignal onGetCredentials(GetCredentialsRequest request,
-                IGetCredentialsCallback callback) {
+        public ICancellationSignal onBeginGetCredentials(BeginGetCredentialsRequest request,
+                IBeginGetCredentialsCallback callback) {
             Objects.requireNonNull(request);
             Objects.requireNonNull(callback);
 
             ICancellationSignal transport = CancellationSignal.createTransport();
 
             mHandler.sendMessage(obtainMessage(
-                    CredentialProviderService::onGetCredentials,
+                    CredentialProviderService::onBeginGetCredentials,
                     CredentialProviderService.this, request,
                     CancellationSignal.fromTransport(transport),
-                    new OutcomeReceiver<GetCredentialsResponse, CredentialProviderException>() {
+                    new OutcomeReceiver<BeginGetCredentialsResponse,
+                            CredentialProviderException>() {
                         @Override
-                        public void onResult(GetCredentialsResponse result) {
+                        public void onResult(BeginGetCredentialsResponse result) {
                             try {
                                 callback.onSuccess(result);
                             } catch (RemoteException e) {
@@ -200,14 +228,29 @@
     /**
      * Called by the android system to retrieve user credentials from the connected provider
      * service.
-     * @param request the credential request for the provider to handle
+     *
+     *
+     *
+     * <p>This API denotes a query stage request for getting user's credentials from a given
+     * credential provider. The request contains a list of
+     * {@link android.credentials.GetCredentialOption} that have parameters to be used for
+     * populating candidate credentials, as a list of {@link CredentialEntry} to be set
+     * on the {@link BeginGetCredentialsResponse}. This list is then shown to the user on a
+     * selector.
+     *
+     * <p>If a {@link PendingIntent} is set on a {@link CredentialEntry}, and the user selects that
+     * entry, a {@link GetCredentialRequest} with all parameters needed to get the actual
+     * {@link android.credentials.Credential} will be sent as part of the {@link Intent} fired
+     * through the {@link PendingIntent}.
+     * @param request the request for the provider to handle
      * @param cancellationSignal signal for providers to listen to any cancellation requests from
      *                           the android system
      * @param callback object used to relay the response of the credentials request
      */
-    public abstract void onGetCredentials(@NonNull GetCredentialsRequest request,
+    public abstract void onBeginGetCredentials(@NonNull BeginGetCredentialsRequest request,
             @NonNull CancellationSignal cancellationSignal,
-            @NonNull OutcomeReceiver<GetCredentialsResponse, CredentialProviderException> callback);
+            @NonNull OutcomeReceiver<
+                    BeginGetCredentialsResponse, CredentialProviderException> callback);
 
     /**
      * Called by the android system to create a credential.
diff --git a/core/java/android/service/credentials/CredentialsResponseContent.java b/core/java/android/service/credentials/CredentialsResponseContent.java
index 32cab50..c2f28cb 100644
--- a/core/java/android/service/credentials/CredentialsResponseContent.java
+++ b/core/java/android/service/credentials/CredentialsResponseContent.java
@@ -29,7 +29,7 @@
 
 /**
  * The content to be displayed on the account selector UI, including credential entries,
- * actions etc. Returned as part of {@link GetCredentialsResponse}
+ * actions etc. Returned as part of {@link BeginGetCredentialsResponse}
  */
 public final class CredentialsResponseContent implements Parcelable {
     /** List of credential entries to be displayed on the UI. */
@@ -124,7 +124,7 @@
          *
          * <p> Once the remote credential flow is complete, the {@link android.app.Activity}
          * result should be set to {@link android.app.Activity#RESULT_OK} and an extra with the
-         * {@link CredentialProviderService#EXTRA_CREDENTIAL_RESULT} key should be populated
+         * {@link CredentialProviderService#EXTRA_GET_CREDENTIAL_RESPONSE} key should be populated
          * with a {@link android.credentials.Credential} object.
          */
         public @NonNull Builder setRemoteCredentialEntry(@Nullable CredentialEntry
@@ -188,7 +188,7 @@
         }
 
         /**
-         * Builds a {@link GetCredentialsResponse} instance.
+         * Builds a {@link CredentialsResponseContent} instance.
          *
          * @throws IllegalStateException if {@code credentialEntries}, {@code actions}
          * and {@code remoteCredentialEntry} are all null or empty.
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialRequest.java
similarity index 83%
rename from core/java/android/service/credentials/GetCredentialsRequest.java
rename to core/java/android/service/credentials/GetCredentialRequest.java
index 9052b54..1d6c83b 100644
--- a/core/java/android/service/credentials/GetCredentialsRequest.java
+++ b/core/java/android/service/credentials/GetCredentialRequest.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import com.android.internal.util.AnnotationValidations;
 import com.android.internal.util.Preconditions;
 
 import java.util.ArrayList;
@@ -30,7 +31,7 @@
 /**
  * Request for getting user's credentials from a given credential provider.
  */
-public final class GetCredentialsRequest implements Parcelable {
+public final class GetCredentialRequest implements Parcelable {
     /** Calling package of the app requesting for credentials. */
     private final @NonNull String mCallingPackage;
 
@@ -40,29 +41,30 @@
      */
     private final @NonNull List<GetCredentialOption> mGetCredentialOptions;
 
-    private GetCredentialsRequest(@NonNull String callingPackage,
+    private GetCredentialRequest(@NonNull String callingPackage,
             @NonNull List<GetCredentialOption> getCredentialOptions) {
         this.mCallingPackage = callingPackage;
         this.mGetCredentialOptions = getCredentialOptions;
     }
 
-    private GetCredentialsRequest(@NonNull Parcel in) {
+    private GetCredentialRequest(@NonNull Parcel in) {
         mCallingPackage = in.readString8();
         List<GetCredentialOption> getCredentialOptions = new ArrayList<>();
         in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
         mGetCredentialOptions = getCredentialOptions;
+        AnnotationValidations.validate(NonNull.class, null, mGetCredentialOptions);
     }
 
-    public static final @NonNull Creator<GetCredentialsRequest> CREATOR =
-            new Creator<GetCredentialsRequest>() {
+    public static final @NonNull Creator<GetCredentialRequest> CREATOR =
+            new Creator<GetCredentialRequest>() {
                 @Override
-                public GetCredentialsRequest createFromParcel(Parcel in) {
-                    return new GetCredentialsRequest(in);
+                public GetCredentialRequest createFromParcel(Parcel in) {
+                    return new GetCredentialRequest(in);
                 }
 
                 @Override
-                public GetCredentialsRequest[] newArray(int size) {
-                    return new GetCredentialsRequest[size];
+                public GetCredentialRequest[] newArray(int size) {
+                    return new GetCredentialRequest[size];
                 }
             };
 
@@ -92,7 +94,7 @@
     }
 
     /**
-     * Builder for {@link GetCredentialsRequest}.
+     * Builder for {@link GetCredentialRequest}.
      */
     public static final class Builder {
         private String mCallingPackage;
@@ -139,18 +141,18 @@
         }
 
         /**
-         * Builds a new {@link GetCredentialsRequest} instance.
+         * Builds a new {@link GetCredentialRequest} instance.
          *
          * @throws NullPointerException If {@code getCredentialOptions} is null.
          * @throws IllegalArgumentException If {@code getCredentialOptions} is empty, or if
          * {@code callingPackage} is null or empty.
          */
-        public @NonNull GetCredentialsRequest build() {
+        public @NonNull GetCredentialRequest build() {
             Preconditions.checkStringNotEmpty(mCallingPackage,
                     "Must set the calling package");
             Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
                     "getCredentialOptions");
-            return new GetCredentialsRequest(mCallingPackage, mGetCredentialOptions);
+            return new GetCredentialRequest(mCallingPackage, mGetCredentialOptions);
         }
     }
 }
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.aidl b/core/java/android/service/credentials/GetCredentialsRequest.aidl
deleted file mode 100644
index b309d69..0000000
--- a/core/java/android/service/credentials/GetCredentialsRequest.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable GetCredentialsRequest;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.aidl b/core/java/android/service/credentials/GetCredentialsResponse.aidl
deleted file mode 100644
index 0d8c635..0000000
--- a/core/java/android/service/credentials/GetCredentialsResponse.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package android.service.credentials;
-
-parcelable GetCredentialsResponse;
\ No newline at end of file
diff --git a/core/java/android/service/credentials/IBeginGetCredentialsCallback.aidl b/core/java/android/service/credentials/IBeginGetCredentialsCallback.aidl
new file mode 100644
index 0000000..9ac28f2
--- /dev/null
+++ b/core/java/android/service/credentials/IBeginGetCredentialsCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.BeginGetCredentialsResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IBeginGetCredentialsCallback {
+    void onSuccess(in BeginGetCredentialsResponse response);
+    void onFailure(int errorCode, in CharSequence message);
+}
\ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index b9eb3ed..1306882 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -17,9 +17,9 @@
 package android.service.credentials;
 
 import android.os.ICancellationSignal;
-import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.BeginGetCredentialsRequest;
 import android.service.credentials.BeginCreateCredentialRequest;
-import android.service.credentials.IGetCredentialsCallback;
+import android.service.credentials.IBeginGetCredentialsCallback;
 import android.service.credentials.IBeginCreateCredentialCallback;
 import android.os.ICancellationSignal;
 
@@ -29,6 +29,6 @@
  * @hide
  */
 interface ICredentialProviderService {
-    ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
+    ICancellationSignal onBeginGetCredentials(in BeginGetCredentialsRequest request, in IBeginGetCredentialsCallback callback);
     ICancellationSignal onBeginCreateCredential(in BeginCreateCredentialRequest request, in IBeginCreateCredentialCallback callback);
 }
diff --git a/core/java/android/service/credentials/IGetCredentialsCallback.aidl b/core/java/android/service/credentials/IGetCredentialsCallback.aidl
deleted file mode 100644
index 6e20c55..0000000
--- a/core/java/android/service/credentials/IGetCredentialsCallback.aidl
+++ /dev/null
@@ -1,13 +0,0 @@
-package android.service.credentials;
-
-import android.service.credentials.GetCredentialsResponse;
-
-/**
- * Interface from the system to a credential provider service.
- *
- * @hide
- */
-oneway interface IGetCredentialsCallback {
-    void onSuccess(in GetCredentialsResponse response);
-    void onFailure(int errorCode, in CharSequence message);
-}
\ No newline at end of file
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index e821af1..d113a3c 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -267,6 +267,8 @@
      * will be restored via NotificationListeners#notifyPostedLocked()
      */
     public static final int REASON_LOCKDOWN = 23;
+    // If adding a new notification cancellation reason, you must also add handling for it in
+    // NotificationCancelledEvent.fromCancelReason.
 
     /**
      * @hide
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index 05c86172..cc83dec 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -525,9 +525,10 @@
             return false;
         }
 
+        // size() calls above took care about gc() compaction.
         for (int index = 0; index < size; index++) {
-            int key = keyAt(index);
-            if (!Objects.equals(valueAt(index), other.get(key))) {
+            if (mKeys[index] != other.mKeys[index]
+                    || !Objects.equals(mValues[index], other.mValues[index])) {
                 return false;
             }
         }
@@ -545,10 +546,11 @@
     public int contentHashCode() {
         int hash = 0;
         int size = size();
+        // size() call above took care about gc() compaction.
         for (int index = 0; index < size; index++) {
-            int key = keyAt(index);
-            E value = valueAt(index);
-            hash = 31 * hash + Objects.hashCode(key);
+            int key = mKeys[index];
+            E value = (E) mValues[index];
+            hash = 31 * hash + key;
             hash = 31 * hash + Objects.hashCode(value);
         }
         return hash;
diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java
index 169c8c5..ce7606a0 100644
--- a/core/java/android/view/DisplayEventReceiver.java
+++ b/core/java/android/view/DisplayEventReceiver.java
@@ -227,8 +227,10 @@
      * timebase.
      * @param physicalDisplayId Stable display ID that uniquely describes a (display, port) pair.
      * @param modeId The new mode Id
+     * @param renderPeriod The render frame period, which is a multiple of the mode's vsync period
      */
-    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+    public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
     }
 
     /**
@@ -303,8 +305,9 @@
 
     // Called from native code.
     @SuppressWarnings("unused")
-    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-        onModeChanged(timestampNanos, physicalDisplayId, modeId);
+    private void dispatchModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+            long renderPeriod) {
+        onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
     }
 
     // Called from native code.
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index 138017c..85cb517 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -180,6 +180,16 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at, which is a divisor of the active mode
+     * refresh rate. This is the rate SurfaceFlinger would consume frames and would be observable
+     * by applications via the cadence of {@link android.view.Choreographer} callbacks and
+     * by backpressure when submitting buffers as fast as possible.
+     * Apps can call {@link android.view.Display#getRefreshRate} to query this value.
+     *
+     */
+    public float renderFrameRate;
+
+    /**
      * The default display mode.
      */
     public int defaultModeId;
@@ -376,6 +386,7 @@
                 && Objects.equals(displayCutout, other.displayCutout)
                 && rotation == other.rotation
                 && modeId == other.modeId
+                && renderFrameRate == other.renderFrameRate
                 && defaultModeId == other.defaultModeId
                 && Arrays.equals(supportedModes, other.supportedModes)
                 && colorMode == other.colorMode
@@ -428,6 +439,7 @@
         displayCutout = other.displayCutout;
         rotation = other.rotation;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = Arrays.copyOf(other.supportedModes, other.supportedModes.length);
         colorMode = other.colorMode;
@@ -475,6 +487,7 @@
         displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source);
         rotation = source.readInt();
         modeId = source.readInt();
+        renderFrameRate = source.readFloat();
         defaultModeId = source.readInt();
         int nModes = source.readInt();
         supportedModes = new Display.Mode[nModes];
@@ -535,6 +548,7 @@
         DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags);
         dest.writeInt(rotation);
         dest.writeInt(modeId);
+        dest.writeFloat(renderFrameRate);
         dest.writeInt(defaultModeId);
         dest.writeInt(supportedModes.length);
         for (int i = 0; i < supportedModes.length; i++) {
@@ -764,6 +778,7 @@
         sb.append(presentationDeadlineNanos);
         sb.append(", mode ");
         sb.append(modeId);
+        sb.append(renderFrameRate);
         sb.append(", defaultMode ");
         sb.append(defaultModeId);
         sb.append(", modes ");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 0743ccb..6d9f99f 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -53,7 +53,6 @@
 import android.view.KeyEvent;
 import android.view.InputEvent;
 import android.view.InsetsState;
-import android.view.InsetsVisibilities;
 import android.view.MagnificationSpec;
 import android.view.MotionEvent;
 import android.view.InputChannel;
diff --git a/core/java/android/view/InsetsVisibilities.java b/core/java/android/view/InsetsVisibilities.java
deleted file mode 100644
index 7d259fb..0000000
--- a/core/java/android/view/InsetsVisibilities.java
+++ /dev/null
@@ -1,134 +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 android.view;
-
-import android.annotation.NonNull;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.Arrays;
-import java.util.StringJoiner;
-
-/**
- * A collection of visibilities of insets. This is used for carrying the requested visibilities.
- * @hide
- */
-public class InsetsVisibilities implements Parcelable {
-
-    private static final int UNSPECIFIED = 0;
-    private static final int VISIBLE = 1;
-    private static final int INVISIBLE = -1;
-
-    private final int[] mVisibilities = new int[InsetsState.SIZE];
-
-    public InsetsVisibilities() {
-    }
-
-    public InsetsVisibilities(InsetsVisibilities other) {
-        set(other);
-    }
-
-    public InsetsVisibilities(Parcel in) {
-        in.readIntArray(mVisibilities);
-    }
-
-    /**
-     * Copies from another {@link InsetsVisibilities}.
-     *
-     * @param other an instance of {@link InsetsVisibilities}.
-     */
-    public void set(InsetsVisibilities other) {
-        System.arraycopy(other.mVisibilities, InsetsState.FIRST_TYPE, mVisibilities,
-                InsetsState.FIRST_TYPE, InsetsState.SIZE);
-    }
-
-    /**
-     * Sets a visibility to a type.
-     *
-     * @param type The {@link @InsetsState.InternalInsetsType}.
-     * @param visible {@code true} represents visible; {@code false} represents invisible.
-     */
-    public void setVisibility(@InsetsState.InternalInsetsType int type, boolean visible) {
-        mVisibilities[type] = visible ? VISIBLE : INVISIBLE;
-    }
-
-    /**
-     * Returns the specified insets visibility of the type. If it has never been specified,
-     * this returns the default visibility.
-     *
-     * @param type The {@link @InsetsState.InternalInsetsType}.
-     * @return The specified visibility or the default one if it is not specified.
-     */
-    public boolean getVisibility(@InsetsState.InternalInsetsType int type) {
-        final int visibility = mVisibilities[type];
-        return visibility == UNSPECIFIED
-                ? InsetsState.getDefaultVisibility(type)
-                : visibility == VISIBLE;
-    }
-
-    @Override
-    public String toString() {
-        StringJoiner joiner = new StringJoiner(", ");
-        for (int type = InsetsState.FIRST_TYPE; type <= InsetsState.LAST_TYPE; type++) {
-            final int visibility = mVisibilities[type];
-            if (visibility != UNSPECIFIED) {
-                joiner.add(InsetsState.typeToString(type) + ": "
-                        + (visibility == VISIBLE ? "visible" : "invisible"));
-            }
-        }
-        return joiner.toString();
-    }
-
-    @Override
-    public int hashCode() {
-        return Arrays.hashCode(mVisibilities);
-    }
-
-    @Override
-    public boolean equals(Object other) {
-        if (!(other instanceof InsetsVisibilities)) {
-            return false;
-        }
-        return Arrays.equals(mVisibilities, ((InsetsVisibilities) other).mVisibilities);
-    }
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeIntArray(mVisibilities);
-    }
-
-    public void readFromParcel(@NonNull Parcel in) {
-        in.readIntArray(mVisibilities);
-    }
-
-    public static final @NonNull Creator<InsetsVisibilities> CREATOR =
-            new Creator<InsetsVisibilities>() {
-
-        public InsetsVisibilities createFromParcel(Parcel in) {
-            return new InsetsVisibilities(in);
-        }
-
-        public InsetsVisibilities[] newArray(int size) {
-            return new InsetsVisibilities[size];
-        }
-    };
-}
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index a6f88a7..ea5d9a6 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -177,6 +177,8 @@
     private static final int ACCENT_UMLAUT = '\u00A8';
     private static final int ACCENT_VERTICAL_LINE_ABOVE = '\u02C8';
     private static final int ACCENT_VERTICAL_LINE_BELOW = '\u02CC';
+    private static final int ACCENT_APOSTROPHE = '\'';
+    private static final int ACCENT_QUOTATION_MARK = '"';
 
     /* Legacy dead key display characters used in previous versions of the API.
      * We still support these characters by mapping them to their non-legacy version. */
@@ -204,8 +206,6 @@
         addCombining('\u030A', ACCENT_RING_ABOVE);
         addCombining('\u030B', ACCENT_DOUBLE_ACUTE);
         addCombining('\u030C', ACCENT_CARON);
-        addCombining('\u030D', ACCENT_VERTICAL_LINE_ABOVE);
-        //addCombining('\u030E', ACCENT_DOUBLE_VERTICAL_LINE_ABOVE);
         //addCombining('\u030F', ACCENT_DOUBLE_GRAVE);
         //addCombining('\u0310', ACCENT_CANDRABINDU);
         //addCombining('\u0311', ACCENT_INVERTED_BREVE);
@@ -229,11 +229,17 @@
         sCombiningToAccent.append('\u0340', ACCENT_GRAVE);
         sCombiningToAccent.append('\u0341', ACCENT_ACUTE);
         sCombiningToAccent.append('\u0343', ACCENT_COMMA_ABOVE);
+        sCombiningToAccent.append('\u030D', ACCENT_APOSTROPHE);
+        sCombiningToAccent.append('\u030E', ACCENT_QUOTATION_MARK);
 
         // One-way legacy mappings to preserve compatibility with older applications.
         sAccentToCombining.append(ACCENT_GRAVE_LEGACY, '\u0300');
         sAccentToCombining.append(ACCENT_CIRCUMFLEX_LEGACY, '\u0302');
         sAccentToCombining.append(ACCENT_TILDE_LEGACY, '\u0303');
+
+        // One-way mappings to use the preferred accent
+        sAccentToCombining.append(ACCENT_APOSTROPHE, '\u0301');
+        sAccentToCombining.append(ACCENT_QUOTATION_MARK, '\u0308');
     }
 
     private static void addCombining(int combining, int accent) {
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 30e7a7a..277b90c 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -187,8 +187,8 @@
             int L, int T, int R, int B);
     private static native void nativeSetDisplaySize(long transactionObj, IBinder displayToken,
             int width, int height);
-    private static native StaticDisplayInfo nativeGetStaticDisplayInfo(IBinder displayToken);
-    private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(IBinder displayToken);
+    private static native StaticDisplayInfo nativeGetStaticDisplayInfo(long displayId);
+    private static native DynamicDisplayInfo nativeGetDynamicDisplayInfo(long displayId);
     private static native DisplayedContentSamplingAttributes
             nativeGetDisplayedContentSamplingAttributes(IBinder displayToken);
     private static native boolean nativeSetDisplayedContentSamplingEnabled(IBinder displayToken,
@@ -1504,6 +1504,7 @@
     public static final class DynamicDisplayInfo {
         public DisplayMode[] supportedDisplayModes;
         public int activeDisplayModeId;
+        public float renderFrameRate;
 
         public int[] supportedColorModes;
         public int activeColorMode;
@@ -1520,6 +1521,7 @@
             return "DynamicDisplayInfo{"
                     + "supportedDisplayModes=" + Arrays.toString(supportedDisplayModes)
                     + ", activeDisplayModeId=" + activeDisplayModeId
+                    + ", renderFrameRate=" + renderFrameRate
                     + ", supportedColorModes=" + Arrays.toString(supportedColorModes)
                     + ", activeColorMode=" + activeColorMode
                     + ", hdrCapabilities=" + hdrCapabilities
@@ -1535,6 +1537,7 @@
             DynamicDisplayInfo that = (DynamicDisplayInfo) o;
             return Arrays.equals(supportedDisplayModes, that.supportedDisplayModes)
                 && activeDisplayModeId == that.activeDisplayModeId
+                && renderFrameRate == that.renderFrameRate
                 && Arrays.equals(supportedColorModes, that.supportedColorModes)
                 && activeColorMode == that.activeColorMode
                 && Objects.equals(hdrCapabilities, that.hdrCapabilities)
@@ -1544,7 +1547,7 @@
         @Override
         public int hashCode() {
             return Objects.hash(Arrays.hashCode(supportedDisplayModes), activeDisplayModeId,
-                    activeColorMode, hdrCapabilities);
+                    renderFrameRate, activeColorMode, hdrCapabilities);
         }
     }
 
@@ -1624,21 +1627,15 @@
     /**
      * @hide
      */
-    public static StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetStaticDisplayInfo(displayToken);
+    public static StaticDisplayInfo getStaticDisplayInfo(long displayId) {
+        return nativeGetStaticDisplayInfo(displayId);
     }
 
     /**
      * @hide
      */
-    public static DynamicDisplayInfo getDynamicDisplayInfo(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-        return nativeGetDynamicDisplayInfo(displayToken);
+    public static DynamicDisplayInfo getDynamicDisplayInfo(long displayId) {
+        return nativeGetDynamicDisplayInfo(displayId);
     }
 
     /**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 09a9d46..727011c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -77,6 +77,7 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -470,16 +471,6 @@
     private boolean mAppVisibilityChanged;
     int mOrigWindowType = -1;
 
-    /** Whether the window had focus during the most recent traversal. */
-    boolean mHadWindowFocus;
-
-    /**
-     * Whether the window lost focus during a previous traversal and has not
-     * yet gained it back. Used to determine whether a WINDOW_STATE_CHANGE
-     * accessibility events should be sent during traversal.
-     */
-    boolean mLostWindowFocus;
-
     // Set to true if the owner of this window is in the stopped state,
     // so the window should no longer be active.
     @UnsupportedAppUsage
@@ -505,6 +496,13 @@
     Region mTouchableRegion;
     Region mPreviousTouchableRegion;
 
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+
+    // This indicates that we've already known the window size but without measuring the views.
+    // If this is true, we must measure the views before laying out them.
+    private boolean mViewMeasureDeferred;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mWidth;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -2593,7 +2591,8 @@
     }
 
     private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
-            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
+            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
+            boolean forRootSizeOnly) {
         int childWidthMeasureSpec;
         int childHeightMeasureSpec;
         boolean windowSizeMayChange = false;
@@ -2649,7 +2648,15 @@
                     lp.privateFlags);
             childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height,
                     lp.privateFlags);
-            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            if (!forRootSizeOnly || !setMeasuredRootSizeFromSpec(
+                    childWidthMeasureSpec, childHeightMeasureSpec)) {
+                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
+            } else {
+                // We already know how big the window should be before measuring the views.
+                // We can measure the views before laying out them. This is to avoid unnecessary
+                // measure.
+                mViewMeasureDeferred = true;
+            }
             if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                 windowSizeMayChange = true;
             }
@@ -2665,6 +2672,25 @@
     }
 
     /**
+     * Sets the measured root size for requesting the window frame.
+     *
+     * @param widthMeasureSpec contains the size and the mode of the width.
+     * @param heightMeasureSpec contains the size and the mode of the height.
+     * @return {@code true} if we actually set the measured size; {@code false} otherwise.
+     */
+    private boolean setMeasuredRootSizeFromSpec(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
+            // We don't know the exact size. We need to measure the hierarchy to know that.
+            return false;
+        }
+        mMeasuredWidth = MeasureSpec.getSize(widthMeasureSpec);
+        mMeasuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+        return true;
+    }
+
+    /**
      * Modifies the input matrix such that it maps view-local coordinates to
      * on-screen coordinates.
      *
@@ -2751,6 +2777,14 @@
                 || lp.type == TYPE_VOLUME_OVERLAY;
     }
 
+    /**
+     * @return {@code true} if we should reduce unnecessary measure for the window.
+     * TODO(b/260382739): Apply this to all windows.
+     */
+    private static boolean shouldOptimizeMeasure(final WindowManager.LayoutParams lp) {
+        return lp.type == TYPE_NOTIFICATION_SHADE;
+    }
+
     private Rect getWindowBoundsInsetSystemBars() {
         final Rect bounds = new Rect(
                 mContext.getResources().getConfiguration().windowConfiguration.getBounds());
@@ -2801,6 +2835,7 @@
         mAppVisibilityChanged = false;
         final boolean viewUserVisibilityChanged = !mFirst &&
                 ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
+        final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
 
         WindowManager.LayoutParams params = null;
         CompatibilityInfo compatibilityInfo =
@@ -2922,7 +2957,7 @@
 
             // Ask host how big it wants to be
             windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
-                    desiredWindowWidth, desiredWindowHeight);
+                    desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
         }
 
         if (collectViewAttributes()) {
@@ -2962,8 +2997,8 @@
                 // we don't need to go through two layout passes when things
                 // change due to fitting system windows, which can happen a lot.
                 windowSizeMayChange |= measureHierarchy(host, lp,
-                        mView.getContext().getResources(),
-                        desiredWindowWidth, desiredWindowHeight);
+                        mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
+                        shouldOptimizeMeasure);
             }
         }
 
@@ -3383,6 +3418,13 @@
             maybeHandleWindowMove(frame);
         }
 
+        if (mViewMeasureDeferred) {
+            // It's time to measure the views since we are going to layout them.
+            performMeasure(
+                    MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
+        }
+
         if (!mRelayoutRequested && mCheckIfCanDraw) {
             // We had a sync previously, but we didn't call IWindowSession#relayout in this
             // traversal. So we don't know if the sync is complete that we can continue to draw.
@@ -3578,20 +3620,8 @@
         }
 
         final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
-        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
-        final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
-        if (regainedFocus) {
-            mLostWindowFocus = false;
-        } else if (!hasWindowFocus && mHadWindowFocus) {
-            mLostWindowFocus = true;
-        }
-
-        if (changedVisibility || regainedFocus) {
-            // Toasts are presented as notifications - don't present them as windows as well
-            boolean isToast = mWindowAttributes.type == TYPE_TOAST;
-            if (!isToast) {
-                host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
-            }
+        if (changedVisibility) {
+            maybeFireAccessibilityWindowStateChangedEvent();
         }
 
         mFirst = false;
@@ -3599,8 +3629,8 @@
         mNewSurfaceNeeded = false;
         mActivityRelaunched = false;
         mViewVisibility = viewVisibility;
-        mHadWindowFocus = hasWindowFocus;
 
+        final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
         mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
 
         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3843,6 +3873,8 @@
                         ~WindowManager.LayoutParams
                                 .SOFT_INPUT_IS_FORWARD_NAVIGATION;
 
+                maybeFireAccessibilityWindowStateChangedEvent();
+
                 // Refocusing a window that has a focused view should fire a
                 // focus event for the view since the global focused view changed.
                 fireAccessibilityFocusEventIfHasFocusedNode();
@@ -3870,6 +3902,14 @@
         ensureTouchModeLocally(inTouchMode);
     }
 
+    private void maybeFireAccessibilityWindowStateChangedEvent() {
+        // Toasts are presented as notifications - don't present them as windows as well.
+        boolean isToast = mWindowAttributes != null && (mWindowAttributes.type == TYPE_TOAST);
+        if (!isToast && mView != null) {
+            mView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        }
+    }
+
     private void fireAccessibilityFocusEventIfHasFocusedNode() {
         if (!AccessibilityManager.getInstance(mContext).isEnabled()) {
             return;
@@ -3973,6 +4013,9 @@
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_VIEW);
         }
+        mMeasuredWidth = mView.getMeasuredWidth();
+        mMeasuredHeight = mView.getMeasuredHeight();
+        mViewMeasureDeferred = false;
     }
 
     /**
@@ -4068,7 +4111,7 @@
                         view.requestLayout();
                     }
                     measureHierarchy(host, lp, mView.getContext().getResources(),
-                            desiredWindowWidth, desiredWindowHeight);
+                            desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
                     mInLayout = true;
                     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
 
@@ -8167,8 +8210,8 @@
         final WindowConfiguration winConfigFromWm =
                 mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
         final WindowConfiguration winConfig = getCompatWindowConfiguration();
-        final int measuredWidth = mView.getMeasuredWidth();
-        final int measuredHeight = mView.getMeasuredHeight();
+        final int measuredWidth = mMeasuredWidth;
+        final int measuredHeight = mMeasuredHeight;
         final boolean relayoutAsync;
         if (LOCAL_LAYOUT
                 && (mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 8de15c1..fd55d8d 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1493,37 +1493,37 @@
         public static String toString(@InsetsType int types) {
             StringBuilder result = new StringBuilder();
             if ((types & STATUS_BARS) != 0) {
-                result.append("statusBars |");
+                result.append("statusBars ");
             }
             if ((types & NAVIGATION_BARS) != 0) {
-                result.append("navigationBars |");
+                result.append("navigationBars ");
             }
             if ((types & CAPTION_BAR) != 0) {
-                result.append("captionBar |");
+                result.append("captionBar ");
             }
             if ((types & IME) != 0) {
-                result.append("ime |");
+                result.append("ime ");
             }
             if ((types & SYSTEM_GESTURES) != 0) {
-                result.append("systemGestures |");
+                result.append("systemGestures ");
             }
             if ((types & MANDATORY_SYSTEM_GESTURES) != 0) {
-                result.append("mandatorySystemGestures |");
+                result.append("mandatorySystemGestures ");
             }
             if ((types & TAPPABLE_ELEMENT) != 0) {
-                result.append("tappableElement |");
+                result.append("tappableElement ");
             }
             if ((types & DISPLAY_CUTOUT) != 0) {
-                result.append("displayCutout |");
+                result.append("displayCutout ");
             }
             if ((types & WINDOW_DECOR) != 0) {
-                result.append("windowDecor |");
+                result.append("windowDecor ");
             }
             if ((types & SYSTEM_OVERLAYS) != 0) {
-                result.append("systemOverlays |");
+                result.append("systemOverlays ");
             }
             if (result.length() > 0) {
-                result.delete(result.length() - 2, result.length());
+                result.delete(result.length() - 1, result.length());
             }
             return result.toString();
         }
diff --git a/core/java/android/window/DisplayWindowPolicyController.java b/core/java/android/window/DisplayWindowPolicyController.java
index f55932e..e027934 100644
--- a/core/java/android/window/DisplayWindowPolicyController.java
+++ b/core/java/android/window/DisplayWindowPolicyController.java
@@ -128,9 +128,10 @@
             ActivityInfo activityInfo, int windowFlags, int systemWindowFlags);
 
     /**
-     * Returns {@code true} if the tasks which is on this virtual display can be showed on Recents.
+     * Returns {@code true} if the tasks which is on this virtual display can be showed in the
+     * host device of the recently launched activities list.
      */
-    public abstract boolean canShowTasksInRecents();
+    public abstract boolean canShowTasksInHostDeviceRecents();
 
     /**
      * This is called when the top activity of the display is changed.
diff --git a/core/java/android/window/TaskFragmentCreationParams.java b/core/java/android/window/TaskFragmentCreationParams.java
index 81ab783..c9ddf92 100644
--- a/core/java/android/window/TaskFragmentCreationParams.java
+++ b/core/java/android/window/TaskFragmentCreationParams.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.WindowingMode;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.TestApi;
 import android.graphics.Rect;
 import android.os.IBinder;
@@ -57,14 +58,33 @@
 
     /** The initial windowing mode of the TaskFragment. Inherits from parent if not set. */
     @WindowingMode
-    private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+    private final int mWindowingMode;
+
+    /**
+     * The fragment token of the paired primary TaskFragment.
+     * When it is set, the new TaskFragment will be positioned right above the paired TaskFragment.
+     * Otherwise, the new TaskFragment will be positioned on the top of the Task by default.
+     *
+     * This is different from {@link WindowContainerTransaction#setAdjacentTaskFragments} as we may
+     * set this when the pair of TaskFragments are stacked, while adjacent is only set on the pair
+     * of TaskFragments that are in split.
+     *
+     * This is needed in case we need to launch a placeholder Activity to split below a transparent
+     * always-expand Activity.
+     */
+    @Nullable
+    private final IBinder mPairedPrimaryFragmentToken;
 
     private TaskFragmentCreationParams(
-            @NonNull TaskFragmentOrganizerToken organizer,
-            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
+            @NonNull TaskFragmentOrganizerToken organizer, @NonNull IBinder fragmentToken,
+            @NonNull IBinder ownerToken, @NonNull Rect initialBounds,
+            @WindowingMode int windowingMode, @Nullable IBinder pairedPrimaryFragmentToken) {
         mOrganizer = organizer;
         mFragmentToken = fragmentToken;
         mOwnerToken = ownerToken;
+        mInitialBounds.set(initialBounds);
+        mWindowingMode = windowingMode;
+        mPairedPrimaryFragmentToken = pairedPrimaryFragmentToken;
     }
 
     @NonNull
@@ -92,12 +112,22 @@
         return mWindowingMode;
     }
 
+    /**
+     * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+     * @hide
+     */
+    @Nullable
+    public IBinder getPairedPrimaryFragmentToken() {
+        return mPairedPrimaryFragmentToken;
+    }
+
     private TaskFragmentCreationParams(Parcel in) {
         mOrganizer = TaskFragmentOrganizerToken.CREATOR.createFromParcel(in);
         mFragmentToken = in.readStrongBinder();
         mOwnerToken = in.readStrongBinder();
         mInitialBounds.readFromParcel(in);
         mWindowingMode = in.readInt();
+        mPairedPrimaryFragmentToken = in.readStrongBinder();
     }
 
     /** @hide */
@@ -108,6 +138,7 @@
         dest.writeStrongBinder(mOwnerToken);
         mInitialBounds.writeToParcel(dest, flags);
         dest.writeInt(mWindowingMode);
+        dest.writeStrongBinder(mPairedPrimaryFragmentToken);
     }
 
     @NonNull
@@ -132,6 +163,7 @@
                 + " ownerToken=" + mOwnerToken
                 + " initialBounds=" + mInitialBounds
                 + " windowingMode=" + mWindowingMode
+                + " pairedFragmentToken=" + mPairedPrimaryFragmentToken
                 + "}";
     }
 
@@ -159,6 +191,9 @@
         @WindowingMode
         private int mWindowingMode = WINDOWING_MODE_UNDEFINED;
 
+        @Nullable
+        private IBinder mPairedPrimaryFragmentToken;
+
         public Builder(@NonNull TaskFragmentOrganizerToken organizer,
                 @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken) {
             mOrganizer = organizer;
@@ -180,14 +215,29 @@
             return this;
         }
 
+        /**
+         * Sets the fragment token of the paired primary TaskFragment.
+         * When it is set, the new TaskFragment will be positioned right above the paired
+         * TaskFragment. Otherwise, the new TaskFragment will be positioned on the top of the Task
+         * by default.
+         *
+         * This is needed in case we need to launch a placeholder Activity to split below a
+         * transparent always-expand Activity.
+         *
+         * TODO(b/232476698): remove the hide with adding CTS for this in next release.
+         * @hide
+         */
+        @NonNull
+        public Builder setPairedPrimaryFragmentToken(@Nullable IBinder fragmentToken) {
+            mPairedPrimaryFragmentToken = fragmentToken;
+            return this;
+        }
+
         /** Constructs the options to create TaskFragment with. */
         @NonNull
         public TaskFragmentCreationParams build() {
-            final TaskFragmentCreationParams result = new TaskFragmentCreationParams(
-                    mOrganizer, mFragmentToken, mOwnerToken);
-            result.mInitialBounds.set(mInitialBounds);
-            result.mWindowingMode = mWindowingMode;
-            return result;
+            return new TaskFragmentCreationParams(mOrganizer, mFragmentToken, mOwnerToken,
+                    mInitialBounds, mWindowingMode, mPairedPrimaryFragmentToken);
         }
     }
 }
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index 43be031..e0b0110 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -226,9 +226,7 @@
         Slog.d(TAG, "Accessibility shortcut activated");
         final ContentResolver cr = mContext.getContentResolver();
         final int userId = ActivityManager.getCurrentUser();
-        final int dialogAlreadyShown = Settings.Secure.getIntForUser(
-                cr, Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
-                userId);
+
         // Play a notification vibration
         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         if ((vibrator != null) && vibrator.hasVibrator()) {
@@ -239,7 +237,7 @@
             vibrator.vibrate(vibePattern, -1, VIBRATION_ATTRIBUTES);
         }
 
-        if (dialogAlreadyShown == DialogStatus.NOT_SHOWN) {
+        if (shouldShowDialog()) {
             // The first time, we show a warning rather than toggle the service to give the user a
             // chance to turn off this feature before stuff gets enabled.
             mAlertDialog = createShortcutWarningDialog(userId);
@@ -269,6 +267,20 @@
         }
     }
 
+    /** Whether the warning dialog should be shown instead of performing the shortcut. */
+    private boolean shouldShowDialog() {
+        if (hasFeatureLeanback()) {
+            // Never show the dialog on TV, instead always perform the shortcut directly.
+            return false;
+        }
+        final ContentResolver cr = mContext.getContentResolver();
+        final int userId = ActivityManager.getCurrentUser();
+        final int dialogAlreadyShown = Settings.Secure.getIntForUser(cr,
+                Settings.Secure.ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, DialogStatus.NOT_SHOWN,
+                userId);
+        return dialogAlreadyShown == DialogStatus.NOT_SHOWN;
+    }
+
     /**
      * Show toast to alert the user that the accessibility shortcut turned on or off an
      * accessibility service.
diff --git a/core/java/com/android/internal/content/om/TEST_MAPPING b/core/java/com/android/internal/content/om/TEST_MAPPING
index 4cb595b..98dadce7 100644
--- a/core/java/com/android/internal/content/om/TEST_MAPPING
+++ b/core/java/com/android/internal/content/om/TEST_MAPPING
@@ -7,6 +7,28 @@
           "include-filter": "com.android.internal.content."
         }
       ]
+    },
+    {
+      "name": "SelfTargetingOverlayDeviceTests"
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
+        }
+      ]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 614f962..75f0bf5 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -97,6 +97,7 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
@@ -415,7 +416,7 @@
     @VisibleForTesting
     public InteractionJankMonitor(@NonNull HandlerThread worker) {
         // Check permission early.
-        DeviceConfig.enforceReadPermission(
+        Settings.Config.enforceReadPermission(
             DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR);
 
         mRunningTrackers = new SparseArray<>();
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 556e146..c3361a2 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -1047,6 +1047,17 @@
     }
 
     /**
+     * Records a wakelock release event.
+     */
+    public void recordWakelockStopEvent(long elapsedRealtimeMs, long uptimeMs, String historyName,
+            int uid) {
+        mHistoryCur.wakelockTag = mHistoryCur.localWakelockTag;
+        mHistoryCur.wakelockTag.string = historyName != null ? historyName : "";
+        mHistoryCur.wakelockTag.uid = uid;
+        recordStateStopEvent(elapsedRealtimeMs, uptimeMs, HistoryItem.STATE_WAKE_LOCK_FLAG);
+    }
+
+    /**
      * Records an event when some state flag changes to true.
      */
     public void recordStateStartEvent(long elapsedRealtimeMs, long uptimeMs, int stateFlags) {
diff --git a/core/java/com/android/internal/power/ModemPowerProfile.java b/core/java/com/android/internal/power/ModemPowerProfile.java
index afea69a..a555ae3 100644
--- a/core/java/com/android/internal/power/ModemPowerProfile.java
+++ b/core/java/com/android/internal/power/ModemPowerProfile.java
@@ -158,11 +158,11 @@
 
     private static final SparseArray<String> MODEM_TX_LEVEL_NAMES = new SparseArray<>(5);
     static {
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_0, "0");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_1, "1");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_2, "2");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_3, "3");
-        MODEM_DRAIN_TYPE_NAMES.put(MODEM_TX_LEVEL_4, "4");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_0, "0");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_1, "1");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_2, "2");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_3, "3");
+        MODEM_TX_LEVEL_NAMES.put(MODEM_TX_LEVEL_4, "4");
     }
 
     private static final int[] MODEM_TX_LEVEL_MAP = new int[]{
@@ -418,7 +418,10 @@
         return Double.NaN;
     }
 
-    private static String keyToString(int key) {
+    /**
+     * Returns a human readable version of a key.
+     */
+    public static String keyToString(int key) {
         StringBuilder sb = new StringBuilder();
         final int drainType = key & MODEM_DRAIN_TYPE_MASK;
         appendFieldToString(sb, "drain", MODEM_DRAIN_TYPE_NAMES, drainType);
@@ -427,6 +430,7 @@
         if (drainType == MODEM_DRAIN_TYPE_TX) {
             final int txLevel = key & MODEM_TX_LEVEL_MASK;
             appendFieldToString(sb, "level", MODEM_TX_LEVEL_NAMES, txLevel);
+            sb.append(",");
         }
 
         final int ratType = key & MODEM_RAT_TYPE_MASK;
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index ebfc4f7..db288c0 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -327,4 +327,14 @@
 
     /** Shows rear display educational dialog */
     void showRearDisplayDialog(int currentBaseState);
+
+    /** Called when requested to go to fullscreen from the active split app. */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @param leftOrTop indicates where the stage split is.
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 1bc903a..a43f0b3 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -339,6 +339,9 @@
                 "dnsproxyd_protocol_headers",
                 "libtextclassifier_hash_headers",
             ],
+            runtime_libs: [
+                "libidmap2",
+            ],
         },
         host: {
             cflags: [
diff --git a/core/jni/TEST_MAPPING b/core/jni/TEST_MAPPING
index 004c30e..2844856 100644
--- a/core/jni/TEST_MAPPING
+++ b/core/jni/TEST_MAPPING
@@ -11,6 +11,29 @@
         }
       ],
       "file_patterns": ["CharsetUtils|FastData"]
+    },
+    {
+      "name": "SelfTargetingOverlayDeviceTests",
+      "file_patterns": ["Overlay"]
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsContentTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "include-filter": "android.content.om.cts"
+        },
+        {
+          "include-filter": "android.content.res.loader.cts"
+        }
+      ]
     }
   ]
 }
diff --git a/core/jni/android_media_AudioMixerAttributes.h b/core/jni/android_media_AudioMixerAttributes.h
new file mode 100644
index 0000000..61adb27
--- /dev/null
+++ b/core/jni/android_media_AudioMixerAttributes.h
@@ -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.
+ */
+
+#ifndef ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H
+#define ANDROID_MEDIA_AUDIOMIXERATTRIBUTES_H
+
+#include <system/audio.h>
+
+// Keep sync with AudioMixerAttributes.java
+#define MIXER_BEHAVIOR_DEFAULT 0
+// Invalid value is not added in JAVA API, but keep sync with native value
+#define MIXER_BEHAVIOR_INVALID -1
+
+static inline audio_mixer_behavior_t audioMixerBehaviorToNative(int mixerBehavior) {
+    switch (mixerBehavior) {
+        case MIXER_BEHAVIOR_DEFAULT:
+            return AUDIO_MIXER_BEHAVIOR_DEFAULT;
+        default:
+            return AUDIO_MIXER_BEHAVIOR_INVALID;
+    }
+}
+
+static inline jint audioMixerBehaviorFromNative(audio_mixer_behavior_t mixerBehavior) {
+    switch (mixerBehavior) {
+        case AUDIO_MIXER_BEHAVIOR_DEFAULT:
+            return MIXER_BEHAVIOR_DEFAULT;
+        case AUDIO_MIXER_BEHAVIOR_INVALID:
+        default:
+            return MIXER_BEHAVIOR_INVALID;
+    }
+}
+
+#endif
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 2433a05..a0e1bca 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -21,6 +21,7 @@
 #include <android/media/AudioVibratorInfo.h>
 #include <android/media/INativeSpatializerCallback.h>
 #include <android/media/ISpatializer.h>
+#include <android/media/audio/common/AudioConfigBase.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
 #include <jni.h>
@@ -34,6 +35,7 @@
 #include <system/audio_policy.h>
 #include <utils/Log.h>
 
+#include <optional>
 #include <sstream>
 #include <vector>
 
@@ -43,6 +45,7 @@
 #include "android_media_AudioEffectDescriptor.h"
 #include "android_media_AudioErrors.h"
 #include "android_media_AudioFormat.h"
+#include "android_media_AudioMixerAttributes.h"
 #include "android_media_AudioProfile.h"
 #include "android_media_MicrophoneInfo.h"
 #include "android_util_Binder.h"
@@ -51,6 +54,7 @@
 // ----------------------------------------------------------------------------
 
 using namespace android;
+using media::audio::common::AudioConfigBase;
 
 static const char* const kClassPathName = "android/media/AudioSystem";
 
@@ -145,10 +149,12 @@
 } gAudioMixFields;
 
 static jclass gAudioFormatClass;
+static jmethodID gAudioFormatCstor;
 static struct {
     jfieldID    mEncoding;
     jfieldID    mSampleRate;
     jfieldID    mChannelMask;
+    jfieldID mChannelIndexMask;
     // other fields unused by JNI
 } gAudioFormatFields;
 
@@ -211,6 +217,7 @@
     jfieldID mChannelMasks;
     jfieldID mChannelIndexMasks;
     jfieldID mEncapsulationType;
+    jfieldID mMixerBehaviors;
 } gAudioProfileFields;
 
 jclass gVibratorClass;
@@ -221,6 +228,13 @@
     jmethodID getMaxAmplitude;
 } gVibratorMethods;
 
+jclass gAudioMixerAttributesClass;
+jmethodID gAudioMixerAttributesCstor;
+static struct {
+    jfieldID mFormat;
+    jfieldID mMixerBehavior;
+} gAudioMixerAttributesField;
+
 static Mutex gLock;
 
 enum AudioError {
@@ -237,6 +251,12 @@
 
 #define MAX_PORT_GENERATION_SYNC_ATTEMPTS 5
 
+// Keep sync with AudioFormat.java
+#define AUDIO_FORMAT_HAS_PROPERTY_ENCODING 0x1
+#define AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE 0x2
+#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK 0x4
+#define AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK 0x8
+
 // ----------------------------------------------------------------------------
 // ref-counted object for audio port callbacks
 class JNIAudioPortCallback: public AudioSystem::AudioPortCallback
@@ -2105,6 +2125,80 @@
     }
 }
 
+void javaAudioFormatToNativeAudioConfigBase(JNIEnv *env, const jobject jFormat,
+                                            audio_config_base_t *nConfigBase, bool isInput) {
+    *nConfigBase = AUDIO_CONFIG_BASE_INITIALIZER;
+    nConfigBase->format =
+            audioFormatToNative(env->GetIntField(jFormat, gAudioFormatFields.mEncoding));
+    nConfigBase->sample_rate = env->GetIntField(jFormat, gAudioFormatFields.mSampleRate);
+    jint jChannelMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelMask);
+    jint jChannelIndexMask = env->GetIntField(jFormat, gAudioFormatFields.mChannelIndexMask);
+    nConfigBase->channel_mask = jChannelIndexMask != 0
+            ? audio_channel_mask_from_representation_and_bits(AUDIO_CHANNEL_REPRESENTATION_INDEX,
+                                                              jChannelIndexMask)
+            : isInput ? inChannelMaskToNative(jChannelMask)
+                      : outChannelMaskToNative(jChannelMask);
+}
+
+jobject nativeAudioConfigBaseToJavaAudioFormat(JNIEnv *env, const audio_config_base_t *nConfigBase,
+                                               bool isInput) {
+    if (nConfigBase == nullptr) {
+        return nullptr;
+    }
+    int propertyMask = AUDIO_FORMAT_HAS_PROPERTY_ENCODING | AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE;
+    int channelMask = 0;
+    int channelIndexMask = 0;
+    switch (audio_channel_mask_get_representation(nConfigBase->channel_mask)) {
+        case AUDIO_CHANNEL_REPRESENTATION_POSITION:
+            channelMask = isInput ? inChannelMaskFromNative(nConfigBase->channel_mask)
+                                  : outChannelMaskFromNative(nConfigBase->channel_mask);
+            propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK;
+            break;
+        case AUDIO_CHANNEL_REPRESENTATION_INDEX:
+            channelIndexMask = audio_channel_mask_get_bits(nConfigBase->channel_mask);
+            propertyMask |= AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_INDEX_MASK;
+            break;
+        default:
+            // This must not happen
+            break;
+    }
+    return env->NewObject(gAudioFormatClass, gAudioFormatCstor, propertyMask,
+                          audioFormatFromNative(nConfigBase->format), nConfigBase->sample_rate,
+                          channelMask, channelIndexMask);
+}
+
+jint convertAudioMixerAttributesToNative(JNIEnv *env, const jobject jAudioMixerAttributes,
+                                         audio_mixer_attributes_t *nMixerAttributes) {
+    ScopedLocalRef<jobject> jFormat(env,
+                                    env->GetObjectField(jAudioMixerAttributes,
+                                                        gAudioMixerAttributesField.mFormat));
+    javaAudioFormatToNativeAudioConfigBase(env, jFormat.get(), &nMixerAttributes->config,
+                                           false /*isInput*/);
+    nMixerAttributes->mixer_behavior = audioMixerBehaviorToNative(
+            env->GetIntField(jAudioMixerAttributes, gAudioMixerAttributesField.mMixerBehavior));
+    if (nMixerAttributes->mixer_behavior == AUDIO_MIXER_BEHAVIOR_INVALID) {
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+jobject convertAudioMixerAttributesFromNative(JNIEnv *env,
+                                              const audio_mixer_attributes_t *nMixerAttributes) {
+    if (nMixerAttributes == nullptr) {
+        return nullptr;
+    }
+    jint mixerBehavior = audioMixerBehaviorFromNative(nMixerAttributes->mixer_behavior);
+    if (mixerBehavior == MIXER_BEHAVIOR_INVALID) {
+        return nullptr;
+    }
+    ScopedLocalRef<jobject>
+            jFormat(env,
+                    nativeAudioConfigBaseToJavaAudioFormat(env, &nMixerAttributes->config,
+                                                           false /*isInput*/));
+    return env->NewObject(gAudioMixerAttributesClass, gAudioMixerAttributesCstor, jFormat.get(),
+                          mixerBehavior);
+}
+
 static jint convertAudioMixToNative(JNIEnv *env,
                                     AudioMix *nAudioMix,
                                     const jobject jAudioMix)
@@ -2868,6 +2962,15 @@
     return canBeSpatialized;
 }
 
+static jint android_media_AudioSystem_registerSoundDoseCallback(JNIEnv *env, jobject thiz,
+                                                                    jobject jISoundDoseCallback) {
+    sp<media::ISoundDoseCallback> nISoundDoseCallback = interface_cast<media::ISoundDoseCallback>(
+            ibinderForJavaObject(env, jISoundDoseCallback));
+
+    return static_cast<jint>(
+            check_AudioSystem_Command(AudioSystem::registerSoundDoseCallback(nISoundDoseCallback)));
+}
+
 // keep these values in sync with AudioSystem.java
 #define DIRECT_NOT_SUPPORTED 0
 #define DIRECT_OFFLOAD_SUPPORTED 1
@@ -2957,6 +3060,142 @@
     return jStatus;
 }
 
+static jint android_media_AudioSystem_getSupportedMixerAttributes(JNIEnv *env, jobject thiz,
+                                                                  jint jDeviceId,
+                                                                  jobject jAudioMixerAttributes) {
+    ALOGV("%s", __func__);
+    if (jAudioMixerAttributes == NULL) {
+        ALOGE("getSupportedMixerAttributes NULL AudioMixerAttributes list");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) {
+        ALOGE("getSupportedMixerAttributes not a list");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    std::vector<audio_mixer_attributes_t> nMixerAttributes;
+    status_t status = AudioSystem::getSupportedMixerAttributes((audio_port_handle_t)jDeviceId,
+                                                               &nMixerAttributes);
+    if (status != NO_ERROR) {
+        return nativeToJavaStatus(status);
+    }
+    for (const auto &mixerAttr : nMixerAttributes) {
+        ScopedLocalRef<jobject> jMixerAttributes(env,
+                                                 convertAudioMixerAttributesFromNative(env,
+                                                                                       &mixerAttr));
+        if (jMixerAttributes.get() == nullptr) {
+            return (jint)AUDIO_JAVA_ERROR;
+        }
+
+        env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get());
+    }
+
+    return (jint)AUDIO_JAVA_SUCCESS;
+}
+
+static jint android_media_AudioSystem_setPreferredMixerAttributes(JNIEnv *env, jobject thiz,
+                                                                  jobject jAudioAttributes,
+                                                                  jint portId, jint uid,
+                                                                  jobject jAudioMixerAttributes) {
+    ALOGV("%s", __func__);
+
+    if (jAudioAttributes == nullptr) {
+        ALOGE("jAudioAttributes is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (jAudioMixerAttributes == nullptr) {
+        ALOGE("jAudioMixerAttributes is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    audio_mixer_attributes_t mixerAttributes = AUDIO_MIXER_ATTRIBUTES_INITIALIZER;
+    jStatus = convertAudioMixerAttributesToNative(env, jAudioMixerAttributes, &mixerAttributes);
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    status_t status =
+            AudioSystem::setPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
+                                                     (uid_t)uid, &mixerAttributes);
+    return nativeToJavaStatus(status);
+}
+
+static jint android_media_AudioSystem_getPreferredMixerAttributes(JNIEnv *env, jobject thiz,
+                                                                  jobject jAudioAttributes,
+                                                                  jint portId,
+                                                                  jobject jAudioMixerAttributes) {
+    ALOGV("%s", __func__);
+
+    if (jAudioAttributes == nullptr) {
+        ALOGE("getPreferredMixerAttributes jAudioAttributes is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (jAudioMixerAttributes == NULL) {
+        ALOGE("getPreferredMixerAttributes NULL AudioMixerAttributes list");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+    if (!env->IsInstanceOf(jAudioMixerAttributes, gListClass)) {
+        ALOGE("getPreferredMixerAttributes not a list");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    std::optional<audio_mixer_attributes_t> nMixerAttributes;
+    status_t status =
+            AudioSystem::getPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
+                                                     &nMixerAttributes);
+    if (status != NO_ERROR) {
+        return nativeToJavaStatus(status);
+    }
+
+    ScopedLocalRef<jobject>
+            jMixerAttributes(env,
+                             convertAudioMixerAttributesFromNative(env,
+                                                                   nMixerAttributes.has_value()
+                                                                           ? &nMixerAttributes
+                                                                                      .value()
+                                                                           : nullptr));
+    if (jMixerAttributes.get() == nullptr) {
+        return (jint)AUDIO_JAVA_ERROR;
+    }
+
+    env->CallBooleanMethod(jAudioMixerAttributes, gListMethods.add, jMixerAttributes.get());
+    return AUDIO_JAVA_SUCCESS;
+}
+
+static jint android_media_AudioSystem_clearPreferredMixerAttributes(JNIEnv *env, jobject thiz,
+                                                                    jobject jAudioAttributes,
+                                                                    jint portId, jint uid) {
+    ALOGV("%s", __func__);
+
+    if (jAudioAttributes == nullptr) {
+        ALOGE("jAudioAttributes is NULL");
+        return (jint)AUDIO_JAVA_BAD_VALUE;
+    }
+
+    JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+    jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jAudioAttributes, paa.get());
+    if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
+        return jStatus;
+    }
+
+    status_t status =
+            AudioSystem::clearPreferredMixerAttributes(paa.get(), (audio_port_handle_t)portId,
+                                                       (uid_t)uid);
+    return nativeToJavaStatus(status);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] =
@@ -3104,12 +3343,23 @@
           "(Landroid/media/AudioAttributes;Landroid/media/AudioFormat;"
           "[Landroid/media/AudioDeviceAttributes;)Z",
           (void *)android_media_AudioSystem_canBeSpatialized},
+         {"registerSoundDoseCallback", "(Landroid/media/ISoundDoseCallback;)I",
+          (void *)android_media_AudioSystem_registerSoundDoseCallback},
          {"getDirectPlaybackSupport",
           "(Landroid/media/AudioFormat;Landroid/media/AudioAttributes;)I",
           (void *)android_media_AudioSystem_getDirectPlaybackSupport},
          {"getDirectProfilesForAttributes",
           "(Landroid/media/AudioAttributes;Ljava/util/ArrayList;)I",
-          (void *)android_media_AudioSystem_getDirectProfilesForAttributes}};
+          (void *)android_media_AudioSystem_getDirectProfilesForAttributes},
+         {"getSupportedMixerAttributes", "(ILjava/util/List;)I",
+          (void *)android_media_AudioSystem_getSupportedMixerAttributes},
+         {"setPreferredMixerAttributes",
+          "(Landroid/media/AudioAttributes;IILandroid/media/AudioMixerAttributes;)I",
+          (void *)android_media_AudioSystem_setPreferredMixerAttributes},
+         {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
+          (void *)android_media_AudioSystem_getPreferredMixerAttributes},
+         {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
+          (void *)android_media_AudioSystem_clearPreferredMixerAttributes}};
 
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
@@ -3272,9 +3522,12 @@
 
     jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat");
     gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
+    gAudioFormatCstor = GetMethodIDOrDie(env, audioFormatClass, "<init>", "(IIIII)V");
     gAudioFormatFields.mEncoding = GetFieldIDOrDie(env, audioFormatClass, "mEncoding", "I");
     gAudioFormatFields.mSampleRate = GetFieldIDOrDie(env, audioFormatClass, "mSampleRate", "I");
     gAudioFormatFields.mChannelMask = GetFieldIDOrDie(env, audioFormatClass, "mChannelMask", "I");
+    gAudioFormatFields.mChannelIndexMask =
+            GetFieldIDOrDie(env, audioFormatClass, "mChannelIndexMask", "I");
 
     jclass audioMixingRuleClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMixingRule");
     gAudioMixingRuleClass = MakeGlobalRefOrDie(env, audioMixingRuleClass);
@@ -3348,6 +3601,15 @@
     gVibratorMethods.getMaxAmplitude =
             GetMethodIDOrDie(env, vibratorClass, "getHapticChannelMaximumAmplitude", "()F");
 
+    jclass audioMixerAttributesClass = FindClassOrDie(env, "android/media/AudioMixerAttributes");
+    gAudioMixerAttributesClass = MakeGlobalRefOrDie(env, audioMixerAttributesClass);
+    gAudioMixerAttributesCstor = GetMethodIDOrDie(env, audioMixerAttributesClass, "<init>",
+                                                  "(Landroid/media/AudioFormat;I)V");
+    gAudioMixerAttributesField.mFormat = GetFieldIDOrDie(env, audioMixerAttributesClass, "mFormat",
+                                                         "Landroid/media/AudioFormat;");
+    gAudioMixerAttributesField.mMixerBehavior =
+            GetFieldIDOrDie(env, audioMixerAttributesClass, "mMixerBehavior", "I");
+
     AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
 
     RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 019e3bd..a8d8a43 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -80,7 +80,7 @@
                        VsyncEventData vsyncEventData) override;
     void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
     void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
-                             nsecs_t vsyncPeriod) override;
+                             nsecs_t renderPeriod) override;
     void dispatchFrameRateOverrides(nsecs_t timestamp, PhysicalDisplayId displayId,
                                     std::vector<FrameRateOverride> overrides) override;
     void dispatchNullEvent(nsecs_t timestamp, PhysicalDisplayId displayId) override {}
@@ -168,14 +168,14 @@
 }
 
 void NativeDisplayEventReceiver::dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
-                                                     int32_t modeId, nsecs_t) {
+                                                     int32_t modeId, nsecs_t renderPeriod) {
     JNIEnv* env = AndroidRuntime::getJNIEnv();
 
     ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal));
     if (receiverObj.get()) {
         ALOGV("receiver %p ~ Invoking mode changed handler.", this);
         env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchModeChanged,
-                            timestamp, displayId.value, modeId);
+                            timestamp, displayId.value, modeId, renderPeriod);
         ALOGV("receiver %p ~ Returned from mode changed handler.", this);
     }
 
@@ -290,7 +290,7 @@
             gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V");
     gDisplayEventReceiverClassInfo.dispatchModeChanged =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchModeChanged",
-                             "(JJI)V");
+                             "(JJIJ)V");
     gDisplayEventReceiverClassInfo.dispatchFrameRateOverrides =
             GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz,
                              "dispatchFrameRateOverrides",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 2c5386d..593482c 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -109,6 +109,7 @@
     jmethodID ctor;
     jfieldID supportedDisplayModes;
     jfieldID activeDisplayModeId;
+    jfieldID renderFrameRate;
     jfieldID supportedColorModes;
     jfieldID activeColorMode;
     jfieldID hdrCapabilities;
@@ -1098,10 +1099,9 @@
                           connectionToSinkType);
 }
 
-static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static jobject nativeGetStaticDisplayInfo(JNIEnv* env, jclass clazz, jlong id) {
     ui::StaticDisplayInfo info;
-    if (const auto token = ibinderForJavaObject(env, tokenObj);
-        !token || SurfaceComposerClient::getStaticDisplayInfo(token, &info) != NO_ERROR) {
+    if (SurfaceComposerClient::getStaticDisplayInfo(id, &info) != NO_ERROR) {
         return nullptr;
     }
 
@@ -1159,10 +1159,9 @@
                           capabilities.getDesiredMinLuminance());
 }
 
-static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jobject tokenObj) {
+static jobject nativeGetDynamicDisplayInfo(JNIEnv* env, jclass clazz, jlong displayId) {
     ui::DynamicDisplayInfo info;
-    if (const auto token = ibinderForJavaObject(env, tokenObj);
-        !token || SurfaceComposerClient::getDynamicDisplayInfo(token, &info) != NO_ERROR) {
+    if (SurfaceComposerClient::getDynamicDisplayInfoFromId(displayId, &info) != NO_ERROR) {
         return nullptr;
     }
 
@@ -1184,6 +1183,7 @@
     env->SetObjectField(object, gDynamicDisplayInfoClassInfo.supportedDisplayModes, modesArray);
     env->SetIntField(object, gDynamicDisplayInfoClassInfo.activeDisplayModeId,
                      info.activeDisplayModeId);
+    env->SetFloatField(object, gDynamicDisplayInfoClassInfo.renderFrameRate, info.renderFrameRate);
 
     jintArray colorModesArray = env->NewIntArray(info.supportedColorModes.size());
     if (colorModesArray == NULL) {
@@ -2018,10 +2018,10 @@
     {"nativeSetDisplaySize", "(JLandroid/os/IBinder;II)V",
             (void*)nativeSetDisplaySize },
     {"nativeGetStaticDisplayInfo",
-            "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$StaticDisplayInfo;",
+            "(J)Landroid/view/SurfaceControl$StaticDisplayInfo;",
             (void*)nativeGetStaticDisplayInfo },
     {"nativeGetDynamicDisplayInfo",
-            "(Landroid/os/IBinder;)Landroid/view/SurfaceControl$DynamicDisplayInfo;",
+            "(J)Landroid/view/SurfaceControl$DynamicDisplayInfo;",
             (void*)nativeGetDynamicDisplayInfo },
     {"nativeSetDesiredDisplayModeSpecs",
             "(Landroid/os/IBinder;Landroid/view/SurfaceControl$DesiredDisplayModeSpecs;)Z",
@@ -2174,6 +2174,8 @@
                             "[Landroid/view/SurfaceControl$DisplayMode;");
     gDynamicDisplayInfoClassInfo.activeDisplayModeId =
             GetFieldIDOrDie(env, dynamicInfoClazz, "activeDisplayModeId", "I");
+    gDynamicDisplayInfoClassInfo.renderFrameRate =
+            GetFieldIDOrDie(env, dynamicInfoClazz, "renderFrameRate", "F");
     gDynamicDisplayInfoClassInfo.supportedColorModes =
             GetFieldIDOrDie(env, dynamicInfoClazz, "supportedColorModes", "[I");
     gDynamicDisplayInfoClassInfo.activeColorMode =
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 7e17840..3c4bac8 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -107,7 +107,9 @@
     sdk_version: "core_platform",
     certificate: "platform",
 
-    srcs: [":remote-color-resources-arsc"],
+    srcs: [
+        ":remote-color-resources-arsc",
+    ],
 
     // Disable dexpreopt and verify_uses_libraries check as the app
     // contains no Java code to be dexpreopted.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 8da91ee..09672c2 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3327,7 +3327,9 @@
          <p>Protection level: normal
          -->
     <permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
-        android:protectionLevel="normal"/>
+                android:label="@string/permlab_startForegroundServicesFromBackground"
+                android:description="@string/permdesc_startForegroundServicesFromBackground"
+                android:protectionLevel="normal"/>
 
     <!-- Allows a companion app to use data in the background.
          <p>Protection level: normal
@@ -3343,6 +3345,8 @@
          <p>Protection level: normal
      -->
     <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
+                android:label="@string/permlab_companionProfileWatch"
+                android:description="@string/permdesc_companionProfileWatch"
                 android:protectionLevel="normal" />
 
     <!-- Allows application to request to be associated with a virtual display capable of streaming
@@ -4021,7 +4025,7 @@
          @hide
     -->
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|module" />
 
     <!-- Allows an application to avoid all toast rate limiting restrictions.
          <p>Not for use by third-party applications.
@@ -4625,6 +4629,8 @@
          <p>Protection level: appop
      -->
     <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
+        android:label="@string/permlab_schedule_exact_alarm"
+        android:description="@string/permdesc_schedule_exact_alarm"
         android:protectionLevel="normal|appop"/>
 
     <!-- Allows apps to use exact alarms just like with {@link
@@ -4650,6 +4656,8 @@
          lower standby bucket.
     -->
     <permission android:name="android.permission.USE_EXACT_ALARM"
+                android:label="@string/permlab_use_exact_alarm"
+                android:description="@string/permdesc_use_exact_alarm"
                 android:protectionLevel="normal"/>
 
     <!-- Allows an application to query tablet mode state and monitor changes
@@ -4914,11 +4922,15 @@
          of their associated companion device
          -->
     <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
+                android:label="@string/permlab_observeCompanionDevicePresence"
+                android:description="@string/permdesc_observeCompanionDevicePresence"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to deliver companion messages to system
          -->
     <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+                android:label="@string/permlab_deliverCompanionMessages"
+                android:description="@string/permdesc_deliverCompanionMessages"
                 android:protectionLevel="normal" />
 
     <!-- Allows an application to create new companion device associations.
@@ -6764,6 +6776,14 @@
          @hide -->
     <permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART"
                 android:protectionLevel="signature" />
+
+    <!-- Allows low-level access to re-mapping modifier keys.
+         <p>Not for use by third-party applications.
+         @hide
+         @TestApi -->
+    <permission android:name="android.permission.REMAP_MODIFIER_KEYS"
+                android:protectionLevel="signature" />
+
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
 
     <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
@@ -7338,6 +7358,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.healthconnect.storage.AutoDeleteService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/assets/geoid_height_map/README.md b/core/res/assets/geoid_height_map/README.md
new file mode 100644
index 0000000..849d32e
--- /dev/null
+++ b/core/res/assets/geoid_height_map/README.md
@@ -0,0 +1,2 @@
+These binary protos are generated at runtime from the text protos in ../../geoid_height_map_assets
+and using aprotoc.
\ No newline at end of file
diff --git a/core/res/assets/geoid_height_map/map-params.pb b/core/res/assets/geoid_height_map/map-params.pb
new file mode 100644
index 0000000..6fd4022
--- /dev/null
+++ b/core/res/assets/geoid_height_map/map-params.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-1.pb b/core/res/assets/geoid_height_map/tile-1.pb
new file mode 100644
index 0000000..546dd0d
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-1.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-3.pb b/core/res/assets/geoid_height_map/tile-3.pb
new file mode 100644
index 0000000..eb3fe46
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-3.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-5.pb b/core/res/assets/geoid_height_map/tile-5.pb
new file mode 100644
index 0000000..0243d6d0
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-5.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-7.pb b/core/res/assets/geoid_height_map/tile-7.pb
new file mode 100644
index 0000000..3c2f777
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-7.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-9.pb b/core/res/assets/geoid_height_map/tile-9.pb
new file mode 100644
index 0000000..5e9a480
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-9.pb
Binary files differ
diff --git a/core/res/assets/geoid_height_map/tile-b.pb b/core/res/assets/geoid_height_map/tile-b.pb
new file mode 100644
index 0000000..c57e873
--- /dev/null
+++ b/core/res/assets/geoid_height_map/tile-b.pb
Binary files differ
diff --git a/core/res/geoid_height_map_assets/README.md b/core/res/geoid_height_map_assets/README.md
new file mode 100644
index 0000000..800b3e5
--- /dev/null
+++ b/core/res/geoid_height_map_assets/README.md
@@ -0,0 +1,8 @@
+These text protos contain composite JPEG/PNG images representing the EGM2008 Earth Gravitational
+Model[^1] published by the National Geospatial-Intelligence Agency.[^2]
+
+[^1]: Pavlis, Nikolaos K., et al. "The development and evaluation of the Earth Gravitational Model
+2008 (EGM2008)." Journal of geophysical research: solid earth 117.B4 (2012).
+
+[^2]: National Geospatial-Intelligence Agency. “Office of Geomatics.” 2022.
+URL: https://earth-info.nga.mil.
\ No newline at end of file
diff --git a/core/res/geoid_height_map_assets/map-params.textpb b/core/res/geoid_height_map_assets/map-params.textpb
new file mode 100644
index 0000000..3f504d4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/map-params.textpb
@@ -0,0 +1,6 @@
+map_s2_level: 9
+cache_tile_s2_level: 5
+disk_tile_s2_level: 0
+model_a_meters: 255.0
+model_b_meters: -128.0
+model_rmse_meters: 0.36
diff --git a/core/res/geoid_height_map_assets/tile-1.textpb b/core/res/geoid_height_map_assets/tile-1.textpb
new file mode 100644
index 0000000..7fac234
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-1.textpb
@@ -0,0 +1,3 @@
+tile_key: "1"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\244i(\242\224R\216\264\361OZ\221je\251V\246Z\225jE\251V\245Z\225jE\247\216\324\361N\317\024f\214\322\023I\2323I\232L\322f\220\232LdqM\316:\321E\024\354\344sJ)\364\340i\340S\200\315(\030\243\255:\224\032p4\264\340i\300\324\213\315:\232EFz\320\0174\244\323I\246\223M-F\3527R\026\244\rR+S\353\314\r%\024QN\247\212z\324\253R\255J\2652\324\253R\255H*U\251\024\324\213O\006\244\024\264QM4RRRt\244$\342\220{\322a\201\312\363M,I\344P\017\255;\214\360h\245\006\234\r<5(4\365cO\006\226\227\024\264\nPiisNSS)\245&\232M0\232J3Fj65\031j7Rn\244\335F\352\225\033\212\224\036+\314\215!8\242\212)\303\245<S\326\245Z\225jU\251\226\245Z\221jU5\"\324\202\244\006\236)\342\235\2323HNh\2434\204\322SI\244\3154\322\t\n\364\241e\301\346\234]X\360(\371{Ss\315(4\340i\340\323\305<S\251A\365\247\216zQE.i3J\017\255(8\251U\251\305\251\204\323wRg4f\226\243sQ\023M\335I\272\223u\033\252x\232\236\322W\232\346\201\315)\351H\r-8S\305<\n\221jU\251V\246Z\225MJ\265\"\232\221jQO\024\360i\340\323\263I\232;\321\232Bi3IHO\024\302i3M4\334R\216)E-:\214\323\301\251\024\323\301\247\203K\326\201\221\305(4\273\251C\003KE&j@iwSKSKR\006\247f\226\230\325\003\034Te\251\245\250\335K\272\244G\300\241\236\274\374\212J\t\245\024\264\341O\002\244\025 \251TT\252*Q\305H\246\245SR\255<T\200\342\236\r<\032x4\271\2434f\214\322g\336\214\322n\3074\326bi\224\322i3I\232PiE:\235J\0058S\205<\032x4\340psN\316\356\264\224\240f\214R\200{sN\036\364\204zP\033\024\026\246\346\220\265\000\323\324\323\211\246\267J\253!\346\241-M\335I\276\215\364\345\227\212S%p\331\244\315!4\240\342\2349\251\000\251\022\244\002\236\005H\016*E\004\367\251\000>\265\"\232\225MJ\246\244SR\003N\006\236\r?4f\215\324\002)3Fh\315!>\264\204\323M7\223M9\024f\226\224S\201\247\212p\024\340)\324\242\234)\302\235\232\0058\014\323\302\346\244\000\001H\334\216*\022\r\' \323\261\232i\024\332L\322\356\3058=\014\374UYZ\2533sM-M-I\276\220\311\203K\346W\036\335i\271\245\315-<T\212jd\000\324\300\nv\332]\264\365\251\024\323\305H\246\244V\251\025\252P\324\360\324\340\324\354\322\346\214\322n\244\315\031\2434\231\367\2439\242\233\315&\t\240\nZu(\024\341\322\236)\340\323\207ZZu--(\251\024T\200S\361M \322\021\212\215\215\013\232v3\326\243u\305Bi\271\243v)\013UyZ\253\026\246\026\244\335HZ\243g\305\n\371\025\313\223\223E\'J\003T\201\263N\rS\306\370\251\321\352]\364\006\247\n\221i\300\323\301\251\026\244\025 4\360i\340\323\263J\r)4\334\321\232L\321E(\243\036\224}h\3074c\326\224\016)\300R\342\226\234:R\212p\247\212u:\212P*t\\u\251\024sO\307\2454\203M*M\"\305\334\323\304`\016\224\205)\214\271\025VE\301\250I\346\232M4\265V\225\252\271j\214\265&\352\013Tl\324\212Nk\235u\301\244\006\226\232G4\345\034S\226\246SR\251\251T\324\200\323\301\251\001\342\224\032p5 5\"\265H\032\236\032\236\032\234\r(4\354\322\032i4QE8R\343\024\244n?-(\030\034\322\355\357J)\301sF)qK\212p\024\242\235N\002\236\005(\024\365^jQ\351R\016*D\030\0314\270\024\234f\220\221J\016)O\"\242u\300\252\317\212\251/\025\\\2651\232\240\220\346\2533TE\2517PZ\230Z\244OZ\304u\315W*E%\031\346\2274\340jE5*\232\221MJ\032\244\016)CS\203S\203T\212\325 jxjxjxjx4\340i\331\2434Rd\032>\224\242\224\032p\247t\373\264\240f\234\247\035E.=*D\311\030\244\307<\322\342\214S\200\245\3058\np\247\201N\002\244Q\3058\nxS\326\235\272\223u!jM\334\346\202\371\251#\177Z\212y;\n\250\354j\274\247+UKb\242f\250]\262\rUc\223Q\261\246\346\220\2657v[\025aH\305d\221Q\262\203Q\264~\225\021\0304\242\234\005<T\200\323\301\247\203O\rN\rO\rO\006\236\rH\r<\032\220\032x4\360i\300\323\301\245&\222\212)E(\247\212p4\341N\034\365\247\004=\251\300`\323\331r3M\242\234\0058\nu\002\244Z\221Fi\346\225E<\275FZ\224P\302\231\2323NV\3051\2335VV\305V\221\370\252\214\334\324.\325\013>\005BMFM4\232ajj\037\232\246\316*\211\031\246\021M<\036j)S\270\250\207\006\236)\300\323\201\247\003O\rN\rN\rO\rR+T\201\252@i\340\323\301\247\203R\003O\006\234\r;4f\226\212QN\035i\302\234)\302\234*@i\300R\266q\326\220R\322\212Zu(\251\026\245N\224\340(\346\2028\250\363\315.\361H\322SKSKR\027\244.1Tg\227\223U\336O\226\253\226\250\335\270\252\356\325\021zc50\2651\232\225\033\034\323\214\225X7\024\3074\314\322\365\025\t\034\320)E:\235J\r(4\340i\341\251\341\252Uj\2205<\032\2205<\032\220\032x5 4\354\321J)iE8R\216\264\361\326\234)\300\323\205<\032x\346\215\270\240\nZ)A\245\025*\324\240b\244QI\'\025\031~\324\322j6ni\244\323wTm.)\215!=*3)\031\315S\222M\315QH\374T%\351\217\'\025Y\236\241g\244-\221Q\231)\245\350\3631F\372\256\257\212\035\371\247+\002)H\300\372\324\r\301\240\002FiA\247\003N\024\341KE(4\360\325\"\265H\255R+T\200\324\200\324\212j@j@i\324\240\322\322\203J\r8S\2058S\251\300\323\201\247\203OS\212\220`\365\247l\006\243<\032(\240\032\225\rN\247\232\225M6S\232\256i\245\2526j\214\265F\317P\273\032h|S\036@{\325f\340\325y_\232\204\265E$\225]\244\250\313\324m\'\2753}&\374\232\031\251w\361\212\210\032\010\315 8\342\245\r\221H\311\270\344S\261\260u\241B\261\347\212xU\'\031\240\250\354h\002\212(\245\006\234\rH\246\244SR\253T\212\325*\265H\246\244SO\3158\034\321\232Pis\212p4\340i\300\323\301\247\003N\024\340i\340\324\213R\251\300\250\336\243\315\031\247\n\231*PjE4\217U\337\203Q\263b\242g\250\331\252\"\325\0335B\315Q3TM \025\003\2605\003\266*\273\266j\274\204\212\207\314$\323Y\351\273\351CsAl\232pj\211Z\244\315;\002\224qR)\024\222\214\257\035\2521N\025 \247\216:R\032\000\024\231\242\224\032x5\"\232\221MJ\246\244SR\251\251T\324\200\322\212u\024\240\322\203N\006\234)\324\341N\006\236\r<\032\221Z\244\335Lza\3068\244\024\3455:\036*@i\340\342\225\216EWaP\311\305Vf\346\232\315Q3Te\251\214j\007\252\316\rC\236\271\250]\372\324\005\361Lr\030Uv\030\250\230\323\013b\205|\323\225\275i\302Nh\024\240\346\236\265 \024\003@\2239\030\315\"\343w\315\300\247\034\003\3058\032x4\264\204SM&i\300\323\201\251\026\244Z\225jU\251\026\245SR)\247\322\322\321J)E<\032p4\271\247\003N\006\236\r<\032xj]\324\323I\364\247\n\224\034\npj\2205!|To \305V\221\352\2635F\317\305D\315Q3\324fJa|\323\030\344Ug\030\315Tr9\252\356\324\315\370\250\344q\332\243$\032\206F\3051_\013I\347`\322\371\340\016\265p\232\007Z\221H\247\223@\246\276\027\225<\323U\311\353R\016i\302\234\016)\300\321\232Bi(\024\365\251V\245QR-J\242\244Z\221jU\247\322\212\\\321Fi\324\341N\024\np4\271\245\rO\rO\rK\272\234\034R\202\r9y\247\223\232Pi\013\342\232d\250\231\252\027j\254[\232\215\232\230Z\240v#\245@\322Sw\344\3224\230\025RisU\267TNs\322\253\273\021Q\026\246\371\225\034\255\221\232\214\036)\214\334\324l\325\262M74\240\234\324\212r9\251\007\000\324\017\311\241x\251T\323\301\3158R\321\232)3J*E\251EH\265*\324\242\244\024\361R\003N\006\235N\242\2123N\006\236\r.h\315\024\271\245\rN\rN\335I\272\2245N\217\305H\264\342x\250\\\324d\323\031\252\031\033\212\256Z\230MD\307\024\322\303\034\325Iz\234TFP\243\336\253IrI\300\250\231\311\034\232\207q&\220\266\006*\0275\013TD\323$o\227\024\302p*&jh\311<V\306\354\323\t\251\243\373\274\323\324\323\363L#\212h\340\323\301\251\026\236)h\242\212QR\n\220T\213R\251\251EH*@i\302\234)\331\245\006\235E\024\242\234\r-\024f\2274f\2245.\3523OSS\257\"\236\246\225\215B\315L&\242f\250\035\252\026j\210\2654\265B\315P\263UIO\245W\315F\315@\340d\324,\331\311\250\213Td\323\r@\347\006\241g\315\0107\034S\300\330s\236\225\177\'4\365\251T\366\247\322\364\024v\244\305(\251\026\236)sFiiE8u\247\n\220T\212*U\251V\244\025 \247\001N\024\264\341J)\331\030\351IJ)h\242\2123K\2323@9\245\247\255N\215\305H\0174\3622*\026\025\021\025\013T/U\331\252&5\0335D\315P;TM\316j\244\200\255F\0334\331\037\013P\253dT.\374\361Q\226\244\335QHj#\3159\030(>\265\033=l\200\r4\360i\350y\251A\241\216\005,|\2574\273M \353O\006\244\007\024f\200iE:\234)\342\244Z\225jU\025*\212\221E<S\205(\247S\201\315(\245\243\351E\024Q\2323Fh\245\006\224\034S\251EJ\246\246\034\214\323\201\241\205B\343\025]\352\007<UF=j&j\214\265D\355U\334\323\013T/\363US\36256^V\241\034-B\375j3Gj\216R\002\324!\276Z\214\2754\265n\253\323\261\232p\030\251\001\247\021\305:1\201O\307\345L<\032)\331\245\006\224S\251E<T\213R\250\251TT\252*@*@*AKE(\247\np4\354\203\332\222\214\372\322\023I\2323Fh\315\024\271\247\003N\024\361O\034T\250\335\251\343\031\247\023U\345`*\243\275Ww\252\356j\0064\306\346\242cPHs\322\241-\353M&\253\3102i$\000\'\275@zsQ=ELv\300\252\222K\232a~*2\324\205\253l5L\215S)\315H\026\236\005=W\025 \034SY8\250\250\247\nu:\234)\342\244QS(\251\224T\252*@*E\024\360)\333h\305&)\302\224\nZ)\246\212L\014u\244\242\214\321\232Pi\300\323\203S\203S\301\251\024\323\303\363C>\005S\226L\232\256\355P;T,\325ZG\346\243\3631Q\273\347\2450\236*\273\2674\200\346\220\214\232c&[\223\300\250g\340qU\230\361P\261\305C/CU\030b\243&\233\232ij\334\006\244SV#j\264\234\212x\342\244Z\220\nR\274Ug\034\361M\245\025 \024\243\255<S\300\251\026\246AS\255L\242\244\002\236\005H\242\237\212LQ\266\227m\030\243\024b\223m\030\246\220;SN1M\242\214\321\232]\324\340\324\340\324\360\365\"\267\024\026\346\221\334m\252r?5\0135@\355P\263\324\016rj\007\342\242\3630y\241\233\212\205\201\355LRKb\207s\273\002\230M5\210\306\rVu\306qP7Z\205\370\252\322T\004\323\t\246\223[\240\324\212j\304f\255!\251\001\251Tb\246ZRj\263\016i1@\024\360)\300S\200\251\000\251\024T\312*e\0252\324\242\244\025\"\212~(\305&\3321K\212LQ\2121AZiZa\\\347\024\302)\244RQF}\350\335N\rN\004\324\212\330\034\323\032L\232Fo\226\252\273TL\334Uy\036\253\264\225\023I\3151\233\212\254\347\232M\365 \341y\250\207\031\367\246\232k\032\201\330\223M \221PH*\263\236*\263\234\325v<\323\t\246\223[\252je5<ue\017J\235je\247\203J\307\212\212\214R\201J\0058\nx\025\"\255H\242\245QS-L\242\245\002\244\002\236\253O\002\235\2121HE!\036\224b\226\214Q\212\n\324l\246\242a\212a\024\332C\326\233\2323N\006\246Zk\266*\271~i\314\374T\016\325\004\217U]\352\274\215U\313\234\322o4\3269\246/Z\224\266\0056\243s\212\215\232\243=i\340qPH9\252N9\301\252\257P7Z\210\232a5\274\225:\212\236:\262\274b\254!\315J)CzP\\\343\030\244\034\322\201OQN\333N\002\236\005<\n\221EJ\242\245QS(\251\224S\300\251\000\247\201N\305\030\244+M\305\030\244\245\006\226\202)\244TN9\310\250\230S\010\246\232i\024\224\245\200\245\022\340To&\352\217<\3223\361U\336LUg\222\240y*\006z\205\233&\233\272\215\331\247(\346\234\313\315\025\024\234\232\204\365\245\305/j\205\272\223T\346\0309\252\255\311\252\362pj\003Q\265t\010*\302\234T\250j\312\363V\022\236\030\032\025\261\320\323\363\221\3159O\030\247v\247(\247\201J\005H\005<\n\221EH\242\245Z\231jU\251@\247\201R\001K\212)\r%&(\305\030\243\024\021HEF\313Q\262\324ei\204S\010\246\261\305G\313R1\300\342\233\232kTL\330\252\262\2775Y\332\240f\250\331\261Q\026\346\233\236i\353SD2j}\234\234\324.1P=DFi@8\305(\004\214\nc\256\336\265NnsU\010\353U$\250MF\325\321\"\032\231S5*\200*\302\234\nw\231\330S\303\032r\232\225NjAO\024\361O\024\360*@)\341i\340T\212*E\025*\212\231jU\025 \024\3608\245\"\223\024Q\212m\024QK\232Ji\024\302\276\225\031J\214\2550\255DS&\221\206\321Q\221\232\002b\230\342\252\313T\344\252\256j\022\365\013\276M34\n\231*\314?xU\2228&\253\311\336\25352\227\034qD|\212\216j\243%@zU\031\206\r@M1\253\250\351J[\002\205j\231Z\245S\353S)\030\251\027\232\221V\244U\251\025\rJ\261\232\221b&\236#5 B)\352)\340T\200\n\221EJ\253R*\324\200\021\326\236\265 \024QHE%!\024\224QKIE4\212i\024\302\275\351\205i\206<sP\310\274\323U;\320\303\002\240\220\3259\0175NS\212\246\3475]\215DM%*\324\351V\242\030\"\255\0221Ue\252\315M\357OQH\027nj\031\252\224\225]\370\252\322\256j\243\014\032\214\327LOzhl\232x52\232\225MJ\2652\232\235MJ\246\246Z\225EL\2652\212xZxZ\\{P\024}*@\247\352)\340\343\255L\2305(\024\270\245\315\031\245\242\220\212Ji\030\242\212L\321\2323IHi1\232k\014Tl{T/M\350)\215Ue5Q\352\224\334\232\252\374T\014)\204S)\351S\'QV\242\346\245|\214\021P?5\023-DF\rH\2074\3622*\244\243\004\212\251(\346\252\311P\236j\244\313\203U\315tE\363J\265*\212\231jU\034\324\312*eZ\231T\324\310\246\247U5:!\251UjP)\342\236>\224\341N\306h\000\216\224\340\304u\251\025\324\366\305J\030v4\241\251w\212p#\265-&)(\244\'4\224\021\232m\024\231\245\315&x\244\007\0249/\315DEDW&\220\214TRUIj\243\203T\344\035j\263\212\205\2051\2050\255*\212\235\005X\214\342\236_\327\221Q1\007\245\'Z\214\216x\247\001\265M&x\252\322\034\346\251\310j\273\212\200\214T2.\341U$\\V\332\324\253S(\251\224T\313S\245N\265:T\350*t\025:\212x\031\247\201N\002\236)qJ)\334\322\322\201N\024\365\315H\006i\333\005.\334t\244\311\024\231\245\315\024\322)\264QHi3I\232:\322\206\300#\035j3M\353LqU\336\253H3U\334dUI\022\252\310\265\\\212\217\0314\2453@J\231F\005<u\247\3435\013\251C\354h\246\363\221J[\203M\317\006\253Jj\253\014\324.*\"\264\306Z\253*f\264\326\245Z\231jU5:\324\351V\022\247AS\240\253\010*e\247\212x\247\016\224\372p\024\340)\300R\205\247\205\245\013O\013O\002\235\212Z\nR\025\244\331\355M \212NM!\024\332)\247\336\232i@&\244T\241\222\233\345\226\031\364\2462\355\250\330T\016\271\250\031*\273\307U\244N*\244\211U\236:\214G\315?e\033iqKO\007\212d\274\212\214PG\024\303L\317\006\240~j&\025\003\212\205\2054\212\201\305\\SR\251\251\222\246J\235*t\253\tV\022\247J\260\225*\324\213N\024\360)\340S\300\247\201N\305(\024\340)\340S\261O\002\234\026\245H\367\032\227\311\002\232\321q\3050\307\216\325\033% \212\220\307\352*6OJn\332c\n`\0252-HN\0054\363H\006\r1\223\'\232\215\223\025\031AQ2\n\255\"\325YW\345\252\016*\026\031\244)\307\024\230\246\342\232x\240\034\322\366\246\236\234\322\001JG\025\021\025\031\351L+Q:b\253\270\252\357L\353Q\270\251\301\251\226\246SS-N\225a*\302U\204\253\tV\026\244Z\221i\340S\305H*@)\352)\370\243\024\340)\300S\300\247\252\324\251\036MXU\300\247\342\232E4\212\214\2504m\024\233i\245\001\246\030\207j\201\3415\037\226\300\323\324\021\326\220\344\232r\255;\024\240T.\274\324\0141P\260\252\356\265^Q\305Q\2219\250\n\321\212iZ\215\306*#J\253KH\302\231F\354\212k\n\214\n1\326\242q\315V\225j\233\216i\240b\243z\225MJ\246\247J\231*\302T\351VR\254%YJ\231jU\251\001\251\026\236\242\236:T\252*@;\323\326\227\024\340)\301jEBzU\210\340<f\247\010\007\002\234\022\224\2454\212a\024\314R\n\r7\255%\004\003M()\245)\246:M\270\246\232BqL$w\353Q8\025\013\255V\222\2538\315U\221sP4t\2051L+Q8\3153e.\321MaM9\"\230E ZR\271\024\2018\244+\324T2.*\254\235\rTu\346\242#\025\023sOJ\231jt5:T\350*\312\n\260\234U\2048\251\325\205J\257R\253T\200\324\212jE5*\232\221MJ\265 \366\024\242\236\242\245U\315[\202<\014\232\225\260\243\212\022\246\002\224\364\250\030\363Q\223LcM\3154\232L\323wS\201\342\214\321\232ajN\325\031\036\224\326\250\315F\325\013\232\257%VcQ5Bi\244f\230\313Q\224\246\221L\"\230E0\214\322\021H\005=W=\2516\342\232G4\307\213p\342\251I\0363\232\253 \252\354)\205i\026\245SS\245X@j\302\n\262\203\0252\324\352jU5*\232\225MJ\246\245SR\255J\265\"\324\252}j@\376\224\345\253\021\256j\312 \0258\245a\220)@\305.\354P[\212\211\215Fi\246\232i\246\232M0\365\247\003JM74\003\203\315)!\272P1QH3\322\240\"\230\302\241aP8\250\031j\007J\205\201\024\332\017Ja\031\250\330TdTl9\240-!\024\3209\251T`R\021L+\221M?v\252\3123\232\246\351\315B\321\346\242d\250\026\246A\232\261\032\325\244\025:T\313S-J\246\245SR\251\251\226\245Z\231jU\251TT\252)\364\340*E\025n\016\225d\032z\232\225H&\236j2)\246\2434\001M4\302)\270\342\230E4\212i\310\240\236)\205\216i\300\322\346\214\323\032\242jcT-\315B\3035\023\n\211\226\241d\250\312R\025\246\025\2462\346\243\"\231\262\224\255F\303\024*\323\310\307Zn3F*\'\\\016*\273\256E@\361\324-\035D\351Y\351VPU\210\352\302\324\252jUj\221Z\246SR\251\251\224\324\313S\240\251\324T\312\2652\212x\251\000\247\205\251\000\253p\217\226\246\013O\002\234\016\r.\352\\\323MFi\r\'ji\246PE0\323M0\323\010\244\316)CS\263\3055\215Fj&\250\332\243j\215\206j\"*2)\245)\245)\214\225\023%0\245\0331Me\250\331h\013M#\'\232\\\036\324\214\204\014\323\010\312\234\325r1Q\260\317Z\211\226\240u\315e\240\251\320U\204\310\251\224\324\213R\255L\246\245SS\245N\225:\n\235\005XAS\250\251Ui\341jEZ\220\n\225S5f5\300\346\246\006\2349\245a\3050\032\\\322\023L4\207\245(\2468\364\250\311\246\026\246\226\2439\246\236)\206\232E\002\234\r\006\230\334T/Q\036\264\323Q\232k\014\324eh\tF\332c\257\245B\313L+M+\212a\025\031\034\323H\246\323\227\255\0142)\254>^\225Y\205D\302\241qU\333\203Y\310\2652\216\225*\324\253R-L\265*\324\310*\302\n\260\202\254 \253\010\265a\026\254\"\325\210\342-\322\245\3733c4\2331OU\251Pb\254\257LR\201\315H\242\211\016\005C\272\227u4\232J)E-0\250\250\331*2\224\241i\214)\230\240\212LR\205\240\212\215\252&\025\031\024\302)\204SqM+J\005!\024\3223P\262\323\n\342\232EF\313Q\221L\"\232V\2000i\3148\342\230\303+U\330Tl*\t\005Wu\254\325\034T\253R-L\265*\212\231EL\202\247AV\021j\302-YE\253(\265a\026\254*\325\210\320\366\253pr\n\277J\212E\033\270\024\005\247\201R\3069\251v\363OQQK\311\250H9\245\353IFh\315\000\323\201\243\024\230\315!Za\025\033-7m&(\305=S\"\221\226\242e\250\331j&Z\211\205F\302\233K\214\212\000\241\205FE4\212\215\226\243\"\232ED\313L+M\"\220\214P\t9\305\005p\rVa\315F\303\025\013\014\324N\225\222\005=EJ\265*\212\231EL\202\254\"\325\204Z\263\032\325\224J\262\211V\021jtZ\2361\310\315\\\014\252\240/Z\025\210<Q\214\320F)sO\214\363V\200\340\032x\030\025\013/4\322\242\230V\232E!\244\242\200i\342\236\006h\333\315!L\324n\225\036\312iZLsR\240\244aP\260\250\210\250\310\250\312S\031*\"\224\230\305\000\320Ni\244SJ\323\010\250\330S\010\250\310\2462\323\010\244\333\232ENi_\345\004Ub*\026\246c\232\032<\212\300\006\244Q\232\225EL\202\247AS\240\253(*\302\n\263\032\325\250\326\254\242\324\352*U\0252T\242\244\024\374\322\023\232J\2325\357V\024\361O\3154\256i\245i\204S\010\246R\021\2121\305 \034\324\2128\247S\363\221F)\031A\250\331*2\224\302\264\345\024\244TL*\026\024\322(\331\232C\021#\245E$[G5Y\206)\270\315\030\244#4b\243aQ\232a\024\322)\2148\246\025\246\221GN\225\013\222MD\302\243+M\013\3159\206\005sj*U\0252\212\231EL\202\247AVPU\230\305Z\214U\250\326\254\242\324\312\264\361R\251\247\203O\006\235\272\2234\344<\325\264\344\014T\341x\245\333N\333Me\250\210\2460\250\332\243&\223u\000\346\236\r<\032}8R\342\223m#%DR\200\230\246\260\250\331j&\024\300\265,i\223S\004\030\306*\033\230\376Z\314t\346\243\306(\245\333I\214Tl*6Zf)\244S\010\246\221M)\221Q\343\002\243aQ\260\250\312\321\216i\262g\025\316\250\251Uje\0252\212\231V\247AVPU\230\305[\214U\250\305YQR\n3O\006\236\r<S\351qOU\253p\203\212\264\2434\340\224\3420*6\025\021\025\023\n\214\212c%0\255\030\305(4\365\251\001\247\346\234\r8\n\220 \"\243h\361L)Q2\324n1P\232@*P=)\335\2529\016\340A\2522\2475]\3053\024\365\031\244e\250\210\250\310\246\221I\267\"\230\313L\305\0140*\006\246\021Q\260\246\204\'\240\245\331\216\265\004\247\002\260UjUZ\225V\246U\251\325jdZ\262\213VcZ\265\030\253IS)\247R\212z\212\221EH\005<\n\221V\246T\251\223\345\251C`\325\205pE\014)\245i\214\265\013-D\302\233\212B\264\322\264\3221FiC\322\371\225*6je5 jBsHF*\'\"\240a\232\211\226\243\350jT4\346\2467J\255(\252\222\n\212\244Z\030TL\265\031\024\303I\315!\024\322\264\306\034T\004S\010\246\225\245\215y\311\244\223\212\245)\306k\031V\246U\251UjeZ\231\026\247E\253\010\265e\026\254\245N\265*\323\300\247\205\247\201R-H\242\244QS\242\324\341qMn\r9[5*f\247\316E(\244a\221Q2f\241d\3057m\033i\245j\027Z\214\323y\240\023R+\021R\254\206\245\022R\031h2f\230Ni\206\230\302\230V\234\274S\310\342\242j\201\352\273\214\324%piV\202i\206\230\302\243\"\223\024b\232\303\212\215\272T$R\005\243\313\356hn\007\025VJ\25175\230\253R\252\324\312\2652-N\213S\"\324\350*\302\n\235EL\202\247QR\205\251\002\323\302S\202\323\300\251TT\351\305L9\244d&\205\201\210\310\025*#\016\2650\024\243\203A\2445\023.i6Q\345\323\031*\026CP\262Rm\246\221\3158-/AFx\244\0074\271\245\245\353\326\202\264\322\264\233i\370\342\241a\315B\300TEj\007^j3M&\233\232\t\246\342\232E&)\2568\250H\246\021@\024\343\322\243qU\234UIEg\252\324\252\2652\255N\253S*\324\352\2252%N\253S\242\324\312\265:-L\253R\252\323\302\322\343\332\200*U\025*\212\261\027$U\305\010\247\347\0314\327\230.Dc\203O\211\303\2140\0242m84\321\212\221c\315+[\236\324\337 \322\371\036\324\206,Tm\035@\311P\264u\013Fi\233M\024\322i\t\244\315(jx4\341J)i\244\ni<TOQ0\250\332\243e\252\3561Q\323H\240f\214\342\221\2104\001L\177AQc4\322\224m\305\014*&\025\003\214\325i\022\263\324f\246U\251\225*tJ\235\022\247T\251\325*eJ\231\022\254\"T\312\225(JxZp\024\355\264\241jEZ\224-H\274T\200\223R\004\006\244H\361Sc\214\036}\352&C\236*xP\236\247\025q\023\324\212\220\306;\n\215\242\250Z:\211\343\250Z:\211\243\250\031=\252&\2175\023\307\212\205\226\230E&)@\247\250\251\002\323\200\3055\251\271\244\250\332\243aQ\221\3155\207\025\004\211\232\204\256)\207\2321M\"\224-+\014\n\204\212n\332wjk\nc\n\211\206j&\025^E\254\364J\235\026\247D\251\321*tZ\235\022\247D\251\321*uJ\235\022\247T\251\002g\245)\214\257ZP\264\270\245\002\245QR*\324\201i\301i\342\245V\305<\267\245\033\260*H\334\032\2345H\256A\342\247\014\030r)\214\202\241h\352&J\205\343\252\354\224\302\225\023\307U\335*\022\264\335\264\340\265\"\2558\322c4\322\270\250\317\024\335\324\204\346\232V\242e\3050\323\030f\242t\364\250J\320E7\024\360\000\246\260\334*=\264\355\235\351\254)\245j&\025\023\212\205\215B\346\251*T\350\265a\022\247T\251\321*\302%N\211V\021*tJ\231R\245\tR \301\342\234\371f\344Rb\223m(Z\225EL\213\232\235c\315N\220\257\361\032kE\203\305FW\024\240\361Mf8\250\304\205\032\255G85:\316\007z\221nTT\242ezL\212i\025\033.j&J\215\243\025\013GU\244J\252\353\212\217\024\341\232\224\003F(4\306\250\332\243\"\201\326\244\n\rG$uY\306*2\336\264\322\300\212\205\210\246f\216\364\356\270\024\366A\267\212lj6\234\323\037\212\214\323I\246\032\205\305V\220Uv8\250\343Z\260\211V\021*\302%N\211V\021*\302GS\242T\312\2252\245J\022\236\251\203\234T\205\001\034\365\246,T\246:M\264\340\2652\014T\352jU\004\364\247b\232S4y&\230\321b\243h\201\246\204\333FqNQ\223\315Y\215\200\351S\357\243}\005\263M&\230\303=\252\026\025\004\213\236\325\t\267\334y\240Z\001A\267\307Ji\217\024\335\224\205*2\264\306J\205\2050\361M/\216E\006\347\214\032\247<\343\265Q{\203\353Q\371\344\3654\276a=\352T9\251vq@\0304\374\346\225z\232c\255B\303\025\031\250\311\250\335\252\274\225U\372\323\343Z\262\213\232\260\211V\021*\312%XD\253\010\2252%N\251R\252T\312\224\361\035/\227JW\035)\273sF\312P\230\247\001\212\220T\310\330\251G\315\322\230\310A\245\010\330\344\322\265\273c \346\243\020\261\353R\01029\024\306\266\3054B\001\346\244X\300\350jQ\037\024\306LS1\351F\342(\363=\251\013\2554\225\365\244\033OJB\0054\201Q\262f\231\262\232\313Q\354\315C7\313\300\252\304\346\243~*\244\262b\251\311>*\263;9\342\230\310\330\252\316\305\r\t6MZ\216J\271\034\231\024\245\250\006\244\003\220hqP0\250\230T\rQ1\250\034\325g\025b1V\343Z\262\211VQ*\312%XD\253\010\225:%L\251S*T\213\036j@\224\375\224\236^iD4\030\351\276]\'\227J#5\"\241\025*pjl\202*\t\t\3155e`q\232\23599\315XQK\260\032cB\rG\345c\245(CC\naQ\351Q\262S\n\232aZaZiZL\021J3\336\227\024\230\246\262g\2655W\373\302\251\\\217\234\342\252\234\324o\310\254\371\301\346\251\030\231\317\025n\336\323\035EZ6c\035+.\356\307\004\220+5\241dj\221\033\025f)*\312\266i\300\346\254 \312\212V\217#5\013&*)\026\252H*\273T\rP\275[\214U\270\305[\215j\324kV\221*\302%XD\251\321*eJ\231R\244\tR\004\247\204\247\010\363N1\2208\024\303\037\255\001\005#(\355H\006(\357J)\313\326\207L\324b0O&\254\306\212\243\203\223R\201N4\337\251\240\342\231\232Ni\254*\")3\232FL\364\250\331qL\"\233\212\220 \3051\227\024\231\2434\202\241\232\000\340\234sY\322&\302EU\220\342\241\333\270T\220\300\255\332\256\307\000\003\201S\030A\035*\254\366\201\201\342\261/l\366\202@\252?g;rE5\020\206\255(-L\213\220y\241\340h\31755\271\347\006\247t#\351P2\324\022-T\221j\254\202\253?Z\201\315_\214U\270\326\255\306*\344B\255F=*\324kV\021j\302%N\251R\252T\201)\352\231\251U)\341y\241\200\355Le\246\021\212n)6f\202\270\246\322n\"\233\274\232o9\251\341<\363V\326\224\212L\n\215\200=)\002\232\017\024\322\302\230j2)\271\"\232Nz\323)\010\346\246P1Mq\223Q\342\223m.\312\n\341O\025\233p\233\311\300\252o\006:\324\"\"3V \214\203W\221x\247\221Q\262\346\252Ml$\353U\215\222\200F*\224\226![\345\025b\010\031\007\024\351>a\206\034\325`\233_\"\254\261$sP=@\342\253H\265NQU$\030\315U\220\326\254kV\343\025j1V\343\025j1V\243\253Q\212\262\202\254\"\346\246U\251U*UJ\220 \240\2554\2554\2550\2550\256\r7\034\321\203M\"\230V\223e(\2175,i\203V@\247m\3155\206*<sA\351\311\250\330\366\024\334f\215\206\215\206\230\313P\225\246\221I\212\221\006N)XsM\333N\tK\267\035i\030\006\025VH\324sT\245\000\232j\302\032\245\020\340\323\300\305.(\3050\256j3\0350\302\t\311\024\024\000t\252\362\301\225$U=\234\363O\"\242a\305WqP8\252\262\255P\230U9\005l\306*\324b\255F*\334b\255F*\312\n\264\225e*\302\n\260\202\247QR\005\247\001A\024\204Sv\323v\323YsL)I\262\220\245FiUsR\025\013J\204T\200\212]\300R3\203Q4\212:TM \365\246\207\006\234$\002\227\315\364\244\336M!\031\246\225\024\323\036j2\244R\253m4\343\311\315(\245\310\025\013K\223@\220c\025^c\232\250\312I\253\020\307\305LS\212a\\\016)\204RSM\024\322)\244TdsPK\010\373\302\241)\305@\353P2\324.\265ZE\310\252\023%R\221+^1V\243\025n1V\243\025f1V\220U\204\253\021\212\264\202\254\240\251\324T\240R\342\214R\021I\266\220\2554\214Q\2674\322\224\306\035\251\236^i\215\307J\211\331\2155d\"\236\327\001G\0315\013]\023\320SE\303w\244iKv\241\010\357R\0021F\352PsOZ\220\014\323YiB\346\225\320\001\223U\037\031\371i\013\343\251\246\231\207\255Ff\31579\247\n\n\347\265\036Ni\352\233zS\261H@\250\230\n\215\2522Nx\244\335FsHi\206\232j7@j\264\221\343\245Wt\250\035}j\264\211T\345\216\251\274y\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\004\255IDATx^\355\335\321\226\2338\014\000\320\036\372\377\237\2749{\232m\272\035H\006BlcI\367>2\t\030ld\311$\231\037?xfYo\000\000\000`\210\217\353\261\345\343=\300\264n\353\rl\010\001\000\300\347E\005\000\000\000\031<\312Ck\353\000\000\000\323\363\204\017\020\t\3302&h\303H\342\210\233g\t\000\000\300d\324\263W\370\2738\324\003\000\000\000@0\341\2273\302\237\000\000\000\000\260\021\355\307\342\2075\3277\030\000\000\200\234\326eU\344\352g}.\ru\334\365\305\362\236\031\000\000\000\007(\013y)\362\"\021p\206)\201\277\231\005\240\004\241\237\223\226\237\353-\320U\343h\325xw\034\"\273\004\000\000`\237\352\221.\014,\200\227\254\227\003\000@1\212\000\000\000\n\363\330\020\000\200\272,\016\003p\221\321\0132O\247\274\345K;n\267\321\215\002.\36642\000\000P\213J\020\000\306\2122\367Fi\'=X6\204h\304l\310\313\375\235\314\3414\353\360\013\001`\2079\005\340MY\002\347\222\345D  \367\037\000\000@2\236\333G3\2602\367\273\014\274\305x\201\310\334\301\000\014a\302Ii`\225JSz\016\000\200\246$\230\305\031\000\000\025\025X\360Mv\212M\347\353\246;\003\372s\323\206\240\233\000\n\032Tt\014:\014\2632\000\000\000\000\240\252\343\217\036\254\037\000\000@\025\235\263\377\316\273\347s\307+\305b:]\230N\273\2459=\005\000@8\007+p\271nZ;\377;\360\373\277\002\344\'\016\262og2\005 \253I\342\377\301\262~>a\033\036\322m\226\361\n\220B\2429\314\364\000@\017\346\027\222J\224\005\002\000\345\311l\000\302\020\262y\305\032\034\000\000\364%\347&\224\027\013\010}\307\361\355\353Q_\264\001\250\304\267l+\373\325\371\006\000\000\344\243\324\203\000$\342\000\360\006k\230\000\033\277\213\177\0012\000\235\004\225\211\000\000\000\344\341\203\010\331\254z\364Q\276\334\326\3376\340\177\313\372\252Q\315\310\001`I\001\000\270\222\\\204\211\030\216\343\214,y\016\233\262Q\326N\200\234\3046\000\210\310\014\376\233\013\221\307\234KAs\266\n\262\021\313\001\000\230\205\334t\270 \227<H3\001\000\000\346\263}\340\252\304\002\250\314\017\214\027\267\374\\o\001\000\312\220\010\002\000\000q\251h\342+\327\207\345Nx\207\353\001@j&:\200Wr\177b9\367\331\3255\240_\007\034\3423\235s\233\315\3567\033\022\230\276\223G\312\330\301\234g<\000\000$%\321#\261k\213\374k\217~R\310FS\324}\3762\211\001\300\365\244\220\314l|\2768\376\210@\024\342\003\000\000\314\302z\026\000\000\000@\024Vr\000\000\000\000\000\000\000\340n\211\366]\345h\355\005\000>g\376\207\366\334WD\341s\337\245\351\376\016\"\305\177\003\240\270\267\007@\244\321\3153\341\026\252\001\242y{n\005\030\357v}\254\272\276\005\000\000\000\000\000@X>\376\002P\234\'\316\347\334\\:\000h\313\314\n\000\205\230\370\001\000\200\231\004\371\350\214R\252\270\t\276\316\016\000\000\000\300\033,\347\0009\210f$\026\344!!\000\320\310\005\037\274\270\340\220\034!\017\344\356\327\r\372\317z#\005\374\t\315\213E\017(h\221\007@I\2339\177\263!\006\021\254\021\027\222\347\202F\006\336%\004\024g\000\000\000\360\214<\021\000\200\324<\002I*Q\307\216/\312\336:\342[/&\212D7\020\224&DCY\207\277\010\262\010\024I\035\034\000\000u\325\230\001\217\235\345\261W]\250m\276b\216\004&\3264\336\001\334\265M\245\276!\313\232\323\260\001\300\\\222\335\220\311N\347\205\0067\353%\027\252\347\017\0076\270&\000\325\010\235\000\274\3201q\'\262O\006\306\355\243w\037\361\310ld8g=z\250wO}\357K\377\035\353\314c\257b\353\332\256\006h\313l\000@\006r\364}\3679\337\205:)\370\205\013\336|`\000q\002\000\370\236\222\362S\255\027\342?\351\214\263\357=\373>\272h:\244\364mu{\303\311\010\231\220N\001\372\370oJ\330\233\030\200\224\"\244\027O\303S\204\206\267\223\366l\307\235\330\270#\r\326\363\267\007\000\000\000\370c\306\352\353\374\217^.\347\337\032\315\214\035\007\227+\023\001\000\000\330\220\013\326\266\314\264 0OKz\362<\037\000\340b\023\344c\0234\001\362\333/\276j\024\241;\016\\\204\307K\376\005s\343\23463\025f\327\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-3.textpb b/core/res/geoid_height_map_assets/tile-3.textpb
new file mode 100644
index 0000000..486adf4
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-3.textpb
@@ -0,0 +1,3 @@
+tile_key: "3"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364/\245.\352pz\225d\247\356\3151\316j65\0314\302i\245\251\244\323sFh\315&h\315\004\323I\246\223M\335HZ\232M4\232ija4\302i\204\324d\324d\323\t\250\311\246\023Q\223Q\263TL\325\0235D\315Q3T,\325\0135D\315Q\263T,\325\0235D\315Q\263Te\251\205\251\244\323I\244\315&h\315\031\242\2274\240\323\251\300\323\201\247\003R\242\223Vb\212\264!\213\025\353E\251\245\250\017O\017R\007\247\026\030\037Zk\032\214\232\215\215FM74\233\2513K\232L\321\2323M&\232Z\230Z\233\272\220\2654\232ijijaj\215\215FZ\230Z\243&\243-L-Q\263TL\325\0235D\315Q3T,\325\0235B\315Q3TL\325\0235D\315Q\263TE\251\205\251\204\323I\244\315&i3Fh\315.isJ\r;4\340i\302\245E\315[\215*\364H\000\253I\355^\231\276\223}\033\351\341\351\341\351\301\363O\334)\215Q1\250\211\244\315&h\315\031\2434f\220\232ijajaji4\335\324\026\246\026\246\026\246\026\246\026\250\331\2522\324\302\325\0335F\315Q\263TL\325\0235D\315P\263TL\325\0135D\315P\263TL\325\0335D\315Q\263Td\323\t\246\223M&\2234\204\322f\214\322\356\2434\240\322\203O\024\361R\242\022j\334q\364\253\221\307\264d\325\204\253q-z\000z7\322\356\245\017N\017OW\251\003\346\235\2735\033TML\315\031\2434f\214\323sHZ\230M0\2654\2650\265!j7S\031\2522\325\031jaj\214\265F\315L/Q\227\250\331\252&z\215\236\242g\250Y\352&j\211\232\241f\250Y\252&j\211\232\243f\250\213S\013S\t\246\223M&\232M7u!jM\324\233\251A4\360\rH\020\323\304u\"\305S\254F\254\305\016:\325\250\323\025co\002\245\215*\344K\212\355\003\346\215\324o\305(\222\234\036\234\036\244Y*P\371\240\267\025\0214\302i7R\346\214\322\023M&\232Z\230Z\230Z\230Z\232Z\223u\000\323\034\324L\325\0335FZ\243/Lf\250\231\2526z\215\236\242g\250\331\252&z\211\236\242g\250Y\252&j\205\232\242f\250\231\2526j\214\2654\2650\2650\2754\2653u\031\247\005&\234#8\351J#5\"\307\355R\244C\275N\261\217J\231b\036\225*\305R\004\002\244QSF9\253\210\231\253\021\307V\022:\350\326Jxz\013R\007\247\007\247o\251\025\352U\222\244\017\221\3150\236)\204\323wR\206\245\335HZ\232Z\230M0\2650\232ajijM\324\233\261HZ\240s\212\211\232\243-Q\263Te\2526j\211\232\243g\250\231\3526z\211\236\242g\250\231\352\026z\211\232\242f\250\331\252&j\214\265F_\024\302\365\031~i\013f\232h\000\232\235\"\365\253\t\037\240\251<\272M\230\355O\013OU\315J\253\212\235G\025(\034RT\252*\304K\315^\215j\312%YE\2550\324\360\324\355\324\205\2517\323\303\324\212\365 zx\222\234\030\021McL-\315\033\250\335HZ\220\2650\2654\2650\2650\2654\2654\265!jaj\215\232\240f\305F\315Q\226\250\331\252&z\215\232\242f\250\331\252&j\215\232\242f\250Y\252&j\211\232\243f\250\231\2526lT,\377\000\205F\\w\246\347=)*EJR\242\204Q\232\262\007\313V\020p)\370\243m(Jz\256\rJ\027=*@\265 \024\273y\251\221j\314C\025v<U\250\326\254*\325\260\330\245\337K\276\221\244\246\357\247\253\324\241\351\341\351\302Jz\311N\337\236\264\306<\323wQ\272\215\324\322\324\322\324\322\324\302\324\322\324\302\324\322\324\322\324\302\365\0335B\315Q3\324l\325\031j\211\232\243f\250\231\2526z\211\236\242g\250\231\252&j\211\232\243-Q\263TL\330\250X\236\246\242f\246S\200\245\035j`@\024\335\324/Z\234\036\225j#\225\251@\247\201O\013N\333O\214`\324\333)\300S\202\232\231\026\254\"\036\325b4j\271\0225[D\251G4\326\342\243/Az@\364\340\365\"\311O\017K\346S\326J\225d\343\024\026\244&\233\272\215\324\205\251\013S\013SKS\013S\013SKS\013Tl\365\033=D\317Q3\324e\3522\324\306j\205\232\242f\250\231\3526z\211\232\242f\250\231\2526j\214\2651\232\241rs\234\324li\206\223\034R\321N\240T\2129\253\n\234f\255B\277(\251\302\323\302\323\202\324\252\225(\216\236\026\244T\251\026*\263\025\271n\325r8\002\216je\n;T\311\364\253Q\256j0})\254j\00684\335\324n\245\335N\rR\007\247n\245\337OY)\341\363\212v\352Bi7Rn\246\226\246\226\246\026\246\226\246\026\246\226\250\331\352&z\211\236\243g\250\331\352&z\214\276)\245\352\'j\205\232\242f\250\231\2526j\215\232\230A=x\250\233m0\342\230Xv\024\302F*&\344\323H\243\030\242\212QR(\251Q7\036*\340N\000\253\010\230\002\246U\251\002\324\212\225*\245J\022\236#\251\243\2135j8=j\342\"\250\342\2022i\311\035Y\215*\324Q\346\250\253qC6*\t\rE\272\200\324\340iA\247\206\247\006\243u(|S\325\352@\371\247o\365\246\356\315!8\246\226\246\026\246\226\246\026\246\026\246\027\250\331\352&z\205\236\243g\250\231\3526z\215\236\233\277\024\326z\205\232\241f\367\250\213{\323I\035\3114\302\303\265F\315Q\026\246\223Q\261\246\3474\206\220\2121I\2121N\002\245E\315[\202*\274\261`\n\225R\244\013\212z\256juJ\220-J\251R\252U\204\001G\275N\274\324\240b\225FMX\2162{U\350\255\275j\354p\001\332\271\250\344\342\234NEA#qP\226\240=<58\032pjvh\335F\352]\330\247\253\323\374\312R\340\323Kb\232Z\232Z\230Z\230Z\230^\242g\250\231\352&z\211\236\2432TL\365\031zc=3}1\244\250Z^j6\222\241g\246\357\365\246\226\246\026\246\026\240\364\250\311\244\315-\006\233E\030\247\250\253Q%^\211qV\224t\251\200\245\013\223\305N\211\212\231S5 \216\244T\253\010\234T\211\036MXX\352d\2075a QV\243\214/AV\343Bj\322%p\361\276j\300<T,x5Y\233\006\2205<=H\032\227u;u\033\251CR\356\245\335K\272\215\324y\224\205\275)\205\251\245\3526z\215\236\241g\250\231\352&z\210\275F\317Q\227\250\331\3522\364\322\331\250\331\261P\227\250\231\371\246\026\246\227\246\027\246\227\244\335Jd\342\233\232)\331\242\214R\221@\031\251\243J\273\nU\245\\T\350*`8\247\242\022j\334QU\205JxJ\221c\251\3250*x\343\346\254\254u:ES\244&\255G\020\035j\302(\035*\312-y\324G\275YS\221Ls\214\325)\033\006\232\032\234\032\236\257R\256[\245\004\225\353F\374\322\206\245\rK\272\227u\033\251\013\323K\323K\323\031\352&z\215\244\250\231\352\026z\211\244\250\331\3522\365\031z\215\236\243/F\372\211\3335\013=F\315\3150\2650\2654\2654\265&\352]\324\341\203E8\032x\245\245\0035 LT\211\326\257CVUsV#J\262\261\324\311\035N\243\0252.j\312G\232\220%H\251V#\216\255\"U\204CV\0251R*\223V#\216\254\252W\230F\370\253\010\364\216sT\346\353\232\213u(jpz\265o2\251\371\205\027\022\2536W\212\204?\255H\036\227w\245\001\350\337AzizB\364\306z\215\244\250\232J\215\244\250ZJ\211\244\250\232J\214\311Q\263\324e\3526\222\230^\200\364\307z\201\236\230Z\230Z\232Z\232Z\2234\231\2405<58\032x\247\216i\336\302\246ARb\205\034\325\270Z\257C\315\\\215j\312\255<\014T\321\256j\334q\364\253J\234S\325*t\216\254$uj8\352\312GR\210\352DJ\260\211V\021+\307\204\236\2254sv\251w\346\240\224\344Um\324n\247\006\245\337H\\\232\262>h2G#\275F\262T\233\3517\373\322\356\244\337H^\230^\243g\250\232J\211\236\242i*&\222\242i*#%F\322TfJa\222\243/M/F\372c\265D\317Q\226\246\226\244\335F\352L\323I\240\032x5 5\"\342\247T\317\265.0jt\034S\261OU\251\343\\U\330[\025~#\320\325\201\315J\213\232\271\014|U\330\243\342\247\tR\244y\253\t\035X\216*\260\221\340\325\224J\224%H\211S\204\251\343Z\360\245\233wCS$\2305i\037\"\232\346\2539\301\246\346\227u;u&\354\032\235g%v\346\231\300\357K\276\215\343\265;x\355I\276\232^\2432Tm%B\322TM%B\322TM%D\322Tm%F\322Tm%Fd\2442SK\320\036\232\357\305D^\233\232ijM\324f\2279\244&\220\032z\232\225ML\206\254\253qJ\0075*\n\231V\245T\253\010\265e\022\255\302z\003WQ3\322\255D\225r$\253h*tL\325\204\216\254\244uj8\352c\037\024\344\030\251\325jdZ\231R\247H\353\347tl\032\260\222f\254\306\346\245c\300\250d\342\242\335\2127S\203PZ\233\272\245^A\244\335I\276\223\314\305\036e4\311Q\264\225\023IP\264\225\013KQ4\225\023IQ\231*&\222\243i*6\222\230d\246\231i<\332x|\323Y\252-\324o\2434\204\321\232\\\321\326\201\326\236*U\251\226\254%J\242\246QS\240\253\n\2654iV\321x\251\222>j\334y\025v\023\232\277\032f\255G\035\\\216.\225e#\305Y\216<\325\225\217\024\375\264\252\225:\245N\211V\022:\235c\257\232\310*y\247\253`\325\330OCS\261\252\362\265@Z\215\324\340\364n\246\227\251c\220r3H\347\322\2432SL\224\236e4\311Q\264\225\013KP\264\225\013KP\264\264\303%F\322TfJ\215\244\250\332J\214\311I\346f\232_\024\364\222\236\355\306j\035\364n\245\017N\316i3\212p4\361N\003\232\221EH\242\246AS\240\251\320T\350\265a\022\254\306\265a\022\255\242\000\265:\307\306sR\242\342\255@\270<\326\244\003\201Z\021%ZD\253\010\225f5\253\n\264\375\224\365\216\254,ub8\252\312GR\004\257\230\244}\346\233\320\212\271\031\332\005M\346qU\344\223&\242-I\276\224=.\372ij\217~\rX\337\362\324\014\364\303%!\222\232d\250\232J\201\344\250^Z\201\245\250\232Oza\226\232d\367\250\332J\215\244\250\332J\214\311M2R\371\231\247#\324\305\362\265\016\356iCR\251\247\251\301\247\236FE*sR\250\346\244QR\252\361OU\315N\213S\242\324\350\265f4\253H\225:%Z\215*\312\002F\000\251\322?Z\260\221{U\250\343\253\360\'J\321\204c\025q\026\254\"\324\352\2652\212\231W&\246\t\322\254G\035YD\251\325i\341y\257\224\267R\253d\365\251\326\340\214\017J\223\317\310\250\231\362i\273\251\013R\207\243}4\2751\236\244Ir\234\324L\365\031\222\232d\246\264\225\013IP\274\225\003\311U\332J\214\311L2SL\264\303%4\311L/Q\226\246\356\247\003\232\221x\247\227\342\242\r\363S\263\203OV\251\001\315J\207<\032z\256\rN\026\244QS*\324\310\225:\307S\"U\250\343\315Z\216:\266\221q\232\231#\253QGV\322!\216\225:GV\022*\263\034uz\010\375\252\342GW#\\\212\265\032T\252\2652GV\021*UNj\332\'\0252-L\027\212r\212\371\'u\001\261N\rO\017\212\013qF\352M\336\264n\244-M/Q\263\346\204~\324\326|TfJa\222\230\322T-%B\362Uw\222\240i*&\226\243ii\276m\'\231M2S|\312\013Te\371\247\254\270\251<\352x|\212f~j\223w\024\34452\032\225\0175eFj\302\n\221V\246E\253q&j\312\307\355R\244Uj8\261\212\267\034Uv8\270\305J\260\373U\230\341\366\253+\037\250\253\021\307VV:\2364\253\221\014U\224>\225n,U\225\"\247J\262\213S\252T\250\234\325\224\025:\212\230\nP\265\361\376\374u\245-\273\221\320Q\346d\323\267r*U`x\244\'\232i4\233\251\013S\031\2522\324\253\300\3156F\342\2533\342\2432\323\032J\201\344\250ZJ\205\344\252\355%D\322TM%0\311I\346\322\371\224\236e(zij\025\351\333\261S\304\324\245\271\251\024\344S\324\342\246CV\"\003<\325\330\370\351S\252\324\312\275*dNj\364)\322\255\"U\244\2078\342\247X\261V\243N\225n5\2531\240\253J\243\002\235\220\rH\216;U\204b{T\350O\245Y\2103\032\273\032b\244\335\264\342\244B\331\310\346\255G&:\365\253qL*\332J*eqS\243\n\260\246\246S\221R(\257\215\213z\232\2267\302\034TA\260\334\324\241\263\315(~})\305\275\r7y?J\013SKTl\324\300\334\323\214\230\353PI(#\212\254\362T-%1\244\315B\362T\r%D\362Ui$\250ZZ\215\244\246\031)<\312Q%\033\351D\224\375\331\024\014\346\236\017\025,MR1\311\342\244\211\275je\301\251\220b\255F*\334~\365m{T\350\275*\334q\216*\324k\212\271\032\325\350\0235d\303\200\r*\340T\310\330\251\321\316zU\224\311\251\304\031\031<\324\251\t\354*\322[\271\253p\3321#5\261ib\253\202ji\342\000|\242\250\264M\273\245^\266Q\2140\253&\327w+Q\230Y)\213#\206\305\\\216V\342\255\307!\305[\212L\342\256\306r*\302-|`i\276a^\224\007,j`\330\024\273\263C\034\n\013`b\233\272\220\267\025\03357uG#\324\016\365]\336\241i*&\222\241y*\026\222\242g\252\362IU\332J\214\311L2Ry\224\242Jv\372pzz\275J\0374\360jD\251~\265,uaz\325\204\031\2531\n\270\203\000U\270\327\"\255\"t\253\221-\\H\363V\342LU\310\260\265)\223#\002\234\210I\346\255\305\026x\002\257Ed\314>\355\\\207On7V\214V\034c\025n\033\000:\212\273\035\242\000\t\351S\013d\316EXED\357R3G\214\001\232A\0227U\251R$\037\303R\205\031\371F\005+\303\221\3275\017\331\207a\315*\246\323\310\247\026\njX\344\346\264\255\344\r\212\320N\225\361a4\323@\342\236\0374\340\3243d\nB\334\234\323Kb\233\273\212a99\246;\342\253\273\324,\365\003\275@\317Q3\324N\374Uf\223\232a|\212\255#\325v\222\2432S\013\323|\312z\311O\022S\203\324\213%J\257R\253T\361\265MR\306j\312u\2531\212\267\020\315[J\273\n\360*\364i\234U\270\322\256D\265eH\025&\342G\025=\274e\316\024d\326\315\256\230\317\202\303\025\255\005\212G\216+A!T\034\212\231B\2020*\30279\002\246POJ\225b=\352e\2074\357\263\234\325\224\266P2y\251\004c\037(\247*T\241)|\2726b\221\220\021\315f]\023\033c\265Ij\306LV\244A\243\301\355Z\2606\345\025\361ni\271\2434\241\251CR\223J\314\n\202z\324d\344\322c\202)\214\330\252\356\365\003\265@\317P;\324\014\365\013=F\317\305U\221\351\202L\212\206V\305Tg\250\214\224\205\351\273\351\301\352Epj@\342\236\257S\253T\310j\314|\325\220*D\0305f>\265r1\305Z\214U\310\3278\253\361/\025\241\n\360*\352\'\002\246^*tN\346\255End<V\355\205\232FA\3075\265\033\0000\005J\2715aA=jdJ\260\211VcJ\260\251S\242T\253\036\343Rm\344\212\220F})\351\027s\326\237\260R\204\240\306)\214\225\233{\016\3420)\326p\354\353Z\341r\200b\255Z\251\003\006\276+\315&h\316h\315(4\273\251r\017\007\2458\000\0055\333\002\252\310\371\252\356\325\003\265@\355U\335\252\006j\205\236\242g\252\322=B$\301\250\345\220\232\246\317\311\250\313\322o\246\027\245\022T\210\365.\342*Dj\265\033U\2045f#V\321\252d\253q\n\271\030\351\212\265\030\253\360\247\002\257\302\274U\370S\201V\301\300\251\341M\307&\256,E\210\013[6V\273PdV\254H\007J\264\213VQj\312-XE\253\010\225j4\251\325*A\307\025n\025\030\247\010\306\356*`\224\316\344\nz\212~\007\343HV\223h\006\240xC\222H\2428\300l\n\323\212\037\224f\247\216,\034\327\304\006\2234\231\245\315.h&\223u(\227\002\242\222M\325\0035@\355P;Ugj\201\332\241f\250\035\252\026j\255#Ugza\223\212\255#|\306\242-H^\232^\227p\251cz\234\020E=MY\211\252\322U\250\252\352\016*t\025n!\322\257B=j\354KW\241Z\320\201j\364`\001R\250\334\303\025\243\004Y\300\025\265kl\240\002\302\264c\003\265ZAVc\025f1VPU\230\326\255F\265j5\315YU\240\307\316j\334\021\222\265<Q\374\307=\251\345qP\221\226\342\244\002\245*\252\276\264\305 \212B\200\362M5\306@\002\237\005\271$\034V\200\033p*@k\341\243L&\2234f\224\032Bi7SKTL\325\0235B\355U\235\252\273\265@\355P1\250Y\252\027j\253#Ugj\210\265A+r*\"\324\302\364\205\351C\324\310j\322\221\266\234\244\346\254\304\325r3V\342\355Wc5j1V\342\034\212\277\020\253\321\014\325\370V\257D0EX\317LU\313u\307&\266,\"\334A5\260\270\030\002\254\305V\322\254\245Z\214U\230\326\255\306*\314b\254\3060EZAO8\034U\373|2b\235\267nsQHK\034\nhV\317J\231P\234qJ\352FEV(\301\370\351V\022\022\335j\302\333\201\326\254$x\245e4\252\276\265\360\333\n\204\365\246\223FisHM&i\244\324Lj\0265\013\232\254\346\253\271\252\356\325\013\265B\306\240\221\252\244\215U\235\252=\325\024\255\305BZ\243&\232Z\205j\261\033\325\205z\231\0335j#V\3428\253\321\034\342\256F*\324uv*\320\206\257\302:U\370G\"\255\216*\335\272n\344\325\330\027{\214t\255\353e\021\240\035\352\342\032\267\035[\216\254\307W\"\253q\212\267\032\325\230\326\254\306\274\325\2001A^sWm\270\305Y\312\263`\323\035\0009\035*\274\227+\031\250N\247\351\201B^\031\\\016\265\251\034a\324\020*\302\305\216\225*\246jU\2175(\204w\250\335\0247\025\360\223\032\205\2523Gj3A4\334\323I\250\232\241cP\271\252\356j\263\232\201\315@\346\241cU\244j\252\355U\2445\036j)\017\025\016x\246\023M&\224\032\225\rYZ\2363V\343\253\221\325\350j\364F\255\307\315\\\213\265_\207\265h\302:U\370\370\305N\2373\001Zp!\332\005hZ\307\206\025\250\215V\3429\253\221U\270\315Z\216\256EWb\355V\3435n>j\302\2561S\016i\315\320T\320\266*u9<\365\245\221\266\216\265\227:\231\033\212\317\2226F\255]&\r\356\t\256\215#\n8\247\242\234\373T\333x\244I6\2674\351&\335\302\324Y\311\346\276\024\"\241j\211\250\006\202qHM!4\302i\214x\250\036\253\271\250\036\240z\256\365]\352\007<UY\rU\220\325w4\300y\346\242\220\365\250\r0\232J*D5j6\342\246F\346\256D\325z#\232\277\r]\205sWc\\U\310\205_\204c\025\241\025]V\033j\335\252\344\346\265`\255\030\260\243\212\267\031\315\\\214\325\310\232\256Fj\334f\256Dj\344f\256EWb\346\255\240\315J\027\024\377\000+&\225>Y\000\253\312\200\214\212I-\367P\266H{U;\273\020\275\005K\246\257\226\370\255\325PE;\201F\340i\n\212B*&\310\351_\016:b\2538\305@\324\212y\247\036\264\323M\3150\232\215\215D\365]\352\026\252\357U\336\253\275@\346\252Jj\244\206\253\261\246u\250\330\324,j2iz\nAOZ\231\032\255E\315^\210U\330x\255\010{V\204#\030\253\2503V\343\030\025r#\216\265r)*\354J\315\327\245_\205\266\340V\255\250\316\rh!\305Z\210\325\310\215]\214\325\250\315\\\215\252\344MW#j\271\023U\330\232\257Ds\212\2621\201Rn\003\245B\315\363\325\250\346!1K\346\2615<R\034\202j[\225\014\231\252\021\270\216Q[1\311\205\006\223y,EH\264\356\364\206\230y\257\210\235A\025RT\252\216\274\3231\203JO4\323M\2465D\306\243sP=B\365]\352\007\025]\352\254\206\252Jj\253\234\325w\246\257z\211\272\324\rL\240\232L\324\212jx\352\324uv#W\242\347\025\241\017j\320\207\265hF8\2531\324\350{\n\275\016\027\031\353Z\020\313\221\201W\355\343.sZ\220\260A\216\365r3\232\271\021\253\221\232\265\033U\270\332\256F\325n6\253q\265\\\215\372U\370[5z\'\253(sO$\366\246\204\346\246E5:%Y\215*r\273\220\212\307\234\025\227\217Z\320\266\237\200\255VP\202\306\247\024\242\202j2k\342f\025\013\256ER\225pj\022\265\023\036\271\246\253v4\036\264\323Q\260\250\232\241aP\270\250\034Uy*\244\206\252\310j\244\206\252\271\305@\335i\005B\335MB\324\312c\036h\024\365\2531\325\310\205\\\217\222*\3645\241\rhC\332\264\"\346\254\216\005K\t\3475ad\313`V\275\234D\201\232\327\215\202.\005O\023sW\242j\273\023qV\321\252tj\265\033\325\350\236\255\306\325r&\253\221\265]\205\361W\241|\325\304l\324\3523R\005\305J\242\254 \251\327\212\232.s\364\254\353\210\201r})\321\240\0035j\023V\001\247)\244\315FO5\361ST&\253\310*\253u\250$\353P\323\263\221\357HzS\032\243aP\260\250XT\022\n\247%T\220\325Y\rT\220\325g\250Z\232;\324-\336\230i\204TX\311\251\222\002\325!\210\255=\005Z\216\256\304:U\370E_\207\265h\300j\364b\245-\212zK\205\253\226\274\260&\266\241\220\200\000\253\220\271\'\232\275\021\253\261\265\\\215\360*\312IV\021\352\334MWaj\271\033U\310\232\256D\371\253qI\316*\374M\214\032\277\t\357V\343~qVP\206\251\002\355\251\026\245\335\201K\024\303\232\245qq\202\331\357L\212\1770\205\025\243\027\002\246\315*\236iI\353Q\232\370\271\226\240aP\270\315T\221H\317\245U\177Z\214\212fpi\364\204TdTL*\027\025VJ\247-S\222\252IU^\253\275B\302\233\332\2414\334Pc$Tay\253\260\201\306jFPA\305F\253V#\025r!W\241\253\320\326\2045v6\300\247;f\233\031%\200\255{a\2001Z\220\267\025v\023W\242j\267\033U\224z\263\033\325\250\332\256\304\365n\'\253\261\275[\211\352\344MV\324\364\305^\267\2238\006\264\340|qVP\374\325a%\305^\211\204\213N+\264\373S\325\014\247\013J\366\376R\234\034\361X\272\211*F*]92\0015\256\235)\333\250\r\3158\2650\265|j\353P\262\324,\265\004\211\220j\204\313\216\225\\\323\030R\253v4\374qL\"\243aU\344\252rU9j\234\225VAU\\Uw\025\t\024\323\322\242\"\205\\\323\312\361L\331\315L\203\024\346n\324%X\214U\250\352\364=\005^\203\255^\216\254\253b\236\032\226&\303\214\326\265\273qZ\020?j\277\023U\310\332\255\243\342\247G\2531\275\\\215\352\334oW#j\273\023qW\"j\275\023t\253\2617J\264\234\034\212\275o!$V\202\265L\206\254E!C\221Z(D\210\rIn\n9\367\251\'\031J\306\324\340$\006\247\331\r\250+@\032\t\244\006\234O\025\021s\234\036\265\362\003-FR\243h\352\007\217\212\316\236>MTe\301\246\025\246\355\3475&8\246\225\342\242qUd\252rUI\005T\220UY\005V\220Uw\025\013-4\255D\313\3159\006\005;nW4\230\246\223\212h952\n\263\030\2531\365\253\261U\350{U\350\315M\232x<RFI\220V\275\273`\n\275\023\362+B\027\253\221\275XG\253(\365j7\253Q\275\\\215\252\344OW\242|\325\270\236\257\302\365v\'\253\261\266j\334M\202+F3\275x5<G<\036\265>p*{{\222\204/j\322I\200 \372\324\3228#\216\365\235\250\260\362\300\250mxQW\003Rn\245\315.\352%Q\267x\355_#\262TE1Q\225\250]*\224\361g5\237\"`\363P\225\246\225\247\017J\030qU\344\252\222UI\005U\220Ui\005U\221j\263\255Wu\250\212S\031j=\2314\360\231\340R\274ei\241)\222\257\034Th\265e\026\254 \251\343\034\325\330\252\3545r:\237\265*\032\222 \004\2315\247\t\343\212\267\033t\253\3617\025n6\253(\325b6\253q5ZF\253q5^\210\346\256D\330\253q\275]\211\352\364Rr+B\'\253\221\232\275n\3705qFy\035j\300\345pjkU\035\372\325\254g\247j\236\',pz\n\316\324\244\314\241i\320\034(\251\367Q\272\200\364\355\324\273\262\214\t\342\276Rd\250\214t\302\225\004\213UeL\326t\361\363U\331)\205=)v\2201Q\222T\340\364\250%\252\222\n\255 \252\262-U\221j\263\255Wu\252\356\265\036\332\215\222\221S\034\324\210\203\322\234c\315F\321\342\242e\317Zb\245N\213S\250\002\246\214d\325\270\305\\\212\255\307V\0074\252>j\220\374\244\032\277n\331\025n3W\342<U\230\332\255+T\361\265[\215\261Vcz\271\023U\370\037\025y_ T\350\374\325\310^\257B\375*\374RV\204\022\014\001V\243\223\232\320\212L\212\264\255V\"85e[\232\231[\0035\215;\371\227\'\332\255\306x\2517PZ\223u8=9[\203_-\236j29\2460\025\021Pz\325yS#\212\243,95]\355\375*/+\035i\254\265\004\251\306j\254\202\252\270\315Wu\252\316\265Y\326\253\272UgZ\201\326\243+Q\224\311\241\223\260\247\252b\234x\250\\\346\243)H\027\025*\n\223mX\211qV\220U\250\352\334uaE?\035\351G\315\305]\267\\\001Wc\253q\232\265\031\251\321\252\324f\254#{\325\230\332\256\304\365v\026\253\250\374T\350\374\325\330Z\257D\365z\'\253\2615]\215\352\344S\205<\236*\364R\206\344\032\262\222b\254$\231\247M8X\2175\231\021\313\226>\265y\033\212v\352M\324n\2405H\255_1\025\250\312\324l\265\023\n\211\327\212\256\311Q\025\333\332\240t\311\250Y*\t\027\212\245\"\365\025U\306\r@\353U\244Z\256\353U\335j\264\213P2Te)\004tyy4\273*6\\\323<\274\363OX\367R\233zO/m=V\246AVPU\224\025f1VR\244\305 \030j\277\017J\264\206\254\304j\334f\246CV\024\342\245G\2531\275\\\211\353B\007\343\236\225ie\315X\215\363W\242z\273\023\325\370_\245^\211\352\344oR\003\271\261\232\323\200\225P\005[V\251\267\0208\252SL\354v\223SC\300\025d5.\3727Q\272\22459Z\276me\250\212\324l*&Z\211\205B\313\326\242d\250Yj\027Z\253(\252R\202\017\025M\371j\215\226\253\272\325wJ\257\"UvL\232\211\322\242\331\223Jc\300\244\tA\216\233\345d\322\230\251V,\032\231\"\315#Z\363Q\030\n\366\247F\234\325\225Z\235\026\254GV\024T\242\223\034\325\2503\212\271\0375f>*\312t\251\220\363V\024\324\250jt`*\334-WRLU\230\3375j\'\301\253\360\276j\364.\001\031\253\310\300`\203Waz\275\033\361Vm\206\351FkMxc\355J\267J\247\031\251\305\300\333\234\325r\333\3375e\016\005I\272\227u.\352]\324\241\251\352k\347\246\207\212\201\243\250\314u\013\2475\013\'\265B\353P\260\250XT\022\n\251 \353U\312d\034\326s\256\030\212iZ\211\322\240d\252\322&N*#\026\005B\351\236(\020\200=\351\217\035\'\227\212<\2726zR\371t,y52E\203S\371{\227\336\230\326\371\355Q\233r\247\212x\216\244T\251\025pjt\025 \004\324\210\207\251\2531qVc\253h8\0252\364\251\022\247S\300\251W&\247\214z\325\244|t\251\343z\271\023zU\3049\372\325\270d\305]\211\352\364RU\350^\257\305%^\265\227ksZ\261\220Ww\255S\272\217k\006\007\203R\303\222\006MXQ\212\2206)\341\251CS\203R\346\234\032\236\246\274\r\333\002\253\261\250\331\261P3TMP\262\346\240u\367\250\035N*\006\004\216\225ZD89\252\344qT%O\336\032n\312\215\226\241t\315Bb\357PH\230\246,<d\3222\034\323\032>:Q\345\361Hb\243\312\366\243\3134\242<\032\220-9x\251@\004PR\233\260f\245H\201\024\361\020\251\025\000\251UG\245J\0234\021\264\200*\314c\246j\332\216)\352jU\251\324\360*t5:5J\246\245F\253\260I\212\273\033\346\255\306E[\214\325\310\236\257C%^\211\352\3442a\205lE\'\356\361Q\334I\362\250\245\205\361V\203\346\2245?u(l\323\203S\203S\303S\201\257\004qP\271\250\035\352\273IQ4\265\023\313\305Wi*&\222\231\2734\307\\\212\252\311\212\253:\014g\275C\214\212\215\226\240n)\215\310\250\032=\306\244\021|\265\031\206\230c\244\362\275\250\362\250\021\212_&\223\312\244)\212P\225\"\255?o\024\322\234\323\324b\236)\300T\213\301\251\327\326\243\335\271\352\304g\221V\225\252E5*\232\225ML\206\246S\306jEl\325\2045b3W\242n\225r6\253\221=\\\214\325\330\217J\271\023\325\244~\365~\332\344\343i54\222n\024\350\332\254+\324\241\251\341\251\301\251\301\251\301\251\301\252Ej\360\211\rU\221\252\244\215U]\352\026z\211\236\242f\246\023\232r\212y\\\212\202H\370\252\222G\236\265\003\304T\373S\032<\212\254\361\020i\2062h\021\340R\355\342\243d\250\331(\tHc\244\362\361I\202)z\323M\"\216j`\005;\024c4\230\346\235\212x\024\341R\216\225]\270\223\332\255\305\332\255)\004{\324\253R\255J\2652T\240\372\324\253S#U\210\315[\215\252\344MW#j\273\023\325\350\232\255\306\325e_\212\232\027\303\n\272\357R#T\352\365*\265J\032\234\032\234\032\234\032\236\032\236\255\315x\\\246\251\311U$5Y\315@\306\242&\232E&)V\246Q\305+G\362\232\241\"\340\325yNF)\241\306\323\270T$\206\351HPb\232R\233\345\346\220\307M0\346\232\321\342\243+\212i\250\230\3233M\335\212i\220\n<\352p\236\244YjU9\247\250\251DD\364\247\213f\245\362\266\3655\033\305\236\2254Gh\346\246C\310\253IR\250\251TT\312)\343\212z\265J\246\254Fj\334g\245[\215\252\344mW\"j\275\023U\330\216j\322\216*x\370 \324\373\363R\306\334U\204j\231Z\244\rO\rO\rN\006\236\r<5xt\225RZ\247%Vz\201\2522(\305.\332\002\363VR?Z\216\346@\243j\325\006\250\035\t\250\231\030\016\224\301\031\317\"\237\266\232\302\232\0074\2458\246\221M`\030Ui\001\006\242\'\025\003\232\205\237\025\033IQ\027&\22015*\006c\300\253Q\333\310q\301\253q\333H\007\3355e-\237\031\332i\342&\035\2158\254\200t5ZF#9\250\222|\036j\310;\271\025*d\n\271\031\340T\340T\242\244\006\236)V\246Z\231*\324f\255\306j\334f\256D\335*\364M\322\256\302\325v6\315\\\214\003\031>\225\t\233i\251b\270\025n93V\025\252e4\340i\340\323\301\247\003R\003^+\"\3259\227\255Q\220u\252\316*\002)\204Q\212Z\232\030\363\311\245w?\303U\232&s\223J-\375E\006\334SL\000\366\250\244\200\016\325Y\223\031\250Xg\2450\214\032p4\326\3053\245G\"\203U\\b\252\310*\273)4%\273Hx\025m4\247n\306\255E\242\271?t\342\265\255tE\030$\001W\377\000\263\242U\343\255=mbP8\247\030\223\267\024\326\265N\242\201j\230\346\250]X\214\235\2039\254\271,\212\234\343\024\350\240}\330\305h-\266V\244HJ\365\253\n\274T\241x\244<P$\002\244\017R\253f\254Fj\312\036*\304mV\342j\271\023U\330[\245]\210\325\310\332\256B\340\002\017z\204\304\314\307\236*E\201\207\275X\2100\351V\243\223\326\255#T\231\247\003O\006\236\r<\032\361\347J\2472U\tV\252:\324\014)\204R\021M5n\022\014|S\322\035\334\323\314@v\246\230\352&\\TG\255\014\241\205U\232.\rS\333\202j\0318\250w\363K\234\322\212cT\016\231\250\014\005\317\002\245\217N,\303ul\331\351\n0H\255d\260\215\000\342\236#U\340\nF8\246\022MF\331\246\362)7\237Z\004\236\364\355\343\275C2#\203QG\030\317J\262\024\001\236\364\207\031\245Z\220t\250\035\300\004US!\315M\034\276\265n6\310\253Q\265ZC\232\235\rY\211\252\344mW\"j\275\023U\330\333\245Z\215\252t\346\254%K\264u\035i\244\367\035EK\024\231\253hr*AN\024\361O\006\274\226E\252\223/\025\237*\3259\026\253\260\346\243\"\232E4\2415,D\250\305_\200eO\024\346\004\236)\276Y\357P\312\270\252\376]!\\SfO\220\326pB[\030\246\334A\204\310\025\232F\032\244QN#\002\230FjH\355Z^\202\264\355\264\262{V\202YG\037Q\315M\200\275)\215%1\237\322\243$\236\264\224\322i\204\323MFN)7R\026\240>:S\274\312PjE5 <Uk\210\333\250\351U9\035i\312\325j\027\307Z\275\035\\\214\234sS+U\230\332\256Fj\344G\245]\211\272U\330\333\212\265\033U\250\332\255F\3258\366\244x\311\345z\324`\0259\253p\311\221V\223\232v1N\006\234\255^W%S\230U\031\207\025FQU\210\246m\315K\0349\353S}\234c\245:;M\307\245\\Kr\203\030\2462\200x\245+\201U\234\014\363\315D\330\003\201P2\346\227fF\rD`U\344\n\255r\000\214\326)L\261\251\025qC\220)\221\251v\300\025\277\247\331\034\002\303\025\247\263`\300\250Y\252&&\243\3154\232i4\322i\214i\231\244-M&\230i\271\244-NSR\003R\003R\006\300\247\3440\301\2523\306\003eEB\005O\0305~\002E^C\305=j\314f\256Fj\344F\257Dj\334mV\320\325\250\332\255F\325j3\232\231i$L\214\325p\333\032\256\305!8\305M\311\247\000GZp\257.~ES\226\250\313T\345\025\\\216i\361C\270\364\253\211\016;U\210\355\363\324U\330\241U\035\0056\343\n\274U\0227\034\323%<qU\230f\242aM\305(\024\307\\\212\317\272S\264\326[\020\246\2432zS\222&\224\360+Z\306\304\002\t\034\326\332\3425\300\250\244\2235]\230\323\r4\221L&\243-L-L-L-I\273\024\322\364\335\324f\2239\247-H\r<5;4\345jl\204\032\210(\31752\001Vc|U\244\222\245\r\232\263\033U\270\216j\354g\025r#W#5n3Vc5i\rZ\215\252\302\232y9\025VU\301\247\301!\007\025u\0375(j\007^+\313\244<U9MT\222\252\310*!\036Mh[\301\201\234U\225\213=\252dL\nq<{UY\316\343\201\332\242\333\305C \250\030Svf\230S\024c\212\215\306*\205\3361Y\023\250\'\345\346\231\024\005\210\310\255kx6\340\001\212\325\210\010\320b\202sQ\265FsMcP\263Tl\325\031j\214\2654\2650\265!ji4\204\321\272\2245(jpjw\231\212p|\323\303R\026\024\200\363R\253\001\324T\361\310\t\025e*\302\n\260\234U\250\232\256\304sWb\253q\232\267\031\253iVP\325\2045e\rH\r5\306G5]xz\275\031\310\340\324\240\323\324\327\226\311U$\252\262T\004f\247\267\203q\316+M\"\332\240S\266zP\303h\250\334\361\216\365Y\2074\323\300\252\322\236qQ\3434`\ncS0*\0311\315P\226#)#<T_b\000\363V\342\265D\000\342\234\355\260\361\214\323\321\313\032\224\360*\031%\n\t5J[\325\\\363U\216\242\t\347\245\037o\214\322}\241\033\241\243x=)\206ALi*3(\246\371\264\307\237m@\327\200t\250\215\343\036\224\365\272jx\270cS\244\247\034\323\213\023OV u\2517\232P\371\251\343\367\247\367\251U*\314d\216\265m=j\302U\210\352\354]\252\354Un3V\3435j3VP\325\2045f3S\nq\031\025VE(\331\02542U\260r*E\025\345\222\032\251!\252\315\315\010\233\230V\234\020\355\025c\024\207\201\357Q1\342\240s\324\324-Q\310\330\252\262\034sQ\027\003\2554\315\237jkJ=j\007\271\003\245R\271\272\003\241\346\243\206\350\036\365ed\337R\006=*\'\0074,\273?\n\251q\251\225$\003Y\263_\273\367\252\215#\267SM.GZa\227\322\201pGzQz\313\336\246[\364?z\246[\204q\301\246\271\003\221P<\307\265U\222V5\017&\246\215\t5m#\030\251V1R\252\323\302\212\177\035\351\014\253\234R\207\031\251\221\352\302\266MXJ\260\230$U\310\207j\260\023\214\212\2321\203W\"\355W\"\253\221\325\250\352\324f\254\245XJ\260\206\247S\232\220sL\221r*$\0305n3S\003^U)\252\222\034\324[rj\345\254\005\233\245i\210\266\212B\270\344\324NE@\354*\273\032\211\217<TL3\326\253L@\2522IP<\270\357U\236s\330\325y&n\325RB\317\234\322\300\010`+^.\000\251\2529\016;\325\033\213\225@@\353Y2\271s\232`J\016\005W\222AU\332J\211\244\250\332Jn\372U\235\220\360jU\275q\324\346\236/3\326\227\317S\332\236\222\'z\225$L\361VU\301\357R\006\003\275)\235W\2750\334g\356\320$8\347\232\003sS!\253\t\323\212\261\031\253Q\232\265\027QW\243\343\007\265[A\221NN\265n*\275\025ZJ\265\031\2531\232\265\031\253)S\255N\225*\322\260\310\252\303\206\305N\207\025aMyT\2035\027\226X\361SEhI\344V\224\020\204\035*f\340sU\235\262MV\223$\324,\246\242~*&5\014\207\000\346\263.%\3118\252N\365]\3335\021\353N\n\034`\323M\267>\324\242\r\274\212\235\034\255;\317\342\232\356XqY\362\332\263\234\365\250~\312GQQ\310\205\007\002\263\347v\006\252\263\032\211\230\324Li1\232pBi\336Q\243\3114\322\230\245\tN\013\216\364\360\017cR\2430\357Roj\006M<\034T\212\325:T\3501S\243m\342\254\241\315Z\214U\310\305^\217\221VS\245=z\325\230\352\344F\256Dj\334ua*\314f\254\2475a*e\251\224\324\202\241h\362\331\025 N=\351\352ppk\314\266n5f;lu\034\325\250\341\305M\267\002\241\223?\205C\345g\223\322\241p\240\361U\337\212\254\3475\013\266\001\315f\334\317\270\220\247\212\240\355P75\033\na\024\200\342\232\327\005j#r\344qP5\313\203O\216g|U\264}\243\236\264\3573\'\245)\301\034\325y\242\3348\252\022\330\263d\325\tm\031{Usl\304\364\246\233F\364\2446\305\006H\244\001\207j\031\210\246y\236\246\227p4\322@\243u=ML\206\244\342\214zR\212\221jt8\251\225\252\302|\334T\311\225<\325\350Nj\344g\326\257BF*\324~\2250J\2321\212\263\035[\214\325\310\315YCVR\254!\305XCV\026\244\007\025\"\232\\s\232\224\014\212G_J\363\310b\311\253\313\026{T\2420)\257\201P\2200Y\272U\013\233\241\321zV{\316{\032\256\323\222y4\323.{\325;\211\267p\275*\213\363P\260\250\210\250\332\231\365\250\335\2608\252\315\315>%\365\251\232\335[\266)R\035\2751N8\007\223L/\375\334\032\215\213\372\322y\244pM5\3468\342\253I.z\212\256d^\324(\311\245p\270\347\025VIQx\025Q\2432\234\203M\373&:\265\006\334\216\206\220@\304\324\211o\357R\224T\036\364\300rx\251\000\247\201NU\317J\225P\324\252\2652-L\203\025j2\017Z\267\020\002\256\304GCV\343\307j\266\225a\rL\242\254F*\312\n\267\021\253IVP\325\204\251\320\325\2045.x\245W\251\224\346\245^:S\310\334+\202\206,b\256\252`R0\252\362\270S\317J\314\274\273\317\312\275+-\344\311\252\362IPn\315#\023\212\256\365\003TMQ0\250\332\242j\256\346\241=jH\363\326\244iO\255F\323\036\334S\001$\362i\031\310\350j=\355\330\322\035\346\242e\220\372\324N\204\014\271\250|\345Rr*\031/\030gh\305T\222\351\333\275W23\032\2269\034p\rN\245\217Zx\311\247g\024\231n\302\201\0339\346\245\020m\353N\tO\tOT\"\244\037J\225@\251\227\035\252U\251S\212\265\031\253Q\232\271\023U\310\332\255Fj\312U\250\371\253\010*x\3705r3V\022\254!\253\tS\245H[\212@jTj\262\255\305H=\253\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\026IDATx^\355\335\333\266\243(\020\000\320^\231\377\377\344\311\232\311\255\317\211A\243\010J\301\336O\335&\022\254*\020r\351\376\363\207q]\247\007\000\000\000\350\323ez\000\000`\216w\216\001\000\000\000\000\240y\276\007\320;\037\330\000\000\000\014\3046\037\000\000\000\002\261\221\357\231\354\002\000\000\000\000\000\000\020\224\237\247\237O\016\000\2003\370\352#\000C\370\262\351\256}?\254\335>\000\000\000la\237\272\326\2277\024\2003\031\240T\241\260\000\200\026\014\277&\271\016\037\001\250\301\300\002\000\200q\244\277\022p\271\244\217\263\235\035\026\000\000\020\200\255K\232\270\220\2440\350\225\332\006\000\000\000\000\000\000\200\306\370y\017\000\300D\344\005\222oj\026\220]\000\201\242\237}\215\335)\023\211@\251\007\240Weni\014E\321@P%\266\037\327\177\247G\3063\356$x\235)\242q#RV2\270-\222pT\001\037\224\004\014\305\220\007\310Sl\323\227?\021\347\237\311\016\037a/V\nG\t\327a\000\240\214\217e\014\000\000\0004\315Nv\007\237\005@\017\214d\000 \213\275\024@\030\246l8\235a8\250\265\211_\373<\000\000\200\265|\017\000*1\270\000 &o\304\303VF\r\0000 K \000*q\2139\316\330\261\366I\346\237\177\246\007\016u\271\304*@\005\223ic\340bU\305(nY\t\233\231\260\035o\207\020\016@\222a\025Ce\233\237E\340\306\345`\253\024\300\031\352\025\317\346\226\317.\200g\2077\367\033\316r/V\025\013\035+6\300\2135\304\261v\254\215\2469\23745}\030\350\336\216\t\205\346\310\346\300\336\223\177\335vCO=;u\254\022\205;c)0\357\371\271?\363\357\323\227\316\333\250`S\354\224\316E\372\350 \016\234\2456(\220\222\307{z\253.\257\300\253\235*z\377w\233\317\362\374#w\303G\256o\337\322\373\355\361\275\276T\037a\234\236\311\332\245\332\261\323s\027Q\007AK\r\231\313\333\205\335\277\352\237zZ@M\\F\023\235xIv\246\334\257;\236\205\264k\240\354:y<\305r\307\010\272\036]?\027WnJ\353\315\257\002\350\272\026\000xi\366\226X\362>\324\354E\236huLJ&\342L\253/x \367\334\326\nLf\273\275\324\333\350.\271\005\020\226\312\205\035F\2330\000\206\222\275Jrw\250Fh\271d\017\3142\376\326\340\311\375\240\0029\005\240\tV\274p\234\357\343\355\3733b\n\264\364m\246\253\315t\204|\257\001\375\1772o?/~\3765\177\230\'\213\"\2779\340T\311\021\r\000\014\315\372`@_\222\036{\303\367\345\342z\365\223\264\330\351\353\234\344\214\315\017\3449\314\255\326\006\275\035\002\360#\275\364x\334 \334&\016\220N@}9\257\233s\016\007\311\034\255\263\247\315>\300(\214\367C\031q\000\000\235K\254\257\217Z\003\336^:\361\362ws\307\227\034\325\357~\345D\035\000\212r;?E;ao\247\'\024$\255\300K\353\363\201MqE\333\223/\035\345Mc\272=+@H\323\301\177\214\274)&\357,\032\2646\225k\237G\001\347\314\005\234\312\010\033\314$\341\341\006\275\202\355LN\005*\202\200$\215\202r\346\r\326\020Y\240M\226\021T\244\274\356\002\205\241\314r\245L+\r+\221\321\022m\324\327}*\363\304H\036\000\000\300\273\373^\346\366\357V\333\353m\323\326&pO\366\236W\262\247\t\222\202\204\364\270R\016\022\020\000\030\213\033t1\307\255\252\226Ii\023Z)\207\251\352\375R\177\215\223\240\261]\025\300\030\344\231e\325\027\003\023\275Wd\357\327GS\224\333fG\317xU\225\310\177W\001\001\322\322\003\3752\367\000\235\221f\036T\302\340v\177\003\354\262\343\\\032\223\223\312\234s\310\'\336T\245\300:\363\270\307_[^\353\265\334\267\320\226\003[r\250\337~I\002\214hy\236!\214\334D\346\236G\'\024\000@\024\211\031\273\3517\t\250\344\261s\177O\275B\030\234\002(\372\366\0304\250B\211Wh\222\352L\367\274k\254\"L+\301\224I\330O+e\332[\322X\305\237\353\025\214\374\240\024M\235w&\026\255\213N\201<\254t\334+\361\260\256\002\310\"\270023\000c\353\177Ig\214\337\t\303\340f\n\340\232\271\007\337=q\354n`^\305\246\343\232I\263X\3018f\246\001F\241\000\000\370\253\231]@3\035\031\324\001?\370\336\370\n\326+\017\033\303\026@\177W\004\001\031\210\320\260R\003t\266\235\331\007\0060\304\002s\335E\256{\026\364c\300\251\3170\347I)\3600\340<\310/\362\017\034\312\244\003P\313\3103\254\315\355J\267\"y\025J\253A\363\177R\325\326j\346\001\000\0006zno\326\356r\326>\017\000\200#X\235\215\300\'>\207z\206\273\335\261\365\3363\325Q2S%\333\002 $\267\002\000\240+\3365H\213\037\227\330\313\326\370\361\247/\261\307\023@\014\346Z\232\024bY\372\326\311\020=\216\342o0MP+\tT\033^\225k:\330+^\004\357\377\213E\274n\003\005\271\027\227 \212P\2261\005{X\3363u\275\225E\370\302\010\177\001\'\022;\240W\366\r#X\270\213)\000\200w\013S\346>U\032N\314\342\211C\213\266>\277\204\353\031/\332\233IA\025\ti\221FV\2512\034\006\261\030\273\305\007\233\361\253\320bt\270\005\223\321\371\036\270\2310~\214\350\327\201\217\007hV\361\\\335\277\330\004\204\264yB\230\216\367{\003\323\2034\346\232\221\351:\224\312I\236\201\377)\203\234L\334\316y\235\367j)\247\035\372qidf\341`\006>\360\303\2140\n\367\374\215\014\r\030Q##\377\327\367\320\357\223\267\217oZ\223*\224\337\331I=\276\305[\332\211D\326F\267\267\002\366N\036@4\257Qo\364\277\023\217\256\364\234\316\2757\376hF\273\336\257\366\006\344q\376\3077\345\247\177/-{L\326\356\330\313\257\016\036\365\222\247h\372\342\026\252\244\351~\227\262p\375\364m\210\372\246\032\365\003\301\275\177&\367\366\207\rr\316iC\334\236\307b\235\231\253v\205^sr\263\241S\031\2553\006\245\001\000\000p\246*\273\262*\215\022\301\206\367\212\032sV\321\306\215X\302\245\257\313\201\263\2345\037m\027\247\247\241\010+\320,\377\034khU\263W\265\361z\202v\273\025\327\217\355\354\256o\232U\2605\301[\237\017\254\031\354FV\347$x\237\250\361{\374\3703j\357\331l\315l\017\324\267b\3325\\w[\021\345\272\016\313\341\351W\312@\016+kj*\232F3\320\340\024\300\331d`\352m\206\353?<>\351\345\215z\340TE\227\230\305\233\343\253\3523\310\363\277\240\370\370\247(\327\252\336A\362\345&\265\260 \353\242\030\275<Y#%\005\361\230a\000\330\343\324E\330\334V\261\263\233\333\221\2273FDYo\246\"\200.|\033\341\337\036\007\000j\330\260\375r\263\036\333\206R)h\356\235\030 \251\346@\255\3316p\276\364\030O\037=\312\271\257>\246I\314\367\245 \347\354\337\337\251\3139\177L3\221Z\273\216^\216y\342\320\321\326^\010o.{\343\266\367\374%\305\276=[\252\235\n\372\330\311\026\313\324/\345\002\363\210q\271\366\272\262=sa\003\031\266\343Um/\200C|&\253\321\216\306St\t\377\'\225\252]\n7G4\351\002\330]\246p\223./\226\030|\203Z\231\370Rc*\361r\245\232^\345\320\027\013\344\371c\371\300\266\365\277\306\033\032\215\032\347J\001\236\252M|\026\021P\201\201\005g:{\004~\274\376\307\201\031\033o\366\033\237\036\337\177\034\3615X\n\320m\016\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-5.textpb b/core/res/geoid_height_map_assets/tile-5.textpb
new file mode 100644
index 0000000..0cb5489
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-5.textpb
@@ -0,0 +1,3 @@
+tile_key: "5"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\325\210r*\326\332c\257\245S\272\203zq\326\271\353\244*Nj\203>*&;\272S\243OZq\217\212\253\"Ug\025\021\025\033.*\"\265\023\212\254\313M\021\344\323\230m\340TMQ\363HN\005D\304\323w\221NI\260y\251~\320\244t\250\245UpNj\250\201[5\024\226\313\216*\224\220`\324~MH\251\212\260\210jeLv\247\010\363\332\236\"\245(\007|R\205\007\2758*\216\324\274P>\224\360=\251\340T\210\2652\n\235\026\247E\253q\255Z\215j\344kW\"J\267\032U\224\\U\204\025:-[\215j\312-Y\215*p\000\247n\354)\342\244\002\245U\251A\342\245^+\224H\360j\310N)\205EB\351\3075\215\250\332\203\222+\233\271\214\2515\004g\346\346\256*\361JEV\225j\233\255DF*6\025\003T.*&Zh\030\2465D\334\232i\024\302)\214\265\023-3i\245\333A\004\212@\215\332\227\313\343\232i\266R3P\274\000S\004C5:E\236\3258\207\003\232]\241z\322\023L\306\343NU\003\245<&z\322\371b\236\020R\355\245\013R\252\324\252\265:\n\260\213V\243Z\271\022U\310\222\256\304\265i\005N\202\254\"\325\230\326\256D\265e\026\254\242`PF)V\244Z\221y\251\207\025\"\325\210\327&\271\245^j\302\307\305F\361\324.\244\203\212\315\271\214\220w\n\347\357m\216N\005d\264e\036\255\306r\005+\n\255\'5]\222\240u\250\030TL*&Z\211\205FE7mFS\232F\\TdSJ\323\nR\010\251|\272M\270\355Ml\343\212\210\356\244\332\306\224D\307\255H\260\016\365(@:\n\010\250H$\346\223\031\245\013O\tO\000S\250>\324S\224T\2503S\252\324\350\265b5\253q-\\\211j\344b\255\304*\312\212\235\005Y\215j\324kW#\025e\026\254\252\361\315F\364-J\24352\214\np\346\254D\274f\254E\367\253\237)\212\221\007\024\025\315@\311\203Un\242\014\206\262\036\000\344\202+\026\356\334+\236*\262\246\332q\034T\016\265\023/\025]\326\253\270\250XS\n\324L\265\031ZB\224\302\224\306J\217m!ZM\224\340\224\326\300\250Z\233\326\215\236\224\345^\330\251\004g\256)\3338\351M)L\332O\322\232\313\212hQ\353K\201J\0058\n\\R\201O\013J\026\246E\253\010\265:-X\215j\324kV\343\025n!W\"Z\262\213V\021j\314kVcZ\266\202\254\247\025)l-E\324\323\305M\030\251\324qNQS\257\240\251\342Z\306)\236\324\340\230\024\205j\027L\325k\205\371Mf\204\371\3533P\207\014H\025\224\313\212cTn8\250\034Ug\025\003\255FR\230R\243d\250\212\321\266\223m1\222\230c\246\371t\322\230\246\232\211\206j\"(\013R*\342\237\200)~\224\264b\243lTE3\336\223\313\367\247\005\024\360=\005;fz\321\263\024\273i\333qJ\00752-XAV\021j\302-Y\214U\250\305\\\211j\354kVQju\025b1V\243\025j5\342\247QO\3054\256\r9EJ\234T\353\315H\242\254 \253\n0++m.\332aZ\211\205W\225A\0305O\311\352k#P\307#\275bH\274\232\211\2050\256j\'Z\205\324T\014\242\233\260\036\264\326\213\025\023GP4t\315\264\025\244+L+M\"\243e\250\231i\205i\205i1\212N\224\233\21581\247\002i\371\310\246\220\r0\250\244\331\232p@)\352\276\325 L\212]\224\334{Rn\\\342\236\005L\202\254\242\324\350*\302\n\263\032\325\270\326\256\302\265u\027\212\235\026\247E\253\010*\312\n\262\203\212\231jA\3158\256G4\320*U\251V\247\214U\250\327\035idp\200\346\251\021M\"\241\225\266\203T^\340\346\2432\231\010\024L\010\210\342\260n\306\342k6H\352\006\216\243d\305@\365]\306j\026\024\312z\034\360i\255\035@\321\324L\224\320\264\205i\245i\205j6J\211\226\241aL\"\223\024m\244+F\332p\247\204\310\250\330\020x\244\301\247\001OT\024\360\230\245\342\223u79\2441\344{\323\343\007\241\253(*\304b\254\"\325\210\326\255\306\265n5\253\221-\\AV\021jtZ\235\026\254*\324\353R\255J\264\343\300\246\255J\005L\213\221V\243\000\n{\316\261\257^k*\352\3739\301\253\246\230\307\025\237r\3475E\315\020\374\315\305Y\237\0011X\2271\362k>D\250\031*\274\242\251\3109\250H\246m\3155\242\250\310 \324\313\363\255F\361\324\r\035G\267\024\025\2462\324dTl\265\013\n\211\226\230V\223e.\312M\224\205iU1Noja\244\300\245\340R\206\024\027\250\313\023N^jUZ~\336iv\340\203S\3063V\221j\302-Y\215j\324kW#Z\271\032\342\255\"\325\224Z\235\005N\202\245\025*\232\221j\314c\212k\036iTT\261\256ML\\ \252\322\337\204\350j\224\267\245\272\232\253\270\312}\253\245<TN+:\340u\252R\016\rE\014\233d\253\023J\010\254\371\271\252R/5ZU\252r\257\006\2522\234\324e)\230\307Zi\344Te\t\247\306\2705#G\232\205\343\250\032:\214\246*2\264\302\265\033-D\313Q\025\246\225\246\355\242\220\212JF8\351Q\223\2323HO\245!\367\244\335\216\224\231&\224\n\225EL\2434\374`S\212\344\212\261\032\020\325m\026\247AV#Z\267\022\325\270\305\\\214U\230\305X^\005J\225:T\303\255H\2652\216jq\302\324}MJ\253S)\300\250g\014\340\366\025\223pB\344f\252\002]\272\325\350\227\000WHG\025\024\230\000\326e\311\347\212\244\307\255V\301\335H\354sP\2775\013-W\2213T\344CU\335@\250\231sQ2R\010\271\247\210}i\255\026\332P)\032<\212\201\243\250Y*\026LTl*&\025\013\naZc-0\256)\244SH\244\305!Zn\332n(\013\232k!\246\343\024\n\225EH\005<S\327\222*\310\2178#\265N\203\326\254F\265a\026\254\242\325\230\305Z\217\265Z\216\254\245N\265:\n\262\202\237\212\221j\302\017ZWn\302\221j`jU\246J\245\201\305a\336.\3065\005\270\346\257)\342\272\031$\n*\224\323\0228\2522\261&\252\266i\270\3438\250[$\323J\323\031*\007J\257$UY\340\315@\320\221M\020\372\323\274\254R\371u\033\246j\002\204\032Q\356)\0320j\254\221\324\014\276\265\013-D\313Q\025\250\330S\010\246\021L+M+I\262\215\264\302(\t\232x\216\202\242\243(\r7e=V\245\013\305\033i\350\2705r>EL\253V#OJ\267\032T\352\270\251\343\025j5\253(\270\253\010*\302\n\262\203\212\235E>\236\202\246\007\002\230[\232z\232\2206)\301\352@r+\013Q\220\0075\005\261\310\315\\\007\212\336\237\322\2522\325y\022\230\2109\334)%A\216*\261\213\322\233\345S\035p*\253\014\232c&j\026J\211\343\250\374\272B\224\335\264\326Z\201\326\230\026\215\265\014\261\367\252\256\225\013%B\311Q\262\324,\264\302\264\302\264\302\264\233h\333F\312iNiBb\224\214t\250\230\032m.)B\324\200R\201O\013\212\2263\203W\023\006\254 \003\025e\0179\253\t\311\253Q\245YE\253\n*t\031\353VQj\302\216*@qN\0075\"\323\213qH\274\365\251\001\002\223\314\317JUl\232\260[j\023\355\\\276\2416\351\210\007\275X\265\030QVI\256\226X\362*\233\251\007\245!\213\"\242h\366\324.3Q\354\246\262\325y\026\2520\301\246\221Q2\323\n\361L\333M+M)Q\262\324.\225\036\314Q\266\221\223\"\251\310\2305\003\255B\313P\262\346\242e\246\025\310\250\312`\322\024\246\354\240%.\332M\200R\025\244+M+\232aJM\264\340\264\354R\201O\307\024\016\r[\205\262*\302\236j\324F\256\306*\334hx\315[\215*\302GV\021*T\03056q@\346\245Z~\354R\016z\322\226\240\232E5*rE\027\223yp\266=+\224i\014\267\034\372\326\274<(\251\031\253\257a\232\205\343\250\031j\027\031\250\nS\n\324l*\254\203\255Ve\346\230V\243e\246\021L#\024\230\244+M)Q\262Te)\214\265\031\340\3242\307\273\232\252\311\216\265\013GP\230\352&J\214\2450\255&\312B\224\233x\246\355\305&\332]\224\326\025\031\\\323H\244\305(\024\270\247*\324\230\243fj\304K\267\255Y\215sV\342N\225v1W\"<\n\270\235\252\302\324\302\235N\025\"\324\231\240ry\247\026\002\214\323I\315*\361R\306~a\212\253\253I\266\023\315s\226\243t\244\326\322\034\n\031\253\271+\201P\275Ts\203Q\036i\254\264\302\271\025]\324\203P:\346\2532b\242e\305F\302\243\333C-G\266\223h&\224\255FV\230\313Q2\324\0169\246{\032\202T\364\252\344Tl\242\243d\250\231*\026Z6\323J\321\266\232V\233\212R8\246l\3157m\006<\212h\212\235\345P\"\346\236#\245\331\212U\0375H\300\361\212\265\000\343\232\271\030\253iVc\253q\0360j\322\232\225i\364\340i\341\251\331\244-\212\001\247\nu(\251c\342\262\365w\314x\254\333(\361\315h\203\201Lf\257Aq\305Wq\305A\345n&\230\320\343\221Q\225\246\025\250\035*\006^j\007LT\014\242\242d\315&\312aZ\215\226\233\266\220\212a\024\3223Q8\305@W&\230\313Le\014*\007\212\240d\305D\313Q:\324%h\333HV\200\264\2333L1\322l\243e\'\227HR\224%8&i\342:_.\232\313I\262\236\007\255X\217\002\254F\325j3\232\267\035Z\217\203VS\232\235)\304\320*A\357J[\322\223\2558\nz\323\361\305\000T\313\302\223X\232\223y\222\005\024\220\307\261EHMB\355^\222\303 \324\016\264\213\036\325\317sQ\225\354j\273\256\323Q\232\211\305Us\203P\310\300\212\254\335i\204Rb\230\304S\010\244\333M S\010\024\302\265\033\246EWd\305F\302\243#m5\230b\242!Z\242x\275*\273\307\212\204\2574\233h\333M\305.)\n\342\232\027=\251\3333I\262\223\313\346\202\224\233qO\002\237\266\230R\220\2554\014\032\231*e\340\325\310NqWc\251\322\255F*\302\234\n\\\346\234\242\237\264\321\212p\024\340)\302\236:S\221w\032m\324\302\030\311?\205c\'\357d.js\300\250\231\252\006j\364\362)\247\2574\322sQ8\035\252\274\303\"\253g\326\230\334\325i\227\212\250T\346\230V\230V\232EF\313I\212c\036\324\303HE4\212i\034T,\274\324\016\274\324L\271\025\023-DS\024\231\301\250\244\346\253\224\346\223m!Zi\024\230\247\000\017ZpP)\017\024\302=)\240\034\323\266f\217.\224/\265=V\227m1\226\232#\334x\253P\301\201\310\251\032\014\364\247F\214\235E[\211\263Z\021/\002\254(\305<t\247\n\225EH)qF)@\247\201O\305=\230D\204\232\302\274\2717\022\355\007\212\2225\n\242\2075]\315B\306\275T\216*2)\206\243j\211\207\025M\324\202qQ1\250\233\232\201\222\243e\250\230TD\021M\357JG\025\004\213H\007\255!\244#\212\211\2526\250\335r3P\225\305D\302\242a\305D\303\025\023S\010\244\305\005j2\271\246\225\244\305\030\315\000{\323\200\035\350\300\317\025\"\256i\305@\246\021@\024\255\300\250\031\262j\325\274|d\325\221\307J\221\005X\010\033\265H\220\000sV\221p*e\247\201N\002\244SR\212Z\\S\200\251\000\247\242\3675\233\251\334\355R\252k2\3352w\032\267\234\n\215\332\253\273T,\325\353#\2450\212i\024\302\264\307N*\253\2575ZT\364\250\010\2460\250\234T,*&\025\t\353G4\204f\233\214Rb\2028\250\312\372\324r(=*,qP\270\250\030TL*\031\005E\212M\264\233qA\034SqMaM\305\0053\322\233\267i\245#\212j\360j`h\316iqN\013Mu\250\322\034\265]Q\265p)\300T\350*d\030\251\324\324\252ju5 \247\001N\3058\034S\201\247\2575*\255H\005\023H\"\214\375+\234\271\223\316\227\216\225$ch\247\026\250]\252\006j\205\215z\332\236)M6\232N)\255\315W\221j\254\225Y\226\243aP\275Dx\353Q\265@\303\006\233\232v)\204sJ\005\014\274S\nqQ0\305D\303\212\256\342\241aL)P\262sQ\262S6Rm\244\"\233\212B\264\335\224m\244+\232n)\n\322\212u:\236\2640\317JX\306*QR*\324\350\206\254(\002\244\003=*E\025*\324\313R\001K\216)1\353R \251\2213S\204\307Z\216i\204JI\254K\313\343&@<UhT\236OZ\261\322\243f\250\035\252\026j\211\232\275q\rJ\005.\332\215\226\231\266\241\220u\252R\214\032\200\232\211\352\023P\311Q\324L)\201y\2511\3054\255 \247\036E4\257\034T2-@\313P\262\324,\264\312C\036\352\211\243\250\312\323\n\324dSh\244\315\035i\n\321\262\224GA\217\035\250\331F\332P(\247(\251TT\3121Rg\0254{Oz\235F:sR\nz\232\225\016jQ\322\2274\001\223S \253\000\205\0243\340Vm\365\332\200T\236k\025A\221\363\3335mF\000\241\215B\306\240cQ1\250\311\257\\\214\325\210\316MH\303\002\243\316i\255PH*\234\313UY9\250\331j\006\025\003\365\250\311\301\244\"\223\024\206\222\216\264\230\245\003\212\212E\250\035j\026\025\023\n\211\222\223\030\240`\365\2441\203Q4 \364\250\0319\3057\312\246\262\342\233\2126\322\355\247\010\351\341qHE7m&\332M\264\005\247\205\247\201R\n\220sO\013\351R)+R+\023S)\251R\246\335\201M\315H\206\247CN.;\232\257qp\241\t\006\271\371\244i\245\340\325\230c\332*S\305F\306\240sP\261\250\231\2522\325\353\360\363\305]\2120\0074\222\361\322\242Z\030qP8\252\322\214\325r\265\033-@\351\212\256\351U\331i\240Rb\233E(\024m4\355\230\025\033\255B\313\232\211\222\242d\250\312S\nSq\212i\024\303P\260\346\220-!\2174\323\035\001)\302:v\312B\270\246\342\215\264m\244+@ZP\264\340)\300T\212*QO\247\255H\225:\320O4\242\246Q\305H[\002\253\317\'\312H5\213$\256\354FN*h#\000s\326\254\343\003\212\215\315@\355U\335\352\027\222\241/\232aj\366H\276Y1ZJ\006*)y\250\361KP\270\252\356\271\250\031j2*\'L\324\016\225\003G\232\214\307Ld\244X\211\243\312\346\234#\247yt\245*\'N\265\\\2550\250\2462T,\225\031ZaJiJc%D\311H\026\224\241\246\355\366\245\tK\262\223\030\2460\3153m\006\234\007\024\005\311\245+F(\305(\024\361N\025 \247\255J\275jaJ94\354\201N\rMy1\221U\244\311\3435\003\332\356\031\217\357TA\214M\265\3705:\311\221H\304T.F9\250X\002\274TE\021\201\301\250\377\000v\271\365\246*&I\'5\354\261\304K\364\253d\020\265\016\t4\270\244\"\243~\225]\373\324,)\204sLe\250\035j\"\224yY\246\230E\'\226\000\243\313\024\276]\036]F\313\351Q2u\315@\311L\331M)Q2TL\224\302\224\323\0354\2450\305\232o\225K\345\323Lt\335\224\036\005BFi1\212i\031\246\260\245\035)\300`PM&(\240u\247S\305H\265\"\324\240T\203\245&H4\240\344\323\267b\240\335\311\240\234\322+\355<Uk\245\017\363\n\254\222\02585`>i\216\001\006\240u5\001R3Q\0255\031\316+\337\002*\364\246Hs\322\242\034Rri1Q\275Uzn3Me\246\021\232\214\246i\246*\004t\326J\217fivb\233\232v3\322\243(ED\310MD\361b\230c\366\2464u\023%FR\231\345\321\345\346\232c\246\024\305\n\240\236i\216\203\265DEFE0\256i\245qM+\353Q\2650\323\227\2458\n1HE6\212p\247\212\221jE\251\226\226\202h\034\323&|\016*\020\334R\202i\245\251\205\263PN\230]\303\2556)2*\\\344TmP\265B\302\230\302\275\325\230\223M\'\327\2553u&i\t\357P\271\315Wzh\353JFi\204S\225)JRy^\324\306\212\231\345b\230c4\301nX\346\245\021\000\005\006,\324f\034v\250Z,\236\225\033G\212\211\222\242x\352\023\035/\226)\245)\214\270\250\034c\245F\006)\255Q\225\3154\256)\207\330S\010\250\332\242aM\"\225y\247\n\\R\021Q\232QJ:\323\305J\274\324\202\236\016)wQ\234\323\272\n\212R\270\371\215D\030R\027\250\313f\231\234\032\224\'\232\244\032\240\352`\223\025:\234\214\3225D\303\232\211\252&\036\225\356\254\270\250\3150\232@i\030\346\2425\023\nh\034\323\261I\212\231Tb\236\020\032R\243\025\013\255Bh\t\232]\270\024\320\271\346\235\266\215\240\324m\030\025\033C\270dUW\214\203\322\242)\236\325\031\216\233\266\243a\315E%@\313Q\225\246\354\246\025\364\2462\201Q\265D\334\324dS\010\246\021M\007\006\244\352(\315\004\323\0174\200S\200\245\251\024\342\245\rN\315&\352p4\342\340\n\241p\344\26501\247\346\223v)\240\345\252h\337k\324\027\250X\356\250\341l\255Hj&\250\230TF\275\325\352\006\250\330\322f\2239\246\236\365\031\244\305;\024b\236)\340\322\265F\336\365\036\334\322\343\003\212M\271\247\005\243m\001)\031j\"\010\250]7v\2506\020\324\311#\347\212\205\223h5Y\205FV\243+M)L`1\357P\267\035*\027\250\2150\212i\\Tl2j2\264\303\301\245V\305;9\351Hi(\006\2274\264\345\247\212v\352\\\322\346\243w\305T\221\262\324\253N\3155\215,JpX\323\315%\313~\344\343\232\243l\374\220j\331\351Q\265D\325\021\025\356%\270\250\332\2424\224\235(\3051\205 \024\360(\333K\266\234\0058\014\324n\231\245\013\305\0333K\263\002\215\264m\315;g\024\306\002\242+Q\262\324,*6\030\252\262\014\324\016\270\250\312S\010\002\243`*\027\305@\325\013\323\n\036\246\223\201Q\261\346\2439\246\032\214\212f9\247)\245\315!\244\024\242\224\361NZ\226\212L\320\0335\023\344\344\325|d\323\205:\232\3252\377\000\253\000Si]wDEe\257\311.*\3709ZcTmQ5{fsHi\270\246\221\212JP)\n\322m\247\001N\002\214S\202\323\266\342\223ni\3018\244\333\212B(\3058-\0140*\007\025\031\366\250\230\361Q1\250\217\275B\342\253\260\346\243aQ7\025\003\232\205\215Dj&\034\324m\222i\245x\246b\230E1\205FE4\255&1A\244\315\002\234\264\032U\342\245\007\212\t\246\232\024\363N\342\253\310\230<S@\247\001H\302\244\204\344m4\2450i[\345^k\036f\377\000H8\365\253\310~QCTL*&\025\355C\255:\214SXS@\247\205\245\331K\262\223m.\3326\323\302\320iB\323\261HE4\255\001i\341x\246\270\252\356*\006\030\250\232\242aQ\270\342\240l\324D\376\025\023\232\201\315@\365\023\n\214\217Jcq\365\250\210\3151\251\270\342\232\325\013\032a4\334\321M<Sz\236(\247\n\\\361H\r<\032\\\321J:\322\323Xn\250\312\021H)q\3054\214\034\212x\224\367\243%\301\315d\314\000\270\343\326\264#\037(\241\205D\325\033W\264\201N\305(\024\025\315\001)\352\224\375\264\273i6sMd\247\204\244aH\251K\214R\342\220\255&(\003\232\220\014\212\215\305WaQ0\315W\224\343\201U\036LTfBi\205\261\326\243b\010\340\324\'\223P\311\317J\200\323\010\3151\205D\302\230ED\302\232x\250\336\241j\214\322\037j94\323B\014\320\303\024\320i\335\251)\300\323\201\247\nu)\034Rc\024\244pj\035\264\264\021M#\024\341\367X\326C\374\323\3765\245\030\371E\rQ5D\302\275\245\016E<\014\322\201N\0034\360\264\365\030\024\354f\200\264b\215\242\215\264\2052i\312\264\215\035.\312M\224\233(\330\007Zq\034qP\270\250\030T\022p*\204\362c\201T]\2114)\315+.F* \204\032G\025]\315@y8\244n\005Bi\246\243aQ\265D\306\241sP\261\250\311\246\203\223Rf\232}\350\r\203C\220E0S\373Sh\025\"\212x\247\001N\002\227\031\243\030\250X\037JA\326\235\212i\024\262|\2201\365\254t\033\246\255$\351CTL*&\257dS\201S!\315J\0059E<\nv3N\002\227\024\241h\333H\306\221Fj@\264\355\264yt\205qH\026\215\264\204Uw\025\021\\u\252s\347\234Vl\334\036j\253\002\335(D \324\342>3Lu\364\252\262\034\032\204\340\217z\201\206\323Mc\221P\2650\232c\034T\016\325\0135B\315Q1\250\3157q\355N\317\255\004\323sN&\201KE\003\255J)\302\244\002\224S\300\245\307\265\0057\014Ur\273X\212\\R\005\311\250\357\316\310p+2\325r\371\255 8\246\260\250\232\241j\366E\351O^\rZ^E<\n~)@\247\001N\240\361I\270\na\303\037JT\0252\212\220\npZ\ng\265FS\024\230\2467\025\013\016\365\003\325g@A&\251=\276\366\366\250\336\334/j\213\313\013A \n\205\330b\263\356\034\202qU\274\323Am\335j6\250X\323\013TL\365\0035D\306\242cQ1\246\026\246\347\232]\324\205\361M\337OBZ\244\351M\007\232x\024\240sR\250\315;\030\247\212p\024\360)\330\245\002\253\310\270z1J\253\315S\324\217\312\005U\264\\\n\275\216)\214*&\025\013\n\366E\034S\300\253\021\2361S\250\251\002\322\355\245\305\007\000f\243-H9\316i1R\240\251\007\024\340j\302-+\n\214\212\215\252&\300\250\034\325g\3115\003\324y\002\241\221\252\254\215P\263T\016\325J~k>F\330jDp@\2476*\007aP\267\265Ws\212\205\232\242g\250\232J\210\276i\205\251\241\251\333\2513M&\237\033T\244\346\205\034\325\2208\243mH\243\212v)TT\200S\300\245\305(\246H\2319\246\355\247\204\302\223X\367\255\276B\007J\222\336<(\253\030\250\336\241j\205\253\331\224qN^\265\"\037\232\256 \310\251\000\245\305(\025\034\265\0363O\2152y\247\024\000\346\234\007\034R\323\324U\244\340R\265B\325\003\236i\2147\003UO\007\223Q;dUv\340UWl\032\205\3375Y\316MD\306\240sUf=k.v\353Kn\341\207\275H\357\212\201\244\006\242g\250Y\263Le\315@\353\212\254\324\302p*2\324\335\324\340\324\271\246\026\247\304jqR\240\346\247\307Jx\024\360\264\355\271\247\205\245\003\024\361N\305.)6\344s@L\221Q]\270\2110:\326:\251\222L\232\272\213\201Jj&\250Z\241j\366\304L\212f9\251PU\250\316\005;94\345\346\236\005G\'&\221W\234T\244`qM\344\322\343\002\224\n\224\n\231\017\024\023Q9\250\017\255\034\021UYpOz\202CU\344\351Tf<\361P1\250\034\324D\343\255F\304\032\2550\3105\225t\207\223T\321\312\036*W\230\260\346\241\337\223C\270\307Z\256\317\317\024\3174\212C.j\0269\246\021\232\211\226\242<P\032\227u%I\030\346\254\245Y\215sVB\212P1N\024\341N\247\001O\002\235\2121J\0274\343\210\324\223X\367sy\256@\242\030\260*|b\230\325\013T-P\265{x_\226\205\2175 \\S\306E(52\260\002\227w\2457\2559\0079\247\023\305 \024u4\341\326\245\002\236:R\023P\261\315D\306\243g\307\322\242f\316qUdj\253#\325Y\rWcP9\305W\221\352\271\222\243w8\252S8#\004U1\215\324\222\201\216*\253\270CP4\271\351Q\2310i\215&i\236`\246\2313H_\024\201\367\036h\221x\315@z\323\205(\025i\024\005\367\253\021DO5r8\3609\025&1J\0058\014\323\200\245\002\244\024\352SH9\251\020`\363U/\246\302\355\006\263\242\217sd\325\300\270\024\326\250\332\241j\205\252&\257s\000m\241F\r<\n\\R\342\212QR\216\224\341\305\035M\006\222\236\242\244\024\244\340TL\325\016\356i\214j23PH\330\315T\221\352\254\215P9\250d8\252r\311T\244z\256\322s\305!\220\221\315V\227-U\330\005>\365\013\270\317&\252\315\317CU\217\006\242rsQ\226\3050\265\001\270\246;\322)=ju\223\214\032M\231\351J\027\024\340\274\325\210\306p+B \025j]\330\245\0074\372x\036\224\360\236\264\273pi@\245\244\357\305H\243\271\246\314\373W5\225!2\275O\034{E<\361Q\265D\306\241j\205\252&\257vQ\223O\013\315(\024\340\264\273h\333F\332p\024u\247 \240\365\240S\324S\307\002\230\315P;\324\005\271\241\337\025\031\224b\252\310\371\252\222>*\263\275WiqP\311(#\025FBI8\252\333Y\316)\031\002\216j=\303\322\242\221\205P\231\262p*\264\2101\235\334\325f8\353P\273T,\325\003\311Q\031i<\332\024\356<\324\243\212w=\251\351&\323\203S\207SN,;T\22075y\rH\005J\202\245U\311\346\247\003h\340Uw\224\253\363OY\225\273\323\203\002i\304\201M\363\000\245\r\232\212y21U\343\217\234\232\233\2651\215D\306\242cQ5B\325\023\032\367\244\\S\300\247\005\247\001K\212\\{Q\212gCG&\2348\315%8S\305)<T\016\325]\332\242\335\315A4\265\030|\212kt\252S63T&\227\025L\313\270\343\232^\325\023\020*\274\222\355\351\305Uy3\336\243/\357U\246\233\035\rg\315+)\252\257p\335\352\006\231\215F\\\232\211\336\240\221\352\003%&\363\232\225\036\246\017N\363\005*\266ML\246\245U\315X\210m5q*\302\363S%M\030\313T\300\214\32471\003\326\241\020\205\357FpqR/4\2059\241\334(\250F\\\344\323\372R\023Q\261\250\230\324Lj65\013\032\211\215{\341\247\255I\326\224\nu\024S\010\364\243\030\244\242\234)\340\342\243v\252\3625Wf\250\367`\325y~f\300\245\013\264TR=Q\235\305f\316s\234Ud\030j{6\005V\226N\0175FY@\357U^\340\n\256\367G\267\025ZI\262\0175I\3459\344\346\240\222L\324-!\002\231\347v\246<\234\324\022=W\337\315.\372\2266\315I\273\024\34595f4\316*`1S%L\255V\342l\212\267\030\251\2623R\306y\025ch\340\367\250\2475\016Gz\000\035i\305\325\005@\323\0268Z6\347\2559@\024\214j2\324\3065\023\032\211\215F\315Q1\250\230\327\320\000d\323\302\323\261N\024\242\227\024\224\323I\236)(\245\024\244\340T\016\365Y\336\241f\2463\0002i\261\214\363D\216\000\252\023\313\214\325\007\223uU\221\211\310\2507\001\326\241\232a\214\n\245$\243\234\232\314\232}\316pj\007\220\001\315U\226\340\016\365N[\237z\254\3679\357P\264\376\365\023OL\363rhy\006*\006\2235\021|\032O2\245\212Nj\312\275M\031\031\311\253j\343\034S\303T\252\365*\022\306\264aP\024f\254\007\300\342\234\246\254Fy\025h\0163\351U&\223\3465Y\244\'\245 v\247\034\221\315:5\301\251\030\342\231\272\232\315Q\026\250\331\252&j\215\232\243f\250\330\324lk\350UZ\220\014S\261N\013F\3321HE4\212\214\360h4\235)sLv\252\316\365]\332\242\3150\234\237j\014\201G\025Zy\2532yI\357U\267\032\206I\007j\252\357\305R\232oJ\245+\2229\252\022\270^\365Fk\214w\2522\334\023\336\252<\331\357Q\0313\336\230\362c\275E\277\336\2173\025\033L}i\206Zi\2234\201\351\351&\rYI\211\253Q\271\"\247V5\"\311S\243\325\313c\226\346\264Q\270\247\006\346\245SV\021\352g\230\210\316*\233\266T\223L\006\236)I\245V\346\225\332\230[\212\215\232\243-Q\263Tl\325\031j\215\232\243&\243&\276\214Q\305<{\323\300\247Rb\220\323I\246\226\250\330\212nsE5\233\025\004\217U\235\352\006z\215\237\002\253\313)\003\212\254\327\030\030&\253\3119#\255Ty\200\252\317?\275Wyrj\255\304\373W\255Pi\300\0075F\342\357\260\254\331\256I\357T%\236\252I-Wi)\003\323\035\351\201\263J[\212\201\232\230Z\233\270\323\303f\244S\315Z\216\255FqSn\251\020\346\254\306j\334\'\006\257\243qO\rR\253\324\310\365&\340\303\031\252\316\330R)\241\251\341\250\337J\036\202\364\322\374Tl\325\031jc5F\315Q\226\246\026\250\313S\t\257\243\326\236)\340\323\251\245\2050\2650\275F^\232Z\231\277\024\031*&\222\240w\252\356\365\003\275A$\234UYd\343\255Uy=\352\264\217U\244z\253$\225\001\223\255P\272\233<\n\241#\026\035j\224\307\025Bf\252R5Vv\250\031\351\276e4\276h\337\212i\222\230\315Q\026\2405=ML\255\212\261\033\325\204z\224=L\215Vcj\265\024\234\325\324\223\212\224=H\257\232\225_\024\342\374\361QJ\334\373SCS\203\321\276\224=\033\351\245\3522\364\302\324\302\325\031jajajaji5\364\222\323\3058PZ\243-Q\263Tl\365\031zizc5Fd\250\332J\205\344\250\035\352\274\222Ug\222\252\311-Uy*\254\222\325i%\367\252\317&j\031$\n+:\342A\234\3257\237\216*\234\262\016rk>iG5M\345\252\362IU\313\022h\372\232ajM\324\233\251\214\365\031z@\365*=N\255\221S#b\246V\251C\324\321\275[\216LU\204oJ\265\034\270\340\325\205|\364\251\221\252`\324\245\275)\254r\rB\037\236i\333\3517\322\357\243}4\2750\2750\2750\2750\2654\2650\2654\232Bk\351Q\322\234\r\005\261L-Q\263\324L\365\023=0\2754\275F\317Q3\324FJ\205\244\250\036J\255$\225VI*\254\222UY%\252\222IU\236Z\201\3445VY}\3536\346\343\255gIpMWyI\252\322d\365\252\316\r@\355P\263Te\375i\246Ja\222\220\313M2S\013P\rJ\206\254#T\352\325\"\265H\032\246\215\215[\210\346\255#b\247V\315X\205\271\253j\325 z\013\322o\252\356\330\177\255.\372B\364o\243}!zizazizajijijM\324n\257\245\305\005\261L/Q\263\324L\365\033=F\317Q\263\324fJ\211\244\250ZJ\205\244\347\255D\362T\017-V\222Z\251,\265U\345\252\262KUd\222\253\274\200\014\223Ud\234\023\305S\232oz\316\232\\\346\2523\n\211\210\250\235\352\254\222Uv|\324\016\325\0135FZ\230\315Q\226\305&\352P\325*\232\225jT\253\010jU\251\026\247\216\256F8\251U\252d&\254\306pj\312\275?}\033\351\013\324r\034\256Gj`~)\013\321\276\215\364\322\364\322\364\205\351\273\251\013SKSwQ\272\215\325\364\301jc=F\317Q\263\324L\365\033=B\322Tl\365\031z\211\236\241i*\273\311P\264\265]\345\252\322IU]\362j\274\257\201\326\251\264\234\362j\264\327\n\265\237,\345\317Z\200\275T\232J\243,\225X\311\232cIP\274\225RW\252\306L\032k>j&j\2179\240\324mM\247\255L\242\245Z\225\005N\242\245Z\225jd\253(ML\265e\rJ\255S+\361N\337N\r\232B\324\335\334\021P\206\244-I\276\202\364\322\364\205\351\273\350\335I\272\232Z\223u\031\245\335_K\026\250\231\3522\364\306z\211\236\241g\250\231\352&\222\243/\232\215\232\253\311&*\264\222\372T,\304\324.\330\352j\264\222UY$\252S\314{U\t\'#\275S\222]\307\255B[\025\023\311T\245\222\251H\3715\0136*\026z\201\344\252\362>j\271=i\205\251\245\251\231\2434\207\232LS\324\032\231EN\253S\"\342\246Q\232\225V\245U\251PU\2055*\361R\251\251CT\240\361K\272\234\255C5F[\025\036\356i\013SKSKSKRn\244\335K\272\215\324\233\2517Q\272\224\032\372I\244\250Y\351\205\3526z\215\236\241g\250Y\3522\371\244\006\230\355\201T\245~\265X\266M#\266\005S\226J\254\362U9&\252\222\310\rg\316\370\'\025I\236\230d\250\235\370\252R\234\232\256\347\025]\332\253\273T\016\325\0136j75\0214\204\322P\005.)\300T\252\265*\255L\213S\250\251UjU\024\354T\211S\251\251\224\324\253R(\346\245\307JN\224\3654\255P\267z\2046sHZ\232Z\230Z\232Z\220\265&\352]\324\273\2517Q\272\215\324\273\253\350\306\222\243/L/Q\264\225\013IQ\0311Q4\231\250\313\322\254\237-C$\231=j\274\244\036I\250I\013\316j\264\323g\201T\235\352\254\322\340U\007\233\223U\244\2275VV\3105U\372T\r\221P\263T\016\325ZCU\334\325w5Y\330\212\2179\2460\3154\203M\305\030\240\n\220-=TT\252\242\245U\251\225jUZ\224\nx\025 \025\"\214T\200T\212je9\251\343\\\323\363\203M&\234\247\326\206jc\237\2275S8&\202i\244\323\030\323\t\246\356\243u.\3527Q\272\215\324n\240\267j\372)\236\242g\250\332J\211\244\250ZJ\215\244\250\332J\214\2754\311\201PI)\035*\264\223\037Z\257$\307\326\253\264\231\315@\317U&~\rf\313\'5Y\346\250\232L\324L\325\023\236*\253\266*\027j\256\346\2539\252\356j\026\250\310\240\323M7\024\240S\202\324\212\265 ZxZ\221EL\202\245\013R\001R\240\251\002\324\212\264\360\264\340*D\253qc\024\215L\3174f\232\317H\355\362UV<\320M4\232a4\302i\231\2434f\227u\033\250\335F\352@\325\364+IQ\264\225\023IP\264\225\013IQ\264\225\031\222\232e\250\314\231\250\244z\251$\225Y\244\311\346\241y*\273\313U.%\342\263e\222\2533Te\361M\3633M/U\344\3475Y\332\241v\252\362\032\256\325\013u\246\363IF)6\323\302\323\302\324\201i\341j@\265 Zz\214T\312je\\\364\251Uj@)\340b\236)\330\245Z\261\031\342\203M4\225\024\234S\031\262\225\001jM\324\231\246\223Q\261\246\223M\315\031\243u\033\250\3154\265(j\372\t\336\241i*\026\222\241i*\026\222\2432S\014\231\246\263SK\340T\022IU\036Nj\273\311\212\255$\265U\346\367\252\223M\232\252\3075\013SJdu\250_\345\342\230Z\243v\342\252\310j\026\250Z\240j\214\2126\322m\245\013F\312P\265\"\255H\253R\005\251\024S\361J\005H\253S\3061S\201R\001K\212z\2558-(\034\324\212\010\247S\t\246\026\244?0\250\266\225\353\322\241\221q\310\250\263\212\t\244&\243c\232i4\322i3Fh\335Mg\244\006\234\r{\353\311U\236J\205\244\250ZJ\211\244\250\232Za\232\217<t\250\344\233\336\252I8\035\352\253\334\201P4\331\025ZIj\2735B\347\212\200\232\214\232ij\206S\232\203&\241\221\252\271l\324lj&5\031\024l\365\245\331M+@\024\354R\205\251\002\323\302\201O\013R\005\247\205\247\005\251\225*U\025*\212\220\npZp\030\247\212r\217Zp\024\204`\323\030TMQ\346\235\234\216i\204\216\207\245W\2210x\250\263HM4\232a\246\223HN)\245\251\013SsK\234S\201\257vy*\273\311U\336J\205\244\250\332J\205\244\250\232\\T^v\016MC-\317\275Sy\211\250\231\352?7\007\025\024\217P4\224\302\331\250\211\2461\246\026\250\\\324]MG\"\212\200\212\214\255D\302\230E\030\245\240\212LR\342\224\nz\323\305=EH\005H\005<\n\225je\247\250\251qJ)\300S\200\251\002\344\322\355\305\014*6\024\302\265\023G\3157n)\244S\010\310\301\252\262\251\006\230\016i\r0\323I\246\023L-M\315.isFk\334\032J\256\362Uw\222\241i*\026\222\242ij\027\226\253<\276\365\003I\357Q3\324O.\005W2\363N\336\n\324\016y\246\027\3057~i\214\325\0314\306\351Q7\265D\315Q\261\250\311\24674\314R\355\243\030\240\322\036\264b\236\0058.*e\214\021\301\247l\3059EH8\247T\213R-J\246\245^i\340R\212x\251T\323\210\310\244\333M+L\"\243ja\250\315F\302\242q\221UXm4\302i\244\324l\330\250\213f\233K\2323FiA\257gy*\007\222\240y*\006z\211\244\250\036J\201\344\250\032J\201\344\250^Z\201\344\250\014\234\322\211\251\305\363Q\26574\023Q\223L\3150\324,9\246\021\212\214\323\0174\336\224\271\244\315\035iqF\332z\247\275L\027\326\2342:T\241r9\243m8\nZx\251\026\244\025*\032\230\036)qN\035j@02ju\301^(8\250\310\024\306\025\003\212c\n\214\323\032\2435Ve\346\253\223\212\215\237\322\2429&\222\2234\231\245\315.h\025\353\355%@\357P<\225\013IP\264\225\013\311U\336J\201\344\252\357%B\317Q3\324e\251\233\271\247\2074\355\371\024\231\346\220\2650\232a<\322\036\224\316\325\013\232\214\232ni\t\246\021F)\312)I Rn\346\236\257R+\212\220\032z\276)\371\356)\300\323\251E<qO\006\244V\251\320\346\245\307\024\243\255K!\004\014T\221\034\nV\250\233\212\215\215Fy\2465D\324\303Q\266{Uv;\270=j\264\203\025\001\246\023\212i4\334\322f\212\\\323\201\257\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\nIDATx^\355\334\331\222\343*\014\000\320\251\374\377/_\327\255^&\023\307\361\0166H\347<ug\263A\002\204\235\356?\177\026=\336\037\000\310\3439\005\016\257\217\002?\014\214\340z\t\260j\265\222^\022\200Z\376\016\255.\206\330\\\272NO~\372\010|\231\313!\200l\254\224@\010&\263\334\304\037v\351u?|\346\274M\023\000\000O)J\2433\265#\001H\200W\237\306\374\260\322E\237\336C\034\263\361\235}\242\tki\013\034\021w`\265=\2435#n\002$#\337\001\350\325\236b\344\343k-\202\313\336\373\347c\'\002\000\000\320\250\367]\035\000deM\204-\334\003\200\027\351\006D\272\006\357\224\246\226(\222\010\355\366\326\243\335S\273\330\277\216\360\325z\010\312\330f\003i\002!\030\312\225\005\336B\310\035r\3330\0026\274\204\261X]v[kn;0\000\000\000\255^\r\264U\00463a@h\215\226*\244e\321\201>\030\253\237<\327\324x\335s\331\337\005\014\001;\017\000\310\300\325\025\000\2567\267\203\236{\374\220\242\037\226\200\222\000\340\020\313Mr\022\000\240\030[\022\216YX\214\027\236\242\031C\307\203\277\3373\377\344\266\377Bz\327q\271\314\362\\,\001\000\000\202[.\007\211gs\304\177_\270\371\365\304\"\360\331u\232\001\235\2366\274\221\311\300-\032\276\027\320\360\251\305\262m\001\332\366*\256P\374\336q\351\317\003\000\000\240_\007.\000\034x\013\037T\337\236\377=@\365\003\001\000\274R-\002\375\010\271]*9\r\207\354 \000\000\000\366\262=\034i\240;~O\341\3305\200c\357\2421g\322\360\314{i\310\321\261,\001\202\220\000\000\300\026u\326\376\311_;\037-M\306\312|JD\337=3\354\350\237\367\000\365\244\347s\207jv\214\177\000 \026e@^\027\307\376\342\303\261b\020\221\344\\\034\001\312+=\263X\251j\321\263\000\364\241tm\261\327E+\346E\207\341V\367E\371\276#\323\004\t\200$\310N\002\000\020\304\335\027\010\370L\251\321$\303\005\000\000 \205\347\366\317>\020\000\000\346\204\272\231\025\2521\020\302\337QYit>\367\373\225>\237Rf\003t\356\222\315\373\273g\017CtB\017\274/\t\2045\236\362-\000@\313n\232\243n:\354uj-\372\265>\227NH\200N\010\024@\026f|\000\322\010\177%\347\234\331\356Q,\360\211\274\210hv\032\000 \021\253A^\207b?*\n\037J\304\276\274\306k\030~~_K\203b1^;PGv5e\327\213+(\026@\000 55\005p\237\331\031h\366\211\266\335v\332+\007^yz\352\356\355n5a\033\006\020\307\356E\013\000\350\2225\377\220\323\273\332\323\037\320\233(\211V*pM\365\307\025\'s\3051\340v\022=9\t\000\220\315\317\314\177f\376/\265\275\340\026gBO \3061@\034\026w\000:\365\334\226\254\355O\326\326\272\265\347I\351\353\337#\214eK\224I\007\000\220E\266%o\302\032\010o\014\212\344$@.\3432 }QpP\244Q\023\251-i\t\"\000\000k\324\214\'\331=\323\244\303#\373\360\033\001\240\013V:\000R\033\206\351\327\343\263K\333!i\033\236\335k\340%\001\000\000\267R\220f%\362\367\320\357\000\000@L\253\273\035_\364\017\332\003\2555k5\023/\326\332\371p1\t\000\000@o\036\207vy\t+\337\355M>\324\243tO\334\001j3\323\002\r\331\276= $\t@\033\232\250\216\374\337\204x\232H\254\375d\"\000\000\334\257\323\355\004u\004\332\246\311l\370\307x \227@\213\031@%j\203\276X\3316x\351\244x\371]\240E\005>\242q\001[\370lR\300\266\0010\343\247\242Q\374%7\310\201\254*\307]Q\331\262\237\340\307\215Q\334\226\225Ub\0228\362\247\001}\306\247\317\263\256\356H\002\320\200\207\214\246\016\211\005\345Xbc\020\307\344ZK\200\342\347\023|\345\037\312\367X\020o\201\017\330M\337\033\335\200\355\002\200*\202\327\204\300\275\224\345e\350\307\234\252.\321U?\2746#\"\210b\201,\366A\271\350\266\250D\026\000xuam\320\3656\023\000\000B\272pC\000e5\277\305l}t5}~\315G\267\007Y:\261\351L\006\000`\037\305\335\001Y*\177\026\254\3747\260\346s\344\367\364\233?\317\336|\367\353\320x\307\216On\364\333$\255\233n\0104\313\310IN\002\320\271I9@O\204\357rmM\372\022\340IW\314\3201k\226.\366,<\365k\375\025\177.\2306>\035\340\323c_\346\036\347\3338\240\233\302\013\264i\347\000\336\371r\000\200\306\274U3\212\233#\036\017;f\000\216\262\366\002@\233\336\327\350\367\337\273\366\322\230\274\373\331\363-\017\225\023@\n\347g>\"\350~\375\352\276\0017\323\177\000O\243)Q\241\224\211h\247\246\030b\201\364\000\200\313(\312\001\340(\233W\200:\314\257\000\204aQ\333a\347u\312\235/\007\000\332c9\007\2022\275M\244\332\034\213\377~\361\372,^\213\000\000\000\226\330\005\001\000\344\223\352\346\017\000@V\212>\026I\020\000\210\305\332\016\231Mf\200\305/\202,>\t\000\000\000\320\024W2\000\200\350&\367y\200\210\346\206\372\353\343\377\275\374Lvs\031C\022\022\000\000\000 \231\177\267\305\335 \007\000\000\200\256\270\273[N\207}\331\341)\003\000\000\264\302\226*\013\221\006\0023\305\001\300Yi\2777=\214\352\210\264\335\220\272\351\000\000\000t\306=\201\354\356\276\212!\003\201UwOT\000\000+\354k\000\016\262\337\343\213<\000\000\272\246\230\001\000\200\350T\375\200/\005\364\345w\336n1j\377\003\020\243\377\206\213\235b\021\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-7.textpb b/core/res/geoid_height_map_assets/tile-7.textpb
new file mode 100644
index 0000000..83f1fcb
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-7.textpb
@@ -0,0 +1,3 @@
+tile_key: "7"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\364\241\311\247\272\014UR\270j\221E;\024\240R\342\227\024\355\271\244\3074\341\326\234)\340S\302\324\2121O\025\"\324\202\236\265*\324\213R\016\265 \247\221\300\247!\035;\324\312)\342\235\232p4\352\rFx\346\254B\341\206\r<\257j\257  \321\022\0265<\361\205N:\325#\2321IM84\204\n\215\224TD\n\211\361U\334Uw\025\003\212\201\305VqU\334T-P\270\252\322\016*\234\253Ud\025Y\305FV\242\"\241a\232\214\245D\351Q\024\250\235k\327\2323\236)\256\016\334\032\213m8\np\024\340)@\245\3058\nP\264\273i\341i\301i\340S\200\247\201O\024\360*E\251\026\244Z\221jE\251\224\016\365\030 \310qV\024\323\301\247\003N\024\340ii\254)\250v\265[S\221\232c.\346\346\254\300\2358\242\362<(\"\263\366\322m\244+LaL5\033TdT,*\027\250\\Ug\250\030Uy\005We\250XT\016*\264\202\252\312*\234\202\240qP\221Q\262\324L\265\031\024\306\025\013\n\211\205{\010\036\265\024\200T;y\245\013N\333K\266\234\005.)\300S\200\247\005\247\001N\002\224\np\024\360)\300S\305<T\202\236)\336b\257SR\t\227\037/&\201\276C\311\300\251\321v\212\224\032p4\341N\024\341N\024\244f\243#\006\254Dr\274\320\300\203V\255\337\212}\340-\030=\2534\214RQ\212c-F\302\243+Q\270\305@\365]\352\026\250XT\014*\007\025\013-@\353U\344\030\315U\220U9\005VqU\330Tei\2053Q\262\032\210\245D\313\216\265\013\212\205\305z\363q\322\240y0phV\rR\252\322\342\212)\300R\343\322\224\nx\247\001N\305-(\247\np\247\np\247f\202r1\232b\214I\216\306\257\244`\n\225i\340\323\201\247\003O\006\234\r8\032x\247\001HW4\014\257J\221X\265O\037\006\255I\363\304@\254\366\217\232aZLSH\250\312\324o\305Wz\201\352\273\212\210\212c\n\205\326\241+Q2Uy\027\025VAU$\030\315R\222\253=@\302\230E5\216:Tg\232\215\270\252\356j\007\250\034W\255<\200\n\243<\302\2515\367\224\371\315\\\203RW\003\232\224\335\016\306\234\267 \367\245\373H\315J\223\206\251D\202\234\034z\324\313\315;\024\264\003N\247R\212p\245\315;\265F\354W\232\204\334\342A\3175\247\004\333\30758\251\001\245\024\361O\035i\302\236;S\3058S\351\030qO\203\275J\006\rJ\200\223\365\250\245]\246\2414\322)\244Tn*\026\250\034T,*\007\025\t\025\033T-Q3TLj\0075RSTe9\252\216*\273\255DV\232\313Q2Tl0*\273\232\200\324l\265\023\255z4\267\003\007\232\313\270\270\311<\325\tK9\342\230\211*\037\224\232\260\262\314\007SR\255\304\242\247K\306\376!Z6wq\271\001\216+ac\211\324\020\334\3242\304\253\321\251\022M\275\352q>;\346\246W\rN\306zR\342\224\np\247\nQN\002\231*dV;\356[\234\023[V\317\200*\3720\"\244\007\024\361O\024\361\3058\032p4\360i\342\236)q\272\236\213\264\324\246\245\214\322N\205\206@\252\244\021I\214\323J\323\031j\026J\201\326\241e\250\035j\026Z\205\226\241u\252\3561P\265W\220\3259Nj\254\225Y\226\242e\250\310\250\330TNqU\2449\250\030TdS\010\250\334q]K\\\356\025\\\202\346\246H\272T\313\030\251\004c\322\235\345\n_!Oj>\317\267\225\342\246I$A\200\306\247Id\'\3469\0251\215\230d\032\201\244\222>\271\305Y\266\273$\326\224r\206\02584\340i\302\236\0058\nv*)\030(\254\231\030I?\313ZP\214\001V\343r\rZV\315H\0168\247\003N\006\234\r8\032z\232\224\032vjd\0250ZF\351\212lNw\n\270pW\212\253\"\342\241\351IM8\250\236\241l\032\211\222\241d\250\035qP\262\324n\234U9V\2538\252\262UI\005Wu\250Yj\026Z\215\226\253\310j\273\344\324%j6\025\031\024\322*\027\256\221b\247\252\000jA\307Jp85 l\323\301\247n\247\251\315<b\246A\307\322\247\004\355\310\374i\031C\212\245*\030\216S\212\236\326\364\202\003\032\325\216`\303\203S\t*EqR\253\n\220\032R@\352qY\367\327\001\020\205\357Tl\327s\3565\265\020\030\251\024s\305YQ\3005 j~iA\247\003N\006\244SO\006\236\234\232\270\215R\001L~\225[y\215\262zU\350\246WN\265\034\247\232\200\3223\201Q\026\315B\3715\001\342\234\016W\232cTL\265\021Z\205\305T\225j\243\255U\221j\254\213U\331j\026Z\205\226\241qU\235j\026Z\205\226\243aQ\221Q\267\002\253\270\256\246\202h\335F\352p|S\303\323\267\323\325\352Uz\231_<T\253&:R\207\301\241\300qT\336\022\255\225\251\340\270)\303U\305\272\351\315J\267$\036\265f;\241\336\246\373j\201\305V\236\367\336\263\036G\270\177j\321\266\214\240\025\240\215\212\231\033\326\254#v5&y\247\003J\r<\032p4\360i\340\324\261\034\346\255G\324U\2208\246\260\252w*q\305$\014Ur;U\304o1zTrD\303\'\025M\362\247\232\001\310\250\334\324M\355L$\212\214\271\357HZ\243cP\265W\220f\253\272\325Y\022\252\310\225Y\322\253\270\250\030TL\265\003\256*\006Z\205\205D\313L\331P\313\201U\034\327M\272\220\265&\3527\321\276\234\037\232xcN\017R+\324\311%J$\251\267d\0029\247+z\323\260\032\232a\006\221\240*F\r&]i\004\222zT\352]\307Jp\267.~cVa\267\013V\200\333\214S\325\252\302\002}\252\324})\344\323\201\247\212p4\340\324\360\302\235RBy\253\221\236EZV\315;\031\250\244\2175Q\334[\003\221\326\241\267\324B\313\216\325\260\223,\313\322\253\\@95A\270\250\330\324D\340\322\036j6\025\013\002)3Q\2675\013\324\017P8\315U\224b\251\311U\330T,*\026\025\023-B\313P\272f\2421\324R\035\242\250\310rj\006\025\321f\232Z\230^\215\364\273\250\335OY=i\373\307jz\275J\257\212\231d\310\305J\222\021R\254\204\234\324\350\371\025&jD\3062FE&\325\'\216\224\341\020\364\251Q@\343\025&=iA\247\356\243uH\2221 f\257\305#*\362*A \'$\322\203\371R\371\200w\247\007\317\265<\020\335\351\341G\367\215/\314:05$nC|\325z3\337\265ZF\251\224\322\221Y\372\224d\247J\310H\210`En[\202\261\256MYl2\237Z\314\230`\232\254MF\306\224\036)\254*\027\034\032\216\230\325\023{\324\016*\273\325Yj\243\216\265\003\212\205\224\324ej2\265\023\255@\303\025\014\214*\224\315Y\357\'\315Q\264\265\320\026\246\226\250\313P\032\215\364\340\364\340\324\360\324\365z\225Z\245G\305L\255S#\35550q\236*P\334T\261\313\216\243\212\220\225\352*h\361\267=\350i\001n)w\322\356\24058\032r6\r]\023\226@\270\351J\244\324\241\370\245\r\315;u85H\256j@\364\354\325\313yr\274\232\275\033\202*ej\225Z\251j\023\2026\016MS\215G\031\255 \000E\307J3\317\025R\344s\232\244z\324mB\232SP\310j#\322\243j\205\315D\306\242|Ui\024UWJ\256\313Le\250\\b\253\261\305W\222LU)\256\000\351T\244\270&\252\311)j\254\325\023WB\315L-L-I\272\215\324\007\247\207\247\206\247\206\251U\352D|\032\235\037\336\247V\315J\215\371\324\252\365 j\230>\341\322\236\257\371\324\204\250\307\257zqe*1\326\223u8\032pj\221\r^\205\224!\365\251RA\214b\220\236y\2434\240\323\305H\264\360i\340\324\3219C\317J\277\034\230\344T\253p\007=\252\031\365 \200\205\252P\271\231\2131\315]\333\307\025f\026\334\230=\250\'\006\240\270?-S&\243jh84\273\252\'94\302x\250\232\241~*\0065\023\032\211\252\273\340\346\240b\001\250]\200\252\322\275Q\232`\277Z\317\226Fj\254\365\003\212\205\205B\302\241j\333-L-L-M\335F\352]\324\340\325\"\265<5H\034c\336\236\216*Tz\260\262qS\307&\322\rK\346\2069\003\025 \223 T\310\343\036\364\360\370\346\237\36374\241\251\301\251\301\251\352j@jdsVT\232\221NMHq\332\200*@)\343\232P)\375*Tn0E=da\307QJ]\210;\016\rUh\345rwQ\004\255\013\355qZ\221I\270U\210\376\\\232Rj)\233(j\236)\255Q\221\315!\351Q=B\315\212\214\277\255F\346\252\274\233MFe\035\352\026\225}j\027\220\016\365VI@\351U$\230\325Ief\252\257\317Z\205\305@\342\240qP\260\250\036\241j\325-L-L-M-@jp4\006\251U\251\341\251\301\251\352\3252\265J\257S\253\344T\350\330\025*H\000 \363OW\251\225\263O\363\010\030\007\212\221Yvd\236i\003\023\323\245=^\244V\251T\324\311\326\256G\265\207&\226\236\rJ\244S\201\251\001\247\216\264\341\315=jE\247m\311\310\353OV*p\303\"\244\222\3329Wp\024\220\257\226p{U\235\324\205\252\263\271v\300\351JS\002\242aQ54\216*7\351U\332\241`s\315B\344\363\203U_\336\240r\rB@\'\236*\tT\212\254\365]\352\007\252\357P\260\250\034T\017P=B\325\003\n\320-L-M-L-@4\340\324\340i\352j@iwS\203T\252\365\"\265X\215\252\300$u\251\021\206EX\334:c\232Uz\2205.MM\023\200\016ic \223\272\245FS\327\212\224\020:\034\324\310julT\310sR\212z\340S\367\np>\224\360\364\360\324\365j\2205L\2074\346 \nXe\031\332MJ\024~4\355\330\246\034\277\002\200\241hcU\330\344\323\010\250\311\250\230\324-\315D\375*\273Uy\006j\263\214T\017\232\201\311\250O\0078\252\362rs\212\205\233\003\201U\236\241z\201\333\214Uw\250\032\241j\205\252\321jijaji4\252i\304\322\253T\252\324\355\324n\247\007\251\025\252Uj\231\032\254,\234T\210\334\324\301\360\335jM\340\236*Ej\2206i\301\260jEl\234\232x8\251\221\252ej\235\033\246j\326\365\000c\255;u(l\323\201\251\003S\203\323\203\324\201\351\352\3652\2759\233\"\230\215\207\253Fb0iL\231\031\024\365\223\tM\017\232k\034\323\010\342\243cQ6OJ\214\241=\351\214\204Uy3U\3335\003\325w\250^\240aP=@\340\212\254\365]\352\0075\003\232\201\315@\365\013T\017S\223L-M&\232Z\200\324\355\364\241\271\247\207\247\007\247\206\245\006\244V\305J\255R\243\325\200\371\002\246\215\307B)\340\374\330\0257\335\305J\215\305H\247&\244\347\322\234\032\244\014*U\315H\255\216\265:=L\036\244W\355R\003N\337N\337J\032\236\036\234$\251U\352tz\227vEF\371\007\"\246\211\303\'5*\256\345 \036\224\210\340\214\032yB9SI\311\353Mj\211\215D\336\324\336E1\213\n\205\333\332\253\276*&\013\216z\325f\343<Uw\250[\245B\302\241\224n\252\262%Uu\305WqU\334T/P5B\325\013\323\213S\t\246\223I\2323I\232pj]\364\340\364\360\365\"\265I\232z\265N\246\246\rS\241\356:\324\321\311\216\243\232\220\023\336\244V\251\003T\313\'@zT\203\004R\240\311\353S\253\355\340S\367\344\212\2205J\257\212\2208\251\026J\223viwR\207\247\007\247)\251\225\252Uj\234=;9\246\357h\217\003\"\246I\207U\342\225\324\246\037\261\251\221\362:\322\223\336\242f5\033\034\324g\"\232}\350b\010\252\354*&Z\201\205@\342\253\270\250XT\rP=W\220Ui\005V\220Ug\025\003\212\205\205@\342\241aL&\232M4\232L\321\272\2234\233\250\335NV\251CT\212\325(jz\265N\255S+\342\245V\251\327\234{\324\341\370\332{T\212}jQ\202x8\247\364\034\034\323\324\364\317J\221\037i\310\251\003\356l\232~\374\267\024\360\334\324\201\252da\216M<5<585;u\000\324\212\325*5J\257R\253\324\252\365/\017\326\223\312\301\371N)e\220\354\t\327\232\2263\201R\026\342\230G\031\246\026\035\206h\013\270g\275B\352W\255G\273\025\033\021Q7\025\023T\017U\336\240z\201\352\026\250\034Ui\005VqU\234T\014\265\003\212\205\205B\302\253\023M&\232M4\265&\3523HM\000\323\201\251\024\324\252\325\"\265H\032\244V\305L\255S\243T\350\370\251\223,x\251\225\261\326\244\rR\003\351R\253\345pi\350\t\351O\r\212pjr\265J\032\236\036\244W\251\003S\303S\303Q\272\236\032\236\032\246V\251\025\371\251\203T\361\276\0075(j\205\2372\014\366\251\321\352V8\034SKn\030\246\001\212_\247\024\016xj\2574E\016GJ\256y\250\330\324,j\007j\201\315WcP=D\331\250Z\241u\252\356\265^E\252\356\270\025]\305@\302\240qT\311\246\223L&\233\232J\\\322\023E(4\360\325\"\265J\246\244SR\003R\251\251\321\252tl\324\350\307\265L\030\2202jEj\225H\357Rdg\212\221\033\007\"\244$\261\317Jr\343<\363K\236x\247\003R+T\212\325 j\2205;u.\352pjxj\221Z\245V\251\321\252Uo\230\n\262\274\324\027\n\335S\255@\223\310\207\346SV\222\3600\301\340\324\261\271bI\351R\003\232:Q\327\353J\300H\244\036\265I\320\2515\013\212\201\205W\221j\006Z\205\326\240e5\013q\326\241qQ\267J\256\342\253\275Vz\256\302\241u\252\356+<\232i5\0314\334\321\232Pi\017Zu\024\340i\352jE5 5*\232\225ML\206\247F\305Y\211\271\346\246SOB;\323\267b\254\"\202\231\3174\261\261\006\254;p1K\021\014piH\305\001\251\342\244V\247\206\251CS\203S\303R\203O\006\244\rR+T\310\325:\034\221V\221\252P\271\243\310\004\321\366A\235\330\253\n\243n\010\305A(1\237Z\204\315\203\2021NI\206jRy\014\275)\222\000\334\325WZ\201\220T\016\225\013F{T\016\225\t\030\355P\270\007\255V\221qP0\250\331sP:qU\335*\273\255VqU%\006\262w\323K\323KSwQ\272\234\032\235\232\001\247f\224S\305<\034S\301\251\024\324\312jd5:\032\231Z\247V\251\024\323\301\311\251s\267\200sR!\365\342\237\270\376\025&\010\000\323\321\371\245lg\345\247\002@\346\236\017\24585H\036\236\036\234\032\236\255R\003O\006\244V\251\025\252\3025Z\211\363V\220\324\353R\nx\024\025\r\324T\022[+d\342\263\246\267x\330\225\351K\024\344|\257\305M\272\242j\201\205B\342\242\'\025]\352\273\324\016*\027\\\212\256\302\243\"\242qU\244\025ZN\365Y\305D#\311\311\2546U\r\301\310\246\230\201\357BC\273\275G2\252q\236j\024\345\261R\310\2331\212h4\274\216\264\3455 4\341N\3158\032\221ML\246\246SS\241\251\224\324\313\310\310\251\024\324\200\323\324\324\310A\353S#\214`\324\252\343\'#\212L\363\300\342\227w9\2517\226\306iA\247\251\247S\207\024\345lS\303T\201\352Ezxj\221Z\247\215\352\3227q\326\256D\371\025eMJ\246\244\006\226\203\315D\350\017Z\245s\007\004\257Z\253\034\204pz\212y9\250\332\241qQ0\250\034T\016*\006\034T\r\301\250Xu\250\033\203P\271\252\317U\334f\241)\232F\\q\\\256\r4\222)\003\262\3645\033e\2174\344m\224\346\224\2767S\323n\340j\304\221\243.\340qQ\004\\q\326\215\244S\226\235J\rH\246\246CR\255N\206\246\\\232\263\033\014R\347\232\2205=MJ\246\246SN\r\203R\371\271\024\356\n\217Z\010\333@jxjpz~\372pjx4\340jEj\221Z\244V\251\221\252\324OV\342c\273\025q\rL\246\244\006\236\r\031\246\232\212^\225\224\343\023\034S\373S\032\242j\211\205B\342\241qQ\025\315W\2250j\263\212\256\375*\007\346\240aQ\025\246\021\212\255!\346\271\242)\244S\010\244\333M+I\266\224\np\334x\315=r\rNHd\344\363LZ~(\035i\353R\245L\2650\251TT\311\301\342\245\335\223O\006\236\rJ\206\246SO\006\234\r<7\024\273\210\024\375\300\217zvW\034u\247.\336\364\270\317\335\244\r\203R+\324\200\323\201\251T\324\212je5b6\301\253\221\276q\216\265r6\316*u5\"\232x4\354\322\023PJx5\234>g&\236j3Q5F\302\242lTOP\236\rG\"\344UI\027\232\252\342\253\313\305V2`\323K\203U\335\362MW\222\271\242qHZ\232M&h4\001KN\002\237\212Jr\324\202\223\2758T\253S\251\251\243\035\315H*d4\360zT\200\323\301\251T\324\212\325 4\340\324\355\324\271\247\003N\006\237\237JP\304\016)\353\2029\353I\312\232z\275H\247\232\224\032\231\rL\246\245V\253\220\2660j\344g\025aZ\244V\251\003R\356\244-PN~CU\020b\221\2150\232\215\252&5\023TMQ\232i\344Uy\026\252H\265B\343\200k8\276Z\230\362\021ML\221\232d\225\3160\315DA\024\334\321E:\227\212QO\035)*@0\0058t\245\305-=*d\367\253\010r8\251\000\251\024\323\305J*E\305;#\265=MJ\r85874\270=\216i\300\221\326\234\0334\340y\247n\315(<\324\231\365\353H\016\rJ\017\245J\247\212\221\032\247SS+U\270\030\021W\021\252uj\225Z\236\032\227u\005\252\t\233<T$\342\230MFM0\232\215\252&\250\310\250\310\250\311\305B\346\253Hk>\353\2258\254\365\213\004\223PL2\334S\320aj\031k\234ja\250\310\301\246\232ZQN\353J\005<t\244\247\216i\353N\245\3059je5b/Z\2274\240\324\212jE5 4\340i\340\324\200\323\272\032:\322\362:S\203\032xl\323\201\247\003N\006\235\270\223N\316i\352qR\241\'\245H\207&\247SR\253U\250[\006\257#T\352\325*\265<r(-Lg\307\326\242-\334\323\t\246\023L&\230i\206\243ja\250\333\245@\365VW\305Vy*\214\244\263{T\017\307J\252\303-O\306\005V\224\327:\324\303L4\303IN\006\236\r8S\207JJr\324\203\212x\245\245\025*\325\210\317\002\236)\342\236\016\rH\r85<\032\220\032x8\247f\224\032v\352Pi\340\322\346\236\r8\036)A\247\203O\006\236\255R\247&\247\004v\251\024\325\3301\216z\325\244j\260\255R)\251C`\363Li\007\'\265G\273\'&\232[&\232M4\232a\246\323M0\323\010\250\330UyN\005e\\\312rqU\203\356\024\326\305V~j2\270\244n\225Rc\326\271\363Q\232a\246\232i\245\024\341O\006\237\221\212JU\342\245\316qNZu8\nz\365\251\326\244\006\236)\342\234\r8qRn\315<\032xjp4\354\322\346\234\r<\032\\\323\324\323\263J\r<\032vi\353R\243b\246F\343\025<c$\003V\224c\245N\222c\212\264\215\305J\207\232s?5\031$\375(\315%\024\204SH\246\232n3HV\2435\004\215\212\316\270\230\034\200k6f\004\324C\332\220\212aZ\211\352&\351T\346\357XMQ\032\214\232CGZAN\035i\302\226\224\032vx\251\242\0314\3420i\302\236\0059x5*\324\253O\024\341N\025 \247\np4\340i\340\323\301\245\245\025 \245\247\016)\331\245\31585(jxjz\232\231\032\254\304\325r6\350z\325\205\000\266EXSR+S\272\321\212P)qF)\n\323J\323N\0050\232\215\232\240\222P\240\344\326]\335\340\350\reKpOJ\215r\375jP\270\244j\215\215WsQ=T\232\260\232\243j\210\322Rt4\235)\342\234)M(\247\212\222#\206\253.\001\031\024\300*E\024\270\251\005H\225 \245\035i\342\244\024\354R\323\205<S\326\235@\251\001\247\nPih\245\006\224\032x5\"\232\231MXF\253P\276:\325\224|c5eZ\244\006\244\rN\006\234)\331\244\2445\031\2463\001U\345\235W\275g\334_\355\007\006\262\246\277f\316\rSfy\r\002/Z\225W\024\343\322\242cQ3T\016j&5Z^\225\204\325\033Tt\332i\344QJ*U\240\214\032QO\024\364\034\212\262y\300\245e\332i\313N\002\234\005H\240\212\224\014\323\261\306)G\025 \247\203E(4\340jU4\354\321\212u<\032v)(\024\352\0058\032z\232\235\032\247CS\251\351VCg\034\346\255\306\334\n\2305H\2475*\323\300\247b\230N*&\220Uy.@\3175Rk\265\037\305Y\267\027\231\350k9\313\312}\251V\337\035jA\020\024\245E0\323\030\324\016\325\0135D\306\231\236*\274\335+\021\205D\325\031\246\032m(\245\024\354\372R\203\332\235\214S\305H\275j\310q\263\035\350\352i\340T\202\224\n\221jT\031\2470\244\357O\024\340i\324\231\305(<\324\212\324\360j@iisK\232\013\021N\355\223A\342\200iA\251\024\324\252jt5:\266*\302\2779\035j\304.s\212\266\255R\251\251\224\324\242\231$\252\200\344\342\262\256\265UN\024\325\003~\362\0363P\310\362\037Z\200\243\267SJ \035\352A\030\035\250*\0054\212\215\215@\315\212\211\232\240f\250\031\271\2461\250\313qPL\334\032\310j\205\205Fi\246\232E%\024\242\234\016jE\367\251\027\322\245\013\307Zp\253p\306\031\t\364\244\3075 \031\247\005\247\201S(\305!\240S\251\302\235Hi)\342\236)\352i\340\322\346\224\032\0174\354\2221J)wQ\332\234\2652\032\260\225\"\236jp\340~U,r\200\3035z7\315XV\247\371\252\275N*95\024@y\254{\273\366\231\260\235*\232\304\\\345\3715ec\000T\235\260E!@G\035j\"0i\013b\230Z\242f\250\235\252\007j\201\332\240v\250\213S\031\252&n*\274\255Y\306\243aQ\232\214\322\036\224\332(\035i\342\236\265*\212\231E?mX\200\237\272;\323\366\025<\324\200S\200\251\024S\263IE8S\305-6\227\024\242\236:\323\205<\032\\\322\206\245\006\235\232Z^\264\202\236\t\251\227\246jdj\2234\370\316[\025$\352c\034u\244\217P\300\303pEL50\007\025^[\311%\345sP\215\357\367\215J\251\212\225x\247\206\240\2657v)\215%F\315\273\247Z\205\233\025\031z\215\232\241sP9\250\030\324,\324\315\325\023\265W\220\3253L5\023S\r!\246\232J)\342\236\2652\324\350*]\265f\331rH\003\232V\316\356i\352)\370\247\201\232\\`\321@\024\016)\353K\364\245\307\024\224\242\234)\302\226\224\032Zu.i\374\020(\317\006\200i\342\236\246\245\rR\006\247+\340\361VZA*\017QU\236\020\306\225b\003\265I\214\014P8\245\335F\372<\312<\312B\324\306j\210\276*3\'\2551\210\307\025\0215\023\034\324Lj\0075\003\032a5\023\232\255#T\rQ\032\214\323M4\323M7\275-<S\326\247J\260\202\245\002\254\333\022\255\305:C\227\310\247\250\247\342\227\265\004~\224\240\0222(#\201K\214\nQNQ\326\227\024\230\243\024\264\340i\324S\251A\245\245\351K\316y\242\234\032\236\r<\032xjxj\261\033|\206\231\346sN\337\232pl\365\246\027\246\357\244/I\276\215\364o\246\027\246\026\246\023Q\261\246\357\3051\271\372T.\010\252\356j\0265\0315\013\032\256\346\243j\211\2523M4\323M4\332Zx\251\026\246J\263\035N\242\254\333\256\343\216\346\245\222\023\030\311\246-IJ(\31794\240q\301\243\2674\270\247\001J(\245\024\237J\\R\201\232u(\240zR\347\024\341\322\227\255-\024S\201\247\006\247n\245\rOY\010\342\215\374\322\211)\376g\035i\205\350\337H^\223}&\3727\322n\246\226\246\026\246\226\246\023LcL-\236*\031\024\212\200\367\250\\\324,j\007\250\330\324f\2434\303\326\232M#SE8S\205H\265:\n\263\035N\2654Rl>\225l\376\372\"T\222E@\243\006\245\024\224R\212p\346\224S\251\324b\2121E(\245\242\234(\306iz\032ZQK\212(\245\006\215\324n\243u.\357J7R\357\240\271\305\001\350/M\335F\372]\324\233\251\013S\013SKSKSKTmM2z\363PI\355P1\250^\240z\215\215FM0\323\r4\323Z\212QO\025\"\324\311V#\253)O#5j\331\202\251\365\246\377\000\025?<SM&i\300\323\251\300\376\024\341O\300\365\245*G=i:\322\322\342\223\030\245\0034\001\353KI\322\226\234\264\352BqJ\r!\244\315\031\030\246\226\"\215\324n\243u.\3527Rn\243u\033\250\335I\272\227u4\2654\2657u\031\246\223LcQ1\246\026\343\006\241\221p2:T\rP\275D\306\243&\243&\232i)\244\346\212QR-H\005J\265a*\302\032\222\254[\270V\344f\234\347,H\242\220\322QN\006\234\r8\034T\213\3158t\243\003\034R\212\\Q\266\212)GJCE(4\374\322\023M\315\004\346\214R\036)\244\322d\322f\223u;<P\r\004\322n\243u&\3527Q\272\215\324\322i\244\323sHM4\232a5\023S7v=*\031\007\247J\201\252\273\032\215\2150\323I\244&\233\232\005<T\212jAR\255N\225a*A\322\246\204e\205J\343\r\315&i\t\246\320\r;4\340i\300\324\200\342\237\232\\\343\2458\014\232p\030\245\002\220\212L\322\322\036\264\224R\346\202sM&\214\322\203E!\024\302i\204\322\203\232\\\361\212L\321\2323M4\322h\335F\3527Rn\244&\232M!4\322i\204\323\t\250\332\243\'\326\242qU\030\324f\230M4\232i4\231\245\245\025*\324\200\324\250j\302\032\235jQRF\373H\305Lr\347\216i;\322\036\224\323I\232\\\323\201\247\251\365\247\203R\016\224\361\357R\014R\321\320\321\326\223\034\321Hi\010\315\035)3Fi\244\321\273\336\233\236iwzP^\230\315L\245\315\031\2434\240\320M74\322i\244\321\272\2234n\243u4\232nh\3154\323\032\243j\215\2522}j\2214\302j3M4\322h\2434\361OSR\003R\251\253\010j\302\032\224S\207Z\274\233V\023\317&\253\347\232Ri\246\231K\232p4\360i\352x\367\251\007J\220\032\220S\261K\214\321\212)1Hi;SM%!4\322i\245\2517Rf\2234\322h\315\031\367\244\335N\316G\024\252q\326\220\232ajBi\244\322\026\244\315\033\250\315\004\323I\244\315\031\246\236j6\250\215F\325@\232i4\302i\204\322f\214\322\323\201\247\003R)\251T\324\350j\312\032\235iI\305[\265\304\200\2065\034\213\261\310\240\021McL4\200\323\201\251\024\323\324\324\240\361O\006\244SO\006\235J\r.8\246\237jCHi\264\323L=)\244\323OZ\030\00085\036h\315!4\231\2434f\2246)X\372SwqL&\2234\322\324\233\2513Fh\315\031\244&\233\2327PM0\324mQ\232\316&\230M4\232a4\231\245\240S\307Zp\251\005H\246\247CV\020\325\2054\254x\251l\345\304\240\032\275=\271\220\3461\237\245Tu1\360\334T{\263M&\233\232p5 5\"\232\221MH\246\244\006\236\r8\032\\\323\267qIHi\246\222\232i\206\232i\215L&\2234\231\244\2434Rf\220\2327SI\244&\220\232i4\334\321\272\214\322f\2274\231\244&\2234f\220\323\rF\325\226M0\232i4\322i3KN\024\361N\024\361R-J\246\247F\253(\324\342j5b\216\010\255\233=McC\270U\033\253\257>B\325\016\352B\324\231\247\203R\003O\006\244SR\003R\003R\003K\232\\\322\346\2274f\220\323M4\323I\346\230M0\323\r4\323sFh\242\220\360i\271\2434\231\246\223I\232i4\334\321\2323KFi3M&\2234f\214\346\232MF\325\222Z\230Z\230Z\223u\031\245\006\236\rH)\300\323\205H*E5*\265L\257O\337HM cN\006\235\272\220\265\001\252E5 5\"\232\220\032\221MH\r<\032p4\264\240\322\321Fi\246\233Mja\246\232a\246\023L&\2234\240\322\346\220\236)\204\323sM-Fi\t\244\315!8\244\315\031\245\315\024\204\323I\244&\2234f\202i\215X\244\323\013SsIFi\300\323\324\324\200\323\301\247)\247\203O\006\244\rO\rO\017N\rK\232]\324n\2434\240\324\212jE5*\232\220\032x5\"\232x4\340i\300\323\250\315\024f\214\323MFi\244\323s\351L&\230\306\230i\271\2434\240\323\331~\\\212\210\234S\017\037Ji4\231=\251\271\2434QE&h\315\004\323I\2444\231\244\315\033\275\351\t\254&ja4\334\321\232\\\322\203R)\251\001\247\203N\006\236\r8\032x4\340i\301\251\341\251wS\203R\346\226\234\r<S\301\251T\324\212j@i\300\323\301\247\203N\006\236\r.i\t\244\315\031\244&\230M0\232a84\322\336\264\322i\215Q\232\006M(4\340\347\030\244q\305DM0\232M\330\246\226\240\032v}(\315\035h\351Hi3IHM0\322f\2234\026\254\026ni\244\322f\214\321\232p4\360jE4\361\322\236\r8\032p4\360i\300\323\201\247\003N\006\224\032p4\340i\340\323\201\247\203R)\251\024\324\200\323\301\247\203N\3158\032p4\354\321I\2323HM0\232\214\232a4\322i\244\323I\250\311\244\007\035i\336\342\223uI\346\002\270\"\241c\315DM4\232@\330\315(\351\305(4f\2274\271\244&\222\220\323I\246\223M4\334\322f\260Kd\322\023I\272\214\322\346\224\032z\232\221O5(4\340i\300\322\203N\006\234\032\234\032\234\r8\032vi\300\323\201\251\001\247\003O\006\244\006\236\r<\032\22058\032x4\360i\300\323\263Fi\t\244&\214\323\030\324li\204\323\t\246\223M&\230M4\236jEn9\2466G=\251\003qL-M&\230M&i\331\030\343\255.sFOz(\315\033\250\316h\246\032i8\244\246\236\264\206\271\342\334SKR\203FiA\245\006\236\rJ\206\244\006\234\r;4\340iwR\206\247\006\247\006\247\206\247\003N\006\234\247\232\220\032p5 4\360i\340\323\301\247\203O\006\236\246\234\r8\032v\352\\\321\232ni3HNj6\246\023L&\230M4\232i4\302iA\342\244\335\270TG\214\342\230M4\232ni\t\240\032x<\361O\335\232C\3056\2234f\214\322\023M\'4\204\322f\232k\233-I\272\215\324\240\322\346\234\r9NML\r<\032x4\240\322\346\215\324\240\323\203S\303S\203S\203S\301\251\024\323\301\247\203O\006\236\r<\032x4\360\324\360i\340\323\201\247f\234\r\031\2434\204\322f\220\232i4\303Q\232a4\322i\204\322f\214\322\206\301\346\2069\025\0214\334\342\233HzQ\236)CS\325\251\375E34\323II\2323Fi)\264\231\256_4f\224\032\\\323\201\247f\236\265 4\360i\300\323\201\245\315&i\300\322\203O\006\234\032\234\032\236\016jPi\340\323\301\247\203N\006\236\032\236\r<5=MH\032\22458\032vh\315.\352Bi3\357I\232L\323I\246\032\214\323\r4\323s\326\2234\023K\234\214S\032\230M%%&h\3158\032z\2659\271\346\230i\206\220\2323Fi\r!4\225\312f\2274\240\323\201\247\003N\006\244\006\234\r8\032x4\240\322\346\214\373\322\203N\rJ\032\234\032\244SR!\251\001\247\206\247\206\247\003N\rO\rOV\247\206\251\001\247\203J\032\234\032\235\2323J\032\202i3Fi3HM0\363L&\230i\206\233\330\323s\315-\004\323I\374i\207\332\232M&h\2434\003N\rO\rIM4\323\326\212(\244=i\246\271,\322\203N\006\234\r8\034\323\301\247\003N\006\234\r<\032]\324n\2434\271\245\006\234\032\234\rH\rL\r<\032p4\360\324\340\324\340\324\360\324\360\325 4\360i\341\251\331\245\rN\rK\232\001\245\315&h\315&i\t\247.0sM+\270dT\0140y\246\023M\355M\'\2323A4\323M&\233I\301\240\373RQ@4\340\324\340h\246\236i\017ZJPh4\206\270\372p\247\003N\006\234\r8\032Pi\331\245\006\235\232\\\322\356\240585.iA\247\203R\241\251\001\247\006\247\206\247\006\247\206\247\003O\rO\rR+S\301\247\206\247\006\245\31585.\352\\\321\2323Fh\315\006\223v(\017\214\212B\233\206sP\260\3052\230zSM\031\240\234\323M74\032LdqI\232J)sN\rK\234\321M4QE\025\306\212p4\340i\300\323\201\245\315(4\271\247\003K\272\227u.\352\003S\267R\203N\006\236\rJ\247\024\360i\301\251\301\251\340\323\203S\301\247\206\247\203O\006\244\rO\rN\rK\232pjPisF\352\\\373\322f\227>\364f\212m\001\261Ms\232\214\323\r0\232nisHM6\212L\322Q\232L\321\232\\\322\203KE\024\235\351h\2560\032\\\323\201\247f\224\032viA\245\315(4\003K\232]\324\003N\rN\rN\006\234\246\245\rO\rN\335N\rN\rR\003O\006\234\r<\032xj\220585<5(4\340iCR\356\245\315.i3K\2323Fi\t\244&\232M4\232\214\323M4\232L\321\232)\t\240s\305%4\234\321E\031\2434\340h\315.i(\242\270\300iA\247\003N\006\2274\240\322\346\214\322\356\245\315\033\250\315(jv\352Pi\341\251\340\324\201\251\301\251\300\323\303S\203S\303T\201\251\301\251\340\323\301\251\003S\201\247\003N\006\235\232\\\323\201\245\315\031\2434f\227>\364g\336\2234\231\244\3154\232a\246\323M!\244\315.i\r\024\037j@3IHi)s@4\354\322\321E\025\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\010\244IDATx^\355\335\211r\343\250\026\000\320\224\373\377?9\256\031\333\261\343\310Z@\002\304rN\275z5-K\010.\227EJ\267\363\365\325\266\353\364@\264\313\364\000\000\334\214\263@\034_L\001\240I{\227\300q6\tc\333\233\037\000\000\264\304\356\276\021:\352\240\016\236o.\222\000\000\000\006\322\301C\014\201<\354\301AM\017\242\313y\023\376Y\367\345\227>\030\234\004\340N\"\000E5\275u\206\303\256\327\nV\336\3470<\241&\'\334\362t\225\266\271T\265\336\356S\352\226T\306\302\017\000\3000\376<\365D>\377\337O\216\272\002\242\310\256l\016=\366\036\272\230:\351Tj\362\234\374\345\345\240\254\376\000\371\305\316\265?\347/^\266\370\301\016\347n\000R\266\244s\347v\024\345\\7G\205\\\200\256\254\217\371\365O\2419\326\260\235z\t\\/\355\330i\360\346\177\215\033\201\200w\357K\217\000+\227\354\267p\257_\233\'\354\223\251\330.=bu\375\372\367\236\001\007\003\370H\262\333\377gI\2537\007+\372!w}a0\251\207\350_\006lv\331B\23473\340E\252\001,Z\233\"#\267\0001\247Oo\033s-\211=\202\177\371\372\236\034\237\232v\322\264\023\243\035. \334\322\033\260\321M\373t\206\310\301\235\241\000\260\311TIE\244\343\246\303!\nx\226\370\343\360\r#\224\274W\007\256\361\275\031 C\221\201.2 Z\300\357\364l%\246\263-\271\037\014h$\351\265\2228\225\0216\240\214\214Kc\306\242\367\252\260J\220G\360Fbrb\372A\022\\\223dnw\374mG\371\3737\345\021\250G\220\362\206\352\377\322\237?\244\377\351\235\340d\313[\261:\004\007\2431s}7w\214\332\365\232\241\0041h\233\224\270\333\022\027G\313\316X\021\316\270\'U\221\002\305]oQ\177\306\375\300\032p+\342~\371L\031\227\231c\207L\0373S\227\237Yc\325\335V\313\317b#\177\267\306I*\t\026t\302\210:*\305\274\231\242\014\330 \3152\022\\Ng9/\357}\340\357\216\177\231\331c\366m\305\373\037\312T\243c\367\010\177<\324O\377\274\350-\376i\272\"M)l\272D\364\362\231jI\210&\202u\323LE\003\325\222\000\024\360\231\274\237G\252\325PU[!\244e\375|?\364\364\360\026\335D\016\323o\004\223g\235\233}\340g\034\021#<\342\324t\256\263wm\345a\272\025^\361\035\324S6N\022\240\227|\310\334E\231\213\317\245\321j\327b\272a>\333\376\356\334\177%pH\225\203\257\262\251\255\177\227\023b\036z\303\327y\211S5\364\376\275I\034\306Wy\251\313}\312U\356K\366\033\264`\324\321\000\037\014\006\306r0\343\017^\276\310\322\014\220T\360\264\032|bn\327\327\032SM\225\032\262\024\263\245\343i|\374[\2034\362\224\032\346v\357{\036\236Y\211\302\006j\352\036\231\262\234\212\215\326\345\271\036\3578\323%\274cC\317\243k\227\361\246>\350\313\357\243\364\333\221m5\014\374\245:\334\366\340A\215\240\037:\374\220\370\360-\215\276\342\342\253\016\300\274\364S{\372\022\311\350\325]\326V\302\370\301G_\214\374\341\214\333\345\346.\346\311\214<\262\3155:\214td\023c\371;1\313\377%\003E\246\305\246^\037\265\316\266\315\030\312F\002\314~|\213\374\354\007\231=\357\371\375\347\350\303\341l8\243Ag\t\014VHH\002\213\312b.\0178EH\252P\205T]u\346\310\'\241\310\216L\225?T\241\325\356\214\314Z\226\034H\200\003\227&TG-h\200Ic\215\350\214mi\"\275\347\305\334\267\020\317\035\203\362n\251\273\224\276\344UM\3347+b\276\n\323o\230\372m\031\214\252\245Q]w]\227\227\320\345O\316WwL\233Rs7/\370\255\262\177\250RLOC\256\247\266\000\000df\303\315\017\273h\316\"\367r\272O\362!!\356\352\347k=\265\005\010`\320\307\020\255A,\375`E\002\014h)\031*\325Vm\213\022\0326t\230\"\317&u\330\264\014\032\233\355C\255\356\\\356M\356\263\335\303\3534\237\353\266:\332h\307\336\216\334{\035\365\212\232H%\000\014-j\276(\305\274\224\334RH\257\313\0371\222*\277\267\257\253\277\273\000\000\241r\274\030]+\362\343\263\217\003\347\261\027H$GR\001\355iy*\270\327\335\252\020\353\362w\001h5\200\337\267\212\377\274 \270\264\333\214\363\265\033\271[\036\267[\373\032\264<\375\1775_}\356\222\366b\322\302\022\253\264n}M\241\247\006y3\224\247\326\256{uDw3\t\250\333\301\016\\\272|.;\347\216\245\260T\207x\351JjG\3126\317<k/\024\237+\027\206\260\020\323\363}V\354\363H]\374| \277\231\020\317\034\242\177\277\263A\235\tP\373l\005\344V\347\334D1\022\240\2544\361n\357/P?*\334h\265\017\230\351\357\303e>\335\313NV\0327\257p\212+\000\220X\242\355\305\314\366\222`\211:\201rtYaf\230\206u\327y\377\372k\0221.\022\340T\242\017\230\010`xf\001\350\310\216\367k\346\200\236D\'@\364\005\324\315x\236\032,\"\2035\027\210`~\200\\\306\332O\233K\022\020\304\303\204\020R0\222\000\000\310\256\350\246\263\350\315\200\342\306z\013\013\345\031c\000D\032\355\021l\264\366\206\312\025\227\327\326$\327\r\240\022R\034>\030\026\000\000\205\005\377x \370\304E\037\277O\341{\362g\212y\337w?\376\273\334N|\232\006P\251r\203\002\000\200\363\331\375u\306\223\347\311&\035\240?\006\327P\002X\014``\r\315U\000\225\351b\013\265s\031\350\242\355\275\320\031\033v&\371\207{93\205=;@G0\034IO;d+\274\271\030\021\207<\267\2033\333B\250A@j\0161\t<\246\272\350\246\006\204\217&\374\364d\262\016\215N%\316\225\252\347u|\363R\245B_\306J\354\261Z\233\226\3301\022\353EeL@{\255\246\362xa]\r\307\233\311y\327\351\001\232\264\222\360\037_\361\360i\373\014*\267\222\000\237\364\367P\002\272;\340\024\240I\177Fw\324JA\027\226\246w\271\360\264\024\241\023\235\3319\025\206\343\200\276Z3k%Yv\265~\327E\355\352\247\271+\211\020\255\237\250\214c\265\317V?\234J\231I-k>\016\3157\340d\342\007\264+j\335\357\226y\234\036\254\216\346\225\017\223\344\377\277\351\201v$i\177Y\025V\371Y\245\n\253\306\'\3354\234\253N\007\312ha\262Y\331\025\267\255\205\340\367C\264\233\367\234\t\032~\216\335\257\333i0?\241\013\362\372\327\006\325O\225\325W\020\000\000v\261\323]t\211y\254\21397\271\2147\317V\364\275\340l\245\0374\251\327V5\347>7\260\2327\327\255S!\347\320\255\327(7\334G\'\003\000\200B<\200\320\213\2507nP\234\207\274A\351x\300L02}?\274\200\257L\337\"\213\032\027\233\002:\034:b@\303\270b7\000\r1\265\225\2623\211v^\006\320\201z\226\250\357\235\223\361l\013f\017R\267\235\t\300nUE\274\252\3120(+\307\340$\000@\303\354%\001\000\000F\343u\036\264\307\270\005\362\363\256x0\226\226f\350*r(0\347\027\270\005\014\342{z\240vV\256\204\022|Q\004\0041p\273w\321\3074A\242\002\0000\252\337w@v\3050,/\203i\225\245+\0053\300\227T\202\202\016\016\267\304SV\342\342\032v\260_Z4`\223a\312\034\0100\260?\213\200\025a<\372|`\257\316\257<\013<\261\345u\275\377O\224a8\225\317\375\024\"\017\250\217MI1u\206\272\316ZAG\254\375\300@.\266\026\000\000\000\003\272.<\013z1\006\000\320\232\205\215\035\314\221.0,\303\037I\000\000\000}\332\263\325\337s\r\320\000\203\033\000\000\330\340wb\017L\337\003\000\000@\223<\322\017N\002\000\000\000\000\000\020\311w\n\003\214\3516\377\373\361b\333\364\037\000\000\300P\274\312\005\000\000\000\000\000\200\221\370\213\242\000\000\000\000\000\000\000\000\000\000\000\000\320\013\337\'\010\000\000\243\363T\000$\364\037\213Ab\036\317\"\"\006\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-9.textpb b/core/res/geoid_height_map_assets/tile-9.textpb
new file mode 100644
index 0000000..5397cb37
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-9.textpb
@@ -0,0 +1,3 @@
+tile_key: "9"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\363<\322\206\247f\234\r(4\271\2434\271\367\243u.h\rK\272\214\322\356\247\006\247\006\346\244\rO\rN\rO\rN\rO\rO\rO\rO\rO\rR\006\247\206\247\203N\rN\006\234\r8\032\\\321\232]\324\271\2434f\214\322f\214\322f\232M4\323I\244\2444\224QE\024\200\3434\235i\r%\024S\250\242\212up\364f\234\r(4\354\320\r.\3527Q\272\2274\003K\2327R\346\224585<5<5<5<585<5<5<5H\032\236\246\244\006\236\r<\032x4\340i\300\323\201\245\006\2274f\2274f\214\342\214\373\322f\214\320M%6\220\365\244\244=i(\244&\200sKM\246\232(\242\212QKE(\024\265\303\323sJ\r;4f\2274n\245\315\033\250\315(j\\\321\2323F\352pjxn)\301\251\301\252@\324\360\324\360\324\360\324\360\325\"\265J\255O\006\236\r<\032x4\360i\300\323\201\245\315;4f\214\322\347\336\212L\321\232\\\321HM%!\024\224\206\222\212i\245\024\264\332i\242\212(\245\024\264\270\245\351Hk\2074\224\231\346\234\r\031\2434f\214\321\2327R\356\243u.\3523F\352]\324\340\324\360\324\340\324\360\325\"\265<5<5H\rH\246\245V\251\003S\201\247\203R\003N\006\236\r8\032]\324\354\322\346\214\322\321I\232\\\321Fh\242\214\323M!\244\242\223\275-\024\230\346\222\233E\024S\251@\245\242\232k\2104\224\206\214\322\203HM&i3Fh\315(4\271\2434f\215\324\340iA\247\006\247\006\247\206\251\003S\325\252@\325 j\221Z\245SR\003O\006\236\r<\032x4\340i\300\323\263N\006\2274f\235E\031\242\224\032Z(\242\220\322\032LqII\336\226\212)\264\204RQJ\005-:\212CI\\I\024\332CIE\024\206\220\322f\223>\364\271\2434\271\2434n\245\006\224\032p4\340i\341\251\341\251\341\252@\325\"\265J\246\245V\251\024\323\301\247\203O\006\236\r<\032p4\340\324\340i\300\320\016)\300\321E(9\245\247QE\024Sh\2444\224b\227\336\222\220\322R\001\212Z)@\245\242\232h\256,\212a\024\323IE\024\332i\246\346\212L\322\346\214\322\346\214\322\203J\r(4\340i\341\251\301\252Ej\221Z\244\rR\251\251T\324\200\324\201\251\340\323\303S\301\247\003N\006\234\r<\032p4\354\320\r.h\3158S\251\324QE\024\204Rb\212LR\320zSh\246\236(\245\305.(\242\212CI\\{\n\214\212a\024\322))0i)\246\233Hz\322Q\2323Fi3N\006\2274\240\323\201\247\006\247\203OSR\003R+T\252\325*\232\221MH\r<\032x4\360i\340\323\201\247\203N\006\234\r<\032\\\322\346\226\234;S\251\302\212)qF)(\"\223\024b\223\024R\032J)\000\305-\024QHM%\025\313\2749\252\355\031\025\031\030\353M\"\230E%4\323H\246\323i\244\346\214\322f\214\321\232\\\322\346\234\r(4\240\323\301\247\203R)\251T\324\212jU5(5 4\360i\340\323\301\247\203N\006\236\r<\032x4\340ii\302\224S\351\324\3521N\242\227\024\224Rb\223\024PFi\264\204RQE\024\202\226\232z\n(\256}\201\035EB\342\241x\3628\250\031\010\250\310\246\221M\"\232i\246\230i\264\207\255%\024QJ\r(4\352Pi\340\323\301\251\024\324\212jE52\232\220\032\220\032x4\360i\340\323\301\247\203O\006\236\r<S\305.iE<S\305<R\323\251qKE\024b\223\024\224SH\2444\224\206\222\212CHh\315\024\231\2435\222\312\r@\360\372UwB\265\031\301\250Y*\026LS\010\3054\212a\024\322)\206\232Fi(\2444\264R\212\\\323\251\342\234*AR)\251V\245SR\n\220\032x4\360i\340\323\301\247\203O\006\244Zx4\360ii\324\341\326\244Zx\247\np\024\264\354QF(\244\"\222\220\212B)\246\233Hi(\2444\224R\032J+0\212a4\326P\303\232\255$\004Ur\204\036j9W\035*\"3Q\025\246\021M\"\230E4\212i\244\242\212)E(\024\352p\247\212x\355R-J\265*\324\202\236)\342\244\006\236)\340\323\305<S\305<S\305>\234;S\207Z\221i\342\236)\330\247\001K\212Z(\244\305%!\024\323M=i\207\255\024\332)\r%\024\332(\252\014\265\021Zk\014Te\210\250\\\346\240e\3109\250\031\010\250\310\250\312\324dSH\246\021M\"\223\024\230\243\024\264\240R\201K\212p\247\nx\025\"\324\253R-H)\342\244\024\361O\035\251\342\236\264\361R\nx\355O\024\361N\024\361OZ\220\nx\024\360)\300R\342\226\2121M#\024\204R\032a\246\236\264\323IHi)\017ZJ):\320\005-Se\250\312\324N8\250XT,\246\242<S\030\217Ja@\325\023\305\351P2z\324M\221\332\223\031\244\3054\212LQ\266\227\024b\235\212P)@\247\001O\002\236\005J\242\244Zx\251\005<S\305<S\305<v\247\212\220S\326\236)\342\236)\353R(\251\000\247\201O\002\235K\212Z)\010\244\246\322\032a\246\032CM\2444\224\206\222\212(\243\025]\205D\302\243e\250Yj\026\250XTL*\"1I\274\212C\206\250\312\343\202*\027\217o#\2450\212M\264\233h\333K\266\215\264\273i\301iB\322\201O\002\236\005H\005<\nx\024\360*AR\001N\002\236\005<S\305<T\202\236\005<S\300\251\024T\200T\200S\305<R\201K\212\\RQM\244=i\246\230i\246\233M4Sh\240\212LP\005\030\245\252\3548\250XS\010\250\331sQ\262rsQ2T\016\230\250J\324l\264\322(\353\301\246\225\364\346\2431\203\315FT\2126\321\266\227m\033iv\323\202\322\205\245\333N\013O\002\234\005H\0058\n\220\nx\024\360)\340S\300\247\201O\002\236)\340S\305<T\212*E\025 \024\361O\024\361J)\324R\021IHi\247\2554\323M0\323i\r%!\024\224QE\024\240T.*\"\274\323\nSJ\212\201\316\r0\340\365\250\23528\252\345i\245i\205j2\264\336E\033A\351HW\324S\nc\245&\332]\264\273iv\322\355\245\333J\026\234\026\234\026\236\026\234\026\234\005<\nx\024\360)\340S\300\247\201N\002\236\005<S\300\251\000\251\000\251\000\247\212x\247\212p\351N\024\264PzSi\246\220\322\032a\351M4\323IM=h\242\212LQ\212\\QQ\311\307QQ\2023\315D\347\346\"\241c\316\r0\25794\205A\250\210\347\035\252\'J\214\2554\255FV\230R\230W\024\017zw\226\010\342\231\266\227m.\332]\264m\245\333K\266\234\026\236\026\224-8-<-8\np\024\360)\300S\300\247\001O\002\236\005<S\300\247\212\220T\213O\035\251\342\234:S\307JQKE!\244\246\322\032Jm0\364\244\246\322b\222\212(\242\212*2\373\206\rDN)\215\202sLe\3151\224\324y\307\024\322\271\2462\344TEqM+\232C\0354\305Lh\361L)M\332iv\322\355\243m.\332P\264\241iv\323\202\323\202\340\323\266\322\205\247\001J\0058\np\024\360)\300S\200\247\001O\002\236\264\361O\025 \247\212x\355O\024\341N\035)GZu\031\246\223\232)\264\206\222\232i\246\233M\242\212m\024QE\030\250\260\001\246\355\004\320c\301\246\225\246\221\305Wd\301\246\036)\206\243jn)@\247b\220\307\232\215\242\305Fc\246\354\366\243\241\301\245\0034\340\224\241)BS\266R\354\245\333N\333F\332\\S\261J\0058\np\024\340)\300S\200\247\nx\247\212x\247\212x\353N\024\360i\342\234)h\242\212)\264R\021M4\303\326\220\322R\021IHE\030\244\305.(\305-T\rRdu\240\270\357L\334\017CLn{\323\0175\023\000)\207\024\306Q\330\3231F(\247\250\247c=E#F\010\250\214x\355Lhw\366\346\232ad\367\251\025r\005<%.\312]\224\273)v\320\026\227m\033iv\322\201K\212v)qJ\0058\np\247\n}<S\205<S\201\247\203N\024\340i\331\245\242\212)1A\024\206\230i\246\220\323h\244\305%\024QE\025L\214\032c=0\266i\003\342\232_\232\003f\230\355Qg4\034\201MV\311\346\236V\224\n\\S\305<-)L\320\023\006\234\321\002:Ub\2066\351\305<.E8-.\3126R\354\243m\033h\333F\3326\322\342\227\024\270\245\002\226\235N\024\352p\247\003N\006\234\r<\032x4\340i\300\361K\232Z(\242\212CL4\323M\242\223\024\230\244#4b\214PE\000R\342\263\013\346\232M0\234S\013S\013Q\277\024\302\364\201\251\373\251\274f\234\r<\nx\024\340\265*\212xZpJv\323\216)\222\306\032>\225]\007j\227m.\332]\264m\244\333F\3326\322m\243\024b\214Q\212ZP)i\324\240\322\203N\006\234\r8S\305<\032x\247\nZPih\243\024\270\246\232i\246\232i\024\224Q\212n1E\024QF+\017\1774\360\334SI\250\330\323sL&\231\234\232\t\305;w\024\3459\247\014\323\303S\325\215L\207&\246\002\244\002\236\005</\024\320\241\216\017z\202X\274\267\247\005\315;m\033h\333I\266\215\264\233h\333I\212M\264b\214QE(\024\264S\251E8S\205H:\323\205<S\251A\245\035i\324S\250\246\221L4\323\322\222\212)\010\346\222\212)1@\353K\\\343\032r\266E)5\0314\332k\032fy\245oZi5\"\032\234t\246\223J\257\212\235d\350EYI\001\353R\214v\251S\223S\252\323Ja\270\244\236=\313\357P \342\244\333F\332B)1F(\305&)\010\246\342\214Rb\212(\242\212u\024\341O\024\361\332\236)\342\224S\207ZQ\326\235J\0058RR\032a\246\232CIE\030\244\"\223\024Q\336\200=)Es&\2054\342i\204\323sMcH)\343\221\212\215\226\225\016*p\374S\013P\032\244Rj\312?\0305:\260\307Z\2327\301\253\321\020\302\234\313\363S\2312\275*\243.\327\305<\n1I\212LQ\212\010\246\342\220\212LSH\244\305\024\230\244\242\212u\002\234)\342\236;S\305<t\240u\247S\307ZQN\247R\021M\"\232i\246\232i1K\212\\sI\212LQ\214\322\025\346\224\016\324\240W.i\005\004\323O\025\0337\245&iA\247\251\024\244dR\005\243\245!\247-N\270\247n\002\234\262U\210\336\257[\311\203VD\300\232\262\010e\025Z\3456\2604\320(\"\223\024b\214R\021M\"\223\024\334R\021HE%6\220\322QJ:R\212x\353N\024\361O\024\361@\353O\035i\302\234)\302\224QM4\323M\"\233E\024\242\214Q\2121HG4Q\326\271L\323Kb\233\273&\232\317\315FM\000\323\363H_\025*6Fi\331\246\223@\245c\201M\363qHe\315H\222U\210\234\325\270\345\"\255\246H\334*\314R\236\206\211\244/\317\245*\034\212~(\305\030\244\"\223\024\322)\010\244\3054\212f)\010\244\246\322\021IJ\0058S\205<S\307Zp\247\322\212p\247\201N\024\340)\330\244#\232CL4\322)\244Q\212Z1K\2121F)\244P\005-q\346\233\324\320\307h\367\250I\246\323\226\245\003\212c\016jU\030\024\356\324\334\323\226\221\307\025\003)\246\200sS\306*\324b\255\306\271\305[\214\225\\T\360\214\346\247\n\010 \365\250\323\344}\265`\014\322\342\214R\021M\"\232E4\2121M\"\230E4\212i\024\322)(\242\224S\3058\nx\247\np\247\nx\247\212p\247\nZ)\010\246\221M\"\233\212JP)h\242\227\024\322)1K\212\343\013f\232[oJc1=i\271\244\247%L(\306M<\nZi\247-\0148\250\217\"\220/52\361V#Ry\253\221\n\266\243\002\254\300\234dT\354\234dT2/\000\216\325,|\201Rm\243\036\324\230\246\221M\"\233\2121M\"\230E0\212i\024\322)\244Rb\224\nZp\247\212p\351O\245\024\360)\342\236)\302\235N\244\305%!\024\322)\244RQE/jZ)\010\244\242\270|\342\202A\2461\246\347\232Zz\361R\nx\247QM<\323\322\207\351Q\nv)\311\311\2558\020\005\025aTT\3123Va;j\354 8\'\265C,xb;S\"\033X\255O\2121HE4\212i\024\334PE4\212aZi\214\216j2)\244SH\244\242\224\np\247\n\220R\342\235O\024\341O\024\361N\024\242\212)1M\"\232i\270\244\242\235J\005\006\222\212+\200-\315\001\251i;\323\205H\007\024\341O\035){P)\017Z\221(z\217\024\247\245I\002\026`kN1\200*\302\324\312*e\025j\014\203O\230g\004Uc\303\203V\227\221F)1M\"\232E7\024\021M\"\232T\0321\306*\006\\\032i\024\302)1E.)@\247\212p\247\322\201O\024\360)\302\236)\324\270\353F3@\024\224\323M4\332\r \024\264\341E\024\230\243\025\347D\363NZx\351KJ*U\247R\203JM\000\322\023\315J\235(\316M&)\247\322\257Z\307\205\031\253\212*d\025aEL\203&\254\'\007\212\224\214\212\254\313\301\366\251\3429QO\305!\024\322)\244Rb\233\212i\024\322)*6\03750\212a\024\230\243\024b\234\0058\np\024\341J\005<\nx\247\212p\247\nQE\024\206\232i\204R\021IE(\024\264\270\342\200(\305\030\2577\034\323\201\247\203N\245\035i\340\323\301\247\nF\342\2054w\247\226\302\323Q\262jZD\\\2775\243\021\342\254\245N\202\245\310\002\244G\253P\363\315X\306EVu\301>\364\260\235\247\006\254\342\202\264\322\264\322\264\334SH\246\021M\"\223\025\031\034\323H\246\021I\266\223\024b\235\212p\024\340)i\300S\200\247\212x\024\340)\324\275\250\305\030\246\232i\024\206\233I\2121KN\002\212P(\305\030\2575\002\236\0058Q\232\\\323\225\252@i\371\305!9\245QK\212\033\221L\037+U\205\344S\200\301\253q\036\225q\010\035iL\276\224\345rjt\255\033q\2003V\2052H\362*\020\207>\365:\036\306\244\3054\255&\332i\\Tl)\204SH\246\221M+M+M+M\333I\2121J\005.)\300P\0058\nx\024\340)\342\234)\300R\322\342\222\232z\322\032i\024\204Rb\214P\005-(\034S\200\245\244\305y\270Z\\b\212B9\2434\341\326\245Zu\024\364\024\204\363N\034\212FL\362)\312\330\353Rn\315O\023\0003\336\245\016O\322\246\217\236\265aMX\213\226\025\245\017J\262\265&2)\0259\351Oh\3062)\000\310\240\2554\214Tl)\204SJ\323\010\244\333M\333HV\230V\220\2554\255&\332\\R\342\214R\342\234\0058\nx\024\340)\340S\200\245\305\024\021M4\322)\270\244\305\030\240\n1K@\024\360)h\305y\2304\354\346\232N)A\315\004S\220z\324\271\300\245\003\024\243\232\223\240\246\232QS\001\305\030\024\030\373\212|C\'\025d\214b\245F\253\0108\311\253p\0163W\2438\002\254#T\312\325\"\363S\371{\223\"\242\t\212R\265\033\naZaZiZaZ6PR\230V\230V\232V\223m&\3326\321\2121@\024\340)\340S\200\247\201N\002\235\212\\z\320G4\204SqHE&)1F\3321F(\305(\024\340)qF+\313\305:\233\324\323\207\024\340sN\024\341\326\234[<S\201\30585\004\322\216\265(<S\224S\311\342\235o\313\223V$\351Dun3\221V\340\340U\3055:\032\225M<?5j\t{\036\206\245e\364\250\272\366\246\225\246\225\246\2244\302\264\233)|\272k-F\303\025\021\300\246\344QF(\333F\332M\264\273iB\323\302\323\200\247\001O\013F=)q\221JG4\322)1I\212L{Rb\227m&(\305\030\243\024\360)qF+\312\305-8R\232\000\251\000\300\240\236\324\243\2123J\032\236\r(5\"5H\r9\217\024\350\016\01754\215O\214\325\250\210\365\253\221\034U\244j\231Z\245V\245\316\rK\024\234\214V\222r\242\230\340\006\351H\303\212M\224\322\264\322\264\334\000ipOALh\237\322\253:\234\340\320\266\254\3434\326\262n\306\241h\335\017\"\220>:\361O\0074\360)vQ\266\235\262\224-8-8-;\003\246h\372R\201\216\224b\220\212LRm\244\305\033h\305\030\244\305\030\245\3058\n1K\212\362\214\322\212~8\245\002\236\006(&\221FNM8\2650\265&\352xzQ\'\255=^\245\022\nz\276jd \032{\216\364\364n*x\233\201W\242|u\251\325\252tz\225[4\254\330\2536\213\274\346\264\320\342\221\316\347\030\243\024\264\306 S0Z\244[|\363R\254[y\247\272\251N:\325#lY\262EZHF\334T2\302\312x\025]\243\335\324Tmh\030t\252\222@\321\036(G\365\253\n3N\tN\331@J]\264\270\243m\024\270\245\000\032B\264\233i\010\244\305*\216\264\230\240\212LQ\212]\264\340(\305\030\257%=jTZy\342\223v(\r\232w\035\351\245\361\322\243/L\336(\337\336\223\314\315H\032\246\215\207J\221\030d\324\350A\251\224`\324\200\3664\231\332\246\247\203$f\256+p*tj\2305L\217O-\232\275b\3406=kQ\024\036\225\033|\214sK\221Mg\364\241\020\267&\254EnX\360*\372[\205\030\"\234b@9\305FD=\360*\264\262 8Z\256\323m<S\326F\223\036\225(\205Xt\346\206\266\300\371j\264\266\233\263\221Td\262 \344\n\215T\241\346\247A\232\227e.\312M\264\322\264\230\243\024\270\245\333AZM\264\205i6\322\250\000\234\322b\215\264\230\243\024b\235\2121F+\3111\315H\274R\232h\031\247\022\0050\275F\315L\'4\302i\246OJE\220w\247\254\303\326\244YT09\247\254\336c\341x\253h\330\034\032\262\262|\2714\261\311\274\373\324\2238\001GsVm[\367d\032\235\0375*\271\035*d\223&\254+S\303\324\360\273\0021[6\363\344\014\365\253C\347\3523A\210t\002\221m\035\317\3355v\033=\277z\254\210\302\014\360\005C-\317e\252\31730\250\031\232\252\276\342i\2715<R\221S\213\254\034T\3510r0qW\002\006Q\300\346\230\326\201\272U9\3541\310\025I\255\3323\2208\247\'5!^)\205i\245i\245h\013K\266\224-\005i1I\266\215\264\230\306i\002\322\355\244\305&)qKE\030\257&\331\212pZk\373Sz\n\214\234\323\t4\302Oz\211\346\003\201P\371\244\236i\371\310\250\217ZQOZz\261C\221W\"\23389\347\275X3\340\343\265Ii \363y5=\311\314\253\212\263\013`}ju|T\252\364\365|\032\230MV#9\346\256F\330\034\325\353yFEn[H\205GL\325\245e\'\370sR\371\321\240\371\216\343\350)>\322\010\302.\rC$\245\270&\253\021\232M\264\322\225\023\307P\024\240)\024\354q\232\221\t\035+F\336^0\335*\364d\036\225ab\0140\302\240\237O\005IQX\3676\215\021\334\005B\215\232v\332B\264\322\264m\243m.\334Rm\244\333AZM\264\322\274\322\343\024\323\3054\232M\324n\245\315\'>\224\273I\257+lRg\216)\204z\324nj\026`*#%D\362Uwj\214\266)\313%?viA\247\006\305(z\221\037oJ\262\254\n\362y\244Y\2126Gj\320\211\314\307y\364\253L\333UO\255>9sS\253\322\371\230\245\216B^\267\355\255\013@\035O\024\004!\261V\243!:\346\256\3013\266\002\212\324\2066#.j\177\335G\313\234\232O47B\007\2654\234\367\243\024b\227\024\322\200\323\014T\337*\236\261\002)D\035\305H\250E_\266\343\025}\010\317<T\340c\336\253\\[\t\201\342\271\313\270\r\274\247\035(\214\206\034S\212\322\025\244\333N\333HG4\025\246\342\220\2121M\300\250\335\200\250\211,x\247\010\031\251\337g#\255 \217\232\221a8\247yT\206<W\224\025\036\224\323\201P\271\250\035\260*\263\022MF\306\241cQ1\250\215 5\"\276)w\322\356\247\006\247\207\251\222BF\321Ly\n\235\244V\345\236\014JE>v\373\253N\210\360*\33251\337\232\265n\200\200Ml\333]\210\343\nNG\245)\273\313p1Wm\246\014>`\010\255{f\207\031R\006:\324\263_\240\033R\253\t\313\036Nj\302>ju\346\237J\r\007\353J\r\024\340\231\247*S\300\301\251\000\366\251\020\340\325\373e\336G5\243\345qQ:\n\310\324\255<\300N+\017i\205\275\252\302:\260\340\322\221I\200)\t\024\231\024\322\342\2432\016\325\031v=\005&\347\364\246\222\347\265\013\003\261\311\253Q[\205\353S\355\013Ma\236\324\301\037=)\341\016)\014f\217.\274\204\212\211\205B\374UW\3115\013qQ\265D\325\013\na\024\323I\232\\\322\356\245\rO\rOI6\266jI\037p\007\336\266l\234yC\236\224\347\270\005\261\217\306\244\216L\324\302LSD\233\237\025~96\000\005Z\215\213\n\265o\t\221\376n\007z\272\322*\r\211NIXp\rL\256OSVcj\265\033\342\254\244\2250z]\364\273\251U\252@i\353RS\200\006\236\242\237\264\324\360\314b\"\265\355\356\026T\301\373\325#GU\346\2040<V%\345\230\311\300\254\326\264pr\271\024\236T\302\233\344\316{R\213Y\217\\\323\276\303.y\3158i\316z\223S\307`\243\250\315Y\032x#!x\2456 \034\025\250\332\311Gjg\223\216\202\220\300~\224\242\020:\322\024\002\220\250\024\224\323M\257\0365\023\036*\274\207\203U\232\241j\214\364\250\332\243\"\243\"\230E&(\305%\024\340iI\251\003.\337z\277gq\225\353\322\234\322\345\252\344-\305L[\212\215$\303\326\234\r\270\014\326\214X\002\255\tx\302\360)\312jej\231\036\254F\365e\036\247G\251\203\232xzxzP\365\"\266jd5.}\351\312\325(5\"\234\365\245#\232\275g(\215\206k\\:\272|\247\232\211\205A%\272\277QP\265\232\366\000\325w\266\307E\240D\000\345)\2050\331\002\246\t\274r)D\002\203\010ZM\341F\007J\202I\262x\246\371\343\0375D\323F\017\024\217.zTe\252\026j\214\275(9\2434\332\362\006\025\013\212\251)\346\253\275DE0\212a\024\302\264\322\264\302\264\322\264\205i\244SH\305(\245\305C<\276Z\222\016)\332d\345\263\223\326\264\201%\271\253\321>\005J_\212\213v\0335\255k\'\356\324\326\2042f\255!\251\224\323\267\372T\250MXF\305YI\005L\263\001R\254\242\236$\247\t(\022T\311%N\222T\242Z\221^\246G\251\224\346\245Q\2322T\3475b+\355\204\014\326\2147K(\031\353S\343\214\212\214\322\034S\n\003Q\030\2114\360\240S\361\212\2574\300dt\252NI\031\rU\244W\316s\232\202\\\2163LRE;y\0244\231\025\02350\265(zv\354\320My\023Uy\033\322\252?Z\205\2050\2554\2550\2554\2550\2554\212a\024\323L\"\232i\264\352\315\324$\302\220*\326\212\204\214\236\225\253\374uj3S\003\305D\347\006\264\355\030\030\226\264\242\224\001\201V#z\260\255R\241\031\353S\254\201{f\227\314$\344S\326CS\243\023S+\021\326\244\022S\303\323\203T\210\325:\275H\262T\213%X\216J\265\033U\225aMs\236\225]\301\007\"\254\332\335la\232\337\212ex\301\0074\034\032a\024\200\322\346\220\220*3(9\025J\347\'\221T\335\231V\242\016O\336\244`\r7g\025\033\014Td\323\r4\232@h\017\212R\365\344\2621\252\316j\026\031\246\225\246\025\246\221M+NX\201\004\261\305Wu\347\212\210\212a\250\3150\323\t\244\243\265f]\246\351\000\255{\030\374\250F:\325\241\367\252t5:\322\262f\255@\031\000\364\253)!\0075r)\263\336\256F\371\251\343 \036j~\033\356\324\253\220=\351y\0075<s`sN\363A\247\007\247\211)\302Z\221f\307z\224M\232\221d\251U\352x\344\305]\212^*e\222\246S\221Lz\205\362\274\325\375:\364\203\265\217\025\246\263\202z\323\314\312\007Z\210\334(\353Lk\324\025]\356\331\363\216\225\037\236Oz\212IK\036\264\306pG5\0162j@\243\034\322\021\201P\265B\324\323\315Fi\271\244&\232My[\324\014)\273x4\302*2)\244Rb\232\335*&\025\023\n\205\252\0264\302i\244\321JzV|\347\367\2035\253f\013F\t\253\241EH\270\251\227\024\375\352*\304rdd\364\246Ip\027\201V-\244\316+J90\005YG\3175b9MXY)\333\363F\352xjxz]\364y\224\tjT\224\325\210\336\255\306\331\251\324\212\263\033U\205z\231d\305!|\232q\033\326\230\243\3139\025g\355g\030\357A\271\'\2754\316i\245\311\245\016i\333\251\214i\264n\300\342\223\314\315!zal\324Li\264\323\315Fx\246\223M&\274\265\2522\264\230\3050\2550\2550\2554\212c\n\205\252\027\250\036\241ja\244\245\002\234G\025F\346>sW4\351\2066\261\300\025\246\010<\002)y\024\354\232P\244\375*O7h\300\252\345\213\311Zv\274\001Z\010\365z\006\335\305Y\300\035)\003\324\210\331\251\001\247\006\247n\244\337@|\323\3075\"\266*\302I\212\263\034\265a$\253\t-X\216J\225_4\375\325\"59\2153\275<c\024\022(\016)\300\323\267SI\246\223M&\230M4\265!4\231\2444\303Lja\024\323^`E \000R0\310\342\243+\212a\025\033\na\250\232\241j\205\352\006\250\310\246\221M\3058\np\\\324\0271\344b\250\020\361\234\212\261m~\361\261\3632kF\rAX\363W\005\302\205\310\002\241{\242N)V\\\212\236\021\223Z1\014\n\262\215\212\265\004\2705o\315\342\220?5\"\311R\211)\302J]\364\273\350\rS#f\235\272\244F\2531\275XW\251VLT\361KV\222Q\353R\t\005J\262S\367\344Rf\205\223\024\374\203M\350i\333\351w\346\215\324\322i\244\323\t\246\346\2234\271\244\242\242q\212`>\264W\231\225\246\221M\"\230\302\243\"\243e\250\332\242aP\260\250\\T,)\204SH\244\333J\026\244\013\212\216E\315Wh\001\250M\267=(0\024<U\310\203\005\000\232q\214\346\247\211=j\324\177)\253\321\276EN\246\246F\305N$\247\207\315<=J\257\305/\231N\363)C\323\203\324\321\2658\2775,oVQ\252Q%<IR\307.OZ\264\254}jt~9\247\2111R\ti\342L\364\245\316i\341\360(\363)\013\322\206\247n\244\335M&\220\232J(\244\006\202qMnj\"0h\2578+M+L+Q\260\250\330sLaP0\250\330TL*\026\025\021ZaZn\3326\323\325i\032\243\306z\323X`PW\214\323H\311\025\"\234S\267\323\225\352\302\034\325\250\016*\342\232\231N)\341\205<0\247\253T\201\2517S\267\323\325\362je\347\255I\274(\244W\311\251\325\361R\254\2250~)C\346\244W*\302\256\307>@\251\204\331\2453zS\322\\\324\352\365 z]\364\240\320[\024\t1N\017\232]\324\205\251\273\251A\247f\226\233Fr)\244\342\230Ni3^zV\232V\243+Q\262\324L*6\025\023-D\302\243aP\262\324ei\214\264\335\264m\243\024\322\271\244\333\212\215\305+\034\001L\003\234\324\200R\021J\2654mW\"j\266\207\"\234d\305\002\\\324\252\365a\017\024\354\321\232P\325\"6*p\331\034SY\215*\275J$\251VJ\235d\342\236\262`\322\274\26543\347\203S\211H\247\2113S#\342\246\022T\202Zw\235@\233=\351|\312O2\234\262{\323\304\224o\245\017OSO\006\2279\244&\243\'\232B\324\302i7W\020R\243+Le\250\231j&Z\211\226\243e\250\231j&Z\211\226\230V\230R\233\262\223m&\3326\323J\324,\274\323\030f\200\265 \024\021J\0059\001\'\212\267\020\351V\323\245)\024\321\301\251\220\324\352\324\360\324\354\321\232p\353\305N\206\225\31538\247\253T\252\325*\275J\0374\355\331\251\"85d7\024\252\325:\311\305<IN\022\342\227\315\245\363h\023R\211sR\007\247\t)\341\251\352jEj\2245.\3523Q\263S\t\315F\317\212a\222\271r\225\033%D\313Q2\324L\265\023-D\313Q2\324l\265\031Jc-FV\232V\233\266\232E&)\010\250XS6\322\355\247SM\024\36485n6\351V\220\346\224\232eH\271\251T\324\200\323\301\247\212p5 <SK\346\220\032x5*\324\200\324\210jPjDlT\241\251\301\252P\334S\303S\267SK\342\215\364\241\251\352\3252\275<75\"\236j`i\331\251\025\251wQ\277\212\214\266i\204\342\242w\250KV;-DW\332\243t\301\250\231*\026Z\211\226\242d\250\331*&JaJ\215\222\230R\232\313L)L+M+L\"\230V\233\266\223\024\230\246\221F)EH\214E[\211\315N\334\212h\251T\324\213\315H\005H\005;\024S\351\273y\247\001O\002\245Zx\251\024\342\244\006\2245J\255N\335\315L\215\232x4\271\246\226\244\335N\rR+T\212j\302sS(\342\234\033\265\005\251U\351\333\3517R\347\271\250\335\352\026j\210\265Qd\250\331j6N9\353P\262TL\225\023%F\311Q2Tl\225\031J\215\222\230R\243e\246\262\324ei\205i\245i\205i\214\2704\322)\204b\214Rb\235\212U\025:\034T\313\'cO\034\324\212*U\251T\324\200\322\356\245\3158\0323NZx\247\347\024\241\251\301\351|\312<\312\225$\251\224\346\246V\305H\032\224\2654\323i\300\323\301\251\243oZ\266\203\214\324\201\261N\006\224\234\324d\340\323\203f\234\010\035i\035\270\342\240g\250\331\251\205\251\205j\"\231\2462\324,\225\033%D\311Q\262TL\225\033%F\311Q2sL)Q\262S\nTei\205i\245i\205*7^j2\264\322)1F(\305(\025\"\324\212\271\2531\245K\267\024\n\220\032p4\341N\006\227u\004\320\254jA%.\362i\273\311\247\253\023R\014\232xZ\225\026\246\034S\267b\234\257\315)j\004\225 `i\331\245\006\244C\212\266\217\305K\270S|\316iZN8\244\031\357J[h\250\314\246\2173\212c5FZ\2235;&j6J\215\222\242d\250\312Tl\225\023%F\311Q\224\250\331*\"\225\031J\215\222\230\313Q\262S\nS\nSJ\324L\265\033%1\226\223m&\332\n\320\026\236\005J\202\254\245I\232\000\3158\nv)\302\235M4\240\323\205\004\322\203N\006\236\rH\246\245SN\335\212Q%;vi\312i\371\315\000T\200T\213\315?\024\341R\253\340T\201\263N\343\326\234\010\245/\212k8\"\240\335\315.\352Bi\231\2435\242R\243)Q\262TL\224\306J\210\245F\311Q\262Tl\225\033%BR\243e\250\331*2\224\302\225\031ZiZ\214\2550\245F\311Q\272\323v\321\266\215\264\233)\301jE\030\251\024\342\237\232@\3305:\020\325&(\002\226\212a8\243u(4\340i\324\3655\"\324\253N\306h\332i\301H\247\250\247\347\024\241\252e\251\007\255H\2434\360\224\275\005*\265\005\350\3631A\223\"\233\272\233\236i\331\244\3154\320\rm\024\250\312Tl\225\031J\215\222\243d\250\331*6J\211\226\242aQ2Tl\265\033-D\313Q0\246\021L\"\233\266\232R\243)Q\262S6Rm\245\333F\332\002\323\202\323\302\323\266\322\025\247\247\006\254\216E8\n\030T\014H4\204\346\201\326\234*E\024\354S\324T\310*P8\243<\324\213Rb\214SM*\324\240\342\244\00752\n\224P\335\351\203\212k5F\033&\235\232M\324f\234\0334\271\240\322W\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\006\"IDATx^\355\335\333\266\342(\020\000\320\263\234\377\377\344q\315x;G1\367\020\002\324\336Om4\tP\005\001\264\273\177~h\307%=\000\000\000\000\004`G\000\000\000\310\315:\003\000\000\000\000\000\000\000\000\000\000\000\000\000\000\032\345/\013\003\000\000\000\000\324\315>.\000\000\000DdG\000\000\000\000\000\000\000\000\240]~\373\001\000\000\254b\021\001\000\000\000q\\\323\003\204\364\177\036\\\345\002\313\330=\214N\006\000\000t\3114\017\000\242\2623\314\274\213\311bl\022\000b2C\210I\334\001\000\000bJ\326\203v\004[\267r\201\177\013\370\312S\250\232h\002\000\000\000@\\\266\370\001\000X\301\3641\256{\354%\000\000\000\324+\347\277\362\221\363Z\344\347\267\337\214\320s\203\223\000\020\227\376\017\000\221\331%\000\350\226!\036\200\233\322\317\203\322\367#\221\006 }\235\231\315e\000\000\000\200\340\016\336\177\002\000\000\340 \326s\237\242\374\000\"S\3343]\006\000\000\000h\202\235\000\000\000\200p\236\337\241G\371*\2357\266\001\240\t\272*\000\333\230\341C([\273\374\326\363h\335\353?\233\277Zo\004\'\001\242\223\001\301I\200,\314\246\000h\215g\027\000\000\000@\313\354\356\000\000\000S\374(,\2364\346\351k\000\000B0\r\004\010\353\342+\344\310D?8\t\000\000\020\203\255\277\350~3\300\022\000\000\272c\246\307:2\006 \212\333\026\200Q\237\317\034\2601\004\000\315\362\030\007V\353vE\330m\305 \247\367\216\242\323\304!\326\335\261\n\000\000`-s\310\270\304\036\"3\002\000\000DgF\030\214\357\205\203\223\000\020\217\'=\000\360\306\324\240m\342\007\000\360db\024\234\004\240i\333\276\255\333v\326\235\036\323\236#b\266#\205\340|Gt\n\000\200\354rNZ\314\340\033\2645\001\266\2367\355\230\253RJ\215C\200\234\002hC\215\317\020\312\362\314\216N\006\004\367L\000y\020L\032\360\3645\264\3004\026\200\332\205\231c}U\364\353\000\347\021\014\010n|\335\264kx\030\277,\237\216j\251\r\341\033:\345\250\342\0015\032\032\005&\031\"j\366\027\316j\343\264:\343\350\313\241\tp\350\305Y\244\332\241\007z\226\253\343\355\271\316eh\004\23696\3646m\022\313S\355\351\2720+C\202e\270D.F\253\315\036Q\254(\226\034+\355+[B\177;g\313yT\350-\220b\332\274\264{\007P.k\017j\334W\005\376*\222\365F\213/\366\371\301\301-\200\345\336\316.\027\240\266\034\325.k\003\267\366\363\017\327G\361/\267?\354\314\025\010\355\264\356\263q\010z\225\367\275\334\317\361\000\210\313(P7\361\251J\301\'\377\\\3443\025e\3566a\2156L\246\206_\351\25589\013\220\363Zg\030\215\322N\023\355r9\352\236s&\312DnU5\366\2430\037\313\267\267?\347UU\305\3330\030\214\301\203\313\315\237\2763P;O\347\333|\314\026\273G\347~\275#{\375d\016\330!Z-\177\213\215E(\377\235\330\357\370\250<\362\341\370\373\260\322XG\205\355^Yu\371\353\363\217I\301?\333\307\200\3475G\022\366\357pz\203\221\023\342\032i\220\221\303\204\223\366\240\212\205N\332\232\3434X\266\301\203\031\035}\375\316u\333|\335V\354t#-;r\370\347\347\237\364@)\243%\"\261\252\245^_*\255:\351h\203\205\031<\310\200\365-\025z\026\326\201\201\345\372*kN\272\337+G\302\244\327x\177\235\276\307\264{\000s5Z\235?\323/V\250\341\316P\354\366\333\014\027z\205\327\267n\225\327\223#}\007\377\324/c/i\201\316,\014P\232\036\177\256\357\'B\020\022o\204\206i\317\242\230]\236+\277\267\017?W\202\'\016\002\023\267\236x\213E\021\037uo\332\312\332w\2668\373\252\314\237\340-9\233i\255Z\\\261SW\375;,,\366X;,<=\214z\333cc\311\346N\273\275?\367\231V\215%}O\216\036\267\016\276|niq\353\374b\'\217\216\253\266]\232\000[i\3346=\306\303g\364\326\006\361\231<\327\333E\326\236{\214\301ei\372z\314\327\227I\237n\225\374\374\300\324\253Pj\257\372\\\371\026\217\202\237\027\372=m\361\371\375\231k\332>\254\255\345\332\317\323\210\300=}Xo\r\322[}8\214T\201\272\035:\021\233\272\370\324{\034cn@\236{\177\316i\273\243\005\357[\360V\345$\201\337\233\007\257m\263\304\3201j$Rls\037\036\273\034#\013i\275\353\365\036\373\326\343\303N\317\004\350)\017~\353\322S\245\250\305\340b \210\337\252\367\376\\<\303\231y\2253\251\037\227\272\234[\241\r\032+.p4\203B\007\0041<)\000\254d\235\373-c\233\344\\v\316\333R\360\353\343\311\361*g\321\362\226\266\265r[\317k\307\226\314\351[\3771gJ\323\361o\272\360\203\014P\253\364\227\000\265Jfx\265\344iw\t\320]\205*QK\302Nk\243\2244\301P\302\"i\242\224\032\2052\335\'\323ez\244i\356\322\004\207\030\226\016\000\317\317\351(\301I\000\000ZP\366\207\007wK\247T\243N+\362X\311G\217\217\275\001\264\245\374\250CU$\000\363<\362\001\272t\375\367\371C\370e\032~\032\254\250e0\227[\323\274\375\017\022\327\206\243\014l\363\335\355\215\231\000\224\366\3754\002\330\302L\026\240\010\303-\000\205y\364\000\000\204\347\357-\001\000,c\332\004\020\234\357T\000\000\370f\273\200A\022\003\000\200\305\354=\003\000\000\000\000\361\330\031\355\204@\002\000\000\300\371\254\317\001\000\200I\345\376\322\263\345IY\325\265wu\005\nF\373\003\000PX\271\325f\353\252\233\254WW \000\000\000\000\000\000:\344\013E\000\000\000\000\000\250\312\177]O\370\366;\244\233\330\000\000\000\000IEND\256B`\202"
diff --git a/core/res/geoid_height_map_assets/tile-b.textpb b/core/res/geoid_height_map_assets/tile-b.textpb
new file mode 100644
index 0000000..b04a194
--- /dev/null
+++ b/core/res/geoid_height_map_assets/tile-b.textpb
@@ -0,0 +1,3 @@
+tile_key: "b"
+byte_jpeg: "\377\330\377\340\000\020JFIF\000\001\002\000\000\001\000\001\000\000\377\333\000C\000\004\003\003\004\003\003\004\004\003\004\005\004\004\005\006\n\007\006\006\006\006\r\t\n\010\n\017\r\020\020\017\r\017\016\021\023\030\024\021\022\027\022\016\017\025\034\025\027\031\031\033\033\033\020\024\035\037\035\032\037\030\032\033\032\377\300\000\013\010\002\000\002\000\001\001\021\000\377\304\000\037\000\000\001\005\001\001\001\001\001\001\000\000\000\000\000\000\000\000\001\002\003\004\005\006\007\010\t\n\013\377\304\000\265\020\000\002\001\003\003\002\004\003\005\005\004\004\000\000\001}\001\002\003\000\004\021\005\022!1A\006\023Qa\007\"q\0242\201\221\241\010#B\261\301\025R\321\360$3br\202\t\n\026\027\030\031\032%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\203\204\205\206\207\210\211\212\222\223\224\225\226\227\230\231\232\242\243\244\245\246\247\250\251\252\262\263\264\265\266\267\270\271\272\302\303\304\305\306\307\310\311\312\322\323\324\325\326\327\330\331\332\341\342\343\344\345\346\347\350\351\352\361\362\363\364\365\366\367\370\371\372\377\332\000\010\001\001\000\000?\000\310\315-\004\346\212(\245\315-\024\003O\006\234\r;4\273\251sFh\335F\352v\374\014S7SKRn\243}(jP\364\340\364\341%<I\357N\022R\371\264\276m;\315\367\245\022S\204\224\3572\227\314\243\314\243\314\251c\2235)l\212Bx\254\373\221\345\266}j5|\323\213SKS\013Rn\240\265\033\2517Q\272\233\272\224\034\325\210\370\024\354\322n\244-\201Q4\300TfqQ\231\3523=!\270\250\332\350Uv\271$\325\243I\232\\\321\2323Fih\315\024S\201\247\003K\232PisK\232Bizu\246\226\246\226\244-I\272\223u.\3527\322\357\245\017K\346R\371\264\242JQ%H\254M<\023N\334iw\032]\364n\243}H\222b\247\022\344u\240\311\216\365\005\313\007_\245T\007\322\235\272\230Z\232Z\233\272\227u\033\2517Rn\240\232\222>\265eM\014@\250ZP;\325y.3\300\250\014\204\323K\323\013\323\013\324l\365\0235D\317\212\326\242\212(\242\2123K\232L\322\203N\315\031\245\315.isK\232\032L\214S7SKRf\223u!jM\324n\243}\033\350\337F\372pzp~j\302?\025&\372M\364o\245\337K\272\223u(z\221d\247\027\310\252\357)\246+f\224\265F[\024\205\2517Q\272\214\322n\245\rAl\232\2323\201Ry\230\250%\237\035\rViI\357Q\226\315&i\t\2463Tl\325\031j\215\232\242c[tRf\214\321\232J(\315.h\315-\031\245\315\031\245\317\275(4n\3054\2654\2654\265&\3527SKRn\244\335I\272\215\324\233\350\337OS\232v\354S\326lT\202l\322\371\236\364\242J\220IK\276\215\324\233\251\301\351\346L\n\202Bv\223H\207\212R\325\03357u\033\250\335F\352M\324\273\250\335R\tp)\217)\250K\023IHM4\232i4\302\325\033\032\214\232\214\232a5\273A\246\321E&h\315%\024\271\2434\264f\2274f\214\321\232ijajn\352M\324\233\251\013R\026\246\226\243u!jizM\364\365\233\024\246\\\322y\224\242Zp\232\244YI\357R\254\224\361%8=.\352pj\035\270\244V\334\244Ss\216)\245\251\205\251\273\250\335J\032\215\324u\245\346\212Bi\271\244\315!4\204\323I\246\023Q\223Q\261\250\311\246\023L&\272\n\r6\212L\322QHh\315\031\244\315(4\271\2434\243\212L\346\202i\205\251\205\251\245\2517f\220\2650\265&\352izM\364\205\351\205\351\273\350\337K\346R\371\224\273\351C\323\226J\235e\315J\036\236\036\234\036\234$\244y:sJ\222\000iX\367\024\302\324\302i\271\245\315.iG4\361J[\212\215\232\2234\204\322f\222\220\232a5\0314\302j64\302j2i\244\327EHi(\246\321E!4\334\321\232L\322\346\214\322\346\202\324\200\322\026\2463S\t\246\026\243<SKS\013SKSKSK\323\013\323w\322\027\244\337F\372<\312]\364\340\364\242J\231%\305L\262\324\213%?\314\364\240=#>M9^\244W\310\347\2654\2674\322h\315-.}i\333\370\342\223u\033\250\244\315!4R\023L&\230MFM0\232a5\0314\323L&\272J\017Jm\006\233E!\246\232JL\321\2323Fh\315\004\320N)\244\324d\323I\3053=\350-Q\226\246\026\246\026\246\226\246\026\246\226\246\026\246\227\244\337I\276\200\364\355\364\273\351|\312Q-H\263\221\336\247I\263R\254\224\340\364o\346\244W\247\253\340\020)7R\356\245\315.h\006\2274f\224Q\232L\321\326\202i\244\323\t\250\311\246\023L&\230M0\232a\246\232\351h\2444\224\323E!\244\246\322\032J)3@4w\244&\232M0\232c\036\324\204\323\t\246\023Q\263Te\251\205\251\205\251\205\351\245\351\205\351\273\350\337I\276\227}.\372<\312]\364\241\352T\223\336\254$\324\363/\245*\311\236jUz\2326\371I\245\315(4\340is\315.is\3158R\346\220\232\000\240\232i4\302i\204\323\t\246\023L&\230i\246\230M0\327OE\024\204b\222\220\212i\244\244\"\230h\244\244<P\017\024\236\364\204\323I\246\023L\246\223L&\243cQ\226\250\331\2526j\214\2650\2650\2750\265&\372iz7\322o\245\337F\372]\364\340\365\"\275H\257R\253f\246CS\003R\253\343\212x4\360isN\006\2279\247\201KFi:\322\346\2234\322i\204\323I\246\032a4\303M4\323Q\232i\353]0\245\242\212LRR\021\212i\246\232a\246\232)\t\243\266(4\323LcQ\223\232Bj64\302j65\023\032\214\232\211\232\243-L-Q\223M&\220\232ijM\324\233\250\335F\372]\324\360\325\"\265J\246\246SS\306\325:\234\014\232\"$\2615`\032p4\360iA\315H\264\354\322\023Fh\315\031\244&\232M4\323\t\246\232a\246\232i\246\032a\246\036\365\324QK\2121IHFi\r4\212i\024\303L4\206\232i\302\203L&\243<\323I\250\330\323\t\250\330\324Lj&5\0335D\306\243&\243&\232M0\232B\324\322\324\334\322f\214\322\346\224\032\221MH\246\245Z\231jelu5 \334\374t\025:|\243\025(4\360qN\0074\360i\333\251wQ\272\2234\271\2434f\232M4\232i\246\232i\246\236\264\303\326\232i\206\232k\247\245\002\226\212)1IM\"\230E0\212a\246\232h\031j\220\323\032\243cQ\261\246\026\250\313Tl\325\033\032\211\215F\306\242f\250\311\250\311\3150\232c\032a4\322i\244\322\023I\232)sN\006\244PML\213\212\235EJ\242\246LT\240\323\303T\212qO\rN\rN\rJ\032\215\364\273\251sJ\r.h\315%6\233M4\206\232i\246\230i\244S\ru\000R\321E\024Rb\220\212a\024\302)\204S\010\305\"\214sJM0\232\211\215BO4\306j\214\265F\315Q3Tl\325\0335DZ\243-L-L-L-L-L&\220\232Bh\315\002\236\005H\270\025 j\221Z\245V\251U\252Uj\2205H\255\212v\372P\364\340\364\340\324\355\364\241\251\300\323\201\245\006\235\2323HM%%!\246\232CM4\303M4\303]E\024\nv)\010\243\024\224\206\232i\204S\010\250\330Rt\246\223Q\261\250]\252\"j2\325\0235F\315Q3Tl\325\0235F\317Q3\324e\351\205\351\245\351\245\251\273\2517Q\270R\027\002\200\364\242J\220=H\255R\253T\252\325*\275<IR\007\305;\314\247\007\247\207\247\007\247\207\247\006\247\203O\006\234\r8\032vh\315\031\243\024b\222\232E!\024\322)\204S\r4\212\3521F)h\242\212)\246\232i\244S\010\246\021\315Fj65\023\032\205\2175\023\032\205\232\242f\250\231\2526j\211\232\242g\250Y\3526z\214\2750\2754\2754\2757}4\312\0057\315\317J\003\323\203S\203T\201\252Uj\221Z\244\017R\007\247\207\247\211)\301\351\341\351\341\351\341\252@\324\365j\221MH\r<\032p4\340ii@\245\243\024b\232E!\024\302)\244S\010\246\221]=\024R\342\222\212(\246\221L4\302*6\3435\0215\033T.x\250\030\324.j\0265\023\032\211\232\242f\250Y\252\026j\211\232\243f\250\313S\013Te\351\205\351\215%G\270\232z\265(l\323\303S\324\324\201\275\352Ea\232x\223\322\236\257R+\032P\3478\024\375\304S\225\352A%H\032\234\034\346\247V\251\024\324\252j@jAO\024\361N\002\234\005(\024\270\244\"\220\212i\024\322)\204SH\246\021]-\024\242\226\212CA4\224\207\2554\323\010\250$<\324MQ1\250\\\324.j\0075\003\032\205\215D\306\242cP\261\250\035\252&lTL\325\031jaz\215\232\243g\246\023M\3159I\251\001\247\255H\r.\352p$\324\203#\255J\204S\214\224\345\222\245\022\003\326\234\007\245<T\252i\370\364\251\025\252u5*\232\225jQR\001O\002\236\0058\n\\R\342\214R\021M\"\232V\232E0\2554\255t4\240R\321E!4\224\231\244\246\223McU\334\345\252&\250X\324\016y\250\234\325w5\003\032\205\215D\306\242f\250]\252\273\265B\306\242cQ\026\246\223Q\226\246\023M\335\353H95*\2169\247\n\220t\251\025sO\333\266\233\346`\361O\014M.\354Q\27352\014\324\350\265a\022\244\333\353J\023\322\237\202)\313\326\247Z\235*u\251\024T\240S\300\247\201N\002\234\005.(\333F\332iZiZiZa\024\322\265\277E\024\334\321Hh&\222\220\232i\2461\252\354y\250\330\324,j\274\206\241sP5B\365\003\324Lj\0275\003\232\201\215D\306\241cQ\023M&\230MFM&iG\006\246S\232pZ\225H\305L\224\366\\\216*\002\2304\340\330\244\335\232\221Fj\304B\256\304\231\251\200\305<.iB\363R\252\344sM)\203R\240\342\246Z\235*e\025*\212\220\nx\024\340)\300S\202\322\355\244\333HV\232V\232V\230V\230V\266\350\240\323h\246\232)3Hi\244\324NqP1\344\324lj&\252\362T-P\265D\302\241aP\270\250\034Uw\025\003T\017Q1\250\311\246\032a\024\302(\301\245\305J\275*E\346\236\006\rJ\247\025(a\212\211\332\241\316M=x\251\343\346\254\307\305\\\215\300\025 l\232\260\203\212SOC\232\221\223\214\322(\305L\242\246AV\024T\252*@\264\360\264\360\264\360\264\340\264\273i\n\322m\246\225\246\225\246\025\246\025\255|RQHz\322\032m!\244\2444\302j\'<T\016pj65\023\032\205\352&\250Z\243aQ0\250\\Uw\025\003\212\256\342\253\270\250\231j\"\264\335\264\205i\002d\323\304x\245\362\351v\323\325qO\333K\332\230X\212o&\234\006)GZ\265\020\251~\225b%&\255\"\342\246\007\002\234\243uXH\352m\234Sv\324\212\265*\n\260\202\246U\251B\323\302\323\302\323\302\323\266\321\266\215\264\233i\245i\245i\205i\205kO\024\204RR\032Jm!\244\246\232a\250^\241z\214\324MQ\265B\325\033TMQ\232\205\205@\342\240qU\335*&J\215\2435\023Di\236Y\357Hc4\2011N\000\367\245\002\224\nz\2558\256ivf\223\312\2441\342\230i\312\271\2531-YT\315Z\215@\251@\251V2j\314Q\342\255\004\000P\027\232\177\223\306E*\307O\013\212\231\026\254*\324\241i\341i\341i\341iv\322\355\243m&\332B\264\322\265\031ZiZ\277M\2444\230\246\221Hi\246\222\230i\215Q=B\342\242j\214\324mQ5F\325\023\n\215\205D\374\032\205\306j\022\264\206,\216j3\020\035\005D\321f\243x\261P\025\305&\332B\224\233iB\322\204\247\205\247\205\245\002\206\342\241v\250\361\223S\306\265:\220*d5e\rX\214d\325\330\323\212\231V\245U\342\227o5<c\326\234W\024\201jdZ\260\253R\205\247\205\247\205\247\205\247m\243m\033i6\322m\246\225\246\025\246\025\253dRSM6\220\322SH\246\323Z\243j\215\273\324M\322\242aQ\021Q\260\250\330TL)\214*&\025\013\324{\t\240\246)\205i\276]4\245E\"qUY2i\2451M\305&\332]\264\241i\301i\333i\002\320W\212\201\322\232\253S\242\324\351\0215f8qRc\025<\0035\241\030\300\251\325sS\250\247\204\315H\251N\307\024\212\2715aW\0252\255J\253O\013O\013O\013N\013F\3326\322m\244\333HV\230V\243e\251\373\320E0\212B)\010\246\221M\"\232E4\212c\n\215\205B\325\021\250\330TdS\010\250\330TMQ0\250\266n5(@\242\241q\223L\331K\266\242q\212\201\305W\"\243aL\331K\266\227m(Z~\332\002\323\212TL1Q\260\315\013\0375f8\352\322\000*a\216\324\205sV-\327\006\264\020T\350*U\025:\255<\n\220G\271iV<S\302\346\246E\251@\247\252\323\302\323\302\323\266\321\266\215\264\233i6\322\025\246\025\250\312\324\204z\322\032i\244\246\342\220\212n)\244SM1\252&\025\013\n\215\205FEFE0\212\215\252&\025\031\024*c\232k\nn\314\320S\024\302*\027\025\003\255B\313Q\225\244\333I\266\234\022\227e<&i\342*y\217\212\201\342\246\010\252E\212\246T\247\2044\365CR\355\251#\030\253h\3252\275Z\213\232\265\032\346\246\020\232\231#\300\240\2474\241qR\001R*\346\245\013R\005\247\005\247b\227m&\3326\322m\246\225\246\025\2462\320E4\212a\024\224Si\244b\220\361Q\232cTl*6\025\023\n\214\212\214\212c\n\210\212\215\205\"\246\346\251\0311\232\204\2574b\230\302\243e\250Yj&Z\211\226\243)I\345\322\354\245\tR\2549\251\026\034S\366`S\010\244\362\363M1\342\224%H\221\022j\312C\221R\255\267\265I\366ojQm\216\324\361\021\251\022#\232\275\024\\\n\273\004\\\325\321\030\305/\227Ha$dTe1NU\251\225jP\264\365ZxZv\337Z]\264\233h\333HV\232V\232V\243+Qb\220\323H\244\246\323i\r4\323M0\212\215\205F\302\242aQ\260\246\021Q\260\250\310\246\025\247F\270\247\025\250\231)\230\2468\250\215F\302\243+\232\215\222\233\266\223m(\214\232zE\315ZH\270\240\250\024\322*\"\274\323\325i\nd\324\211\025N\221b\254\307\035YH\200\251\2260jO \021\322\233\366nzT\211m\203\322\255$X\025*\361S\246MJ\005H\203\"\242t\346\220%J\253R\252\324\201i\341iB\321\266\215\264m\244+M+L+L+U\351\010\246\221M\"\232i\246\232i\206\233F)\214*&\025\031\024\306\025\031Z\215\226\243\"\2435\"/\002\244\333Q\262\324%i\214\265\036\312\215\222\231\262\220\2453\313\245\021T\213\020\251\004x\247\355\2462\324dSv\344\324\251\036jA\016jd\207\0252\303S\244X\251B\032\22649\253h\234S\304U:CNh\261Dp\222j\332\333\340t\250\331pjh\327\212\215\226\220/5\"\255L\253R\005\245\013K\266\227m\033h\333M+M+M+L+T\361F)\244SH\246\021M\"\223\024\326Zn)\010\2460\250\310\2460\250\312\323Yx\315D\302\243e\250\314D\236\265(B)\300R2\361P\025\250\331j<Rl\315!JB\224\337.\234#\245\013\212~))\215Q\021\232UJ\263\032U\224\2175*\305Vc\213\332\246\021S\2045*E\212\235R\245H\352\312G\201N\362\362jx\240\002\244u\300\252\254\274\323\324`R\025\246\355\346\244U\251\225j@\264\241iv\321\266\227m&\332B\264\322\264\302\264\302\265G\024b\220\212i\024\302)\244SqHE0\322c\212a\024\302*2)\245i\2733Q\025\244\t\223La\264\342\244\333\3057o42\361P\225\246\024\315\'\223I\263\024\306Z\214\212P)M%!\246Q\214\322\210\352E\212\254G\035ZH\352\314qf\255$5*\303O\021R\210\361R\254u2&*`\265\"\'5aW\002\230\353\232\204\245.\332M\264l\247\252\324\212\265 Zv\3326\322\355\243m!ZB\264\322\264\302)\205k?\024b\220\323H\246\021M\"\233\212\010\250\310\244\3054\212\214\2557\024\322)\204SJ\323v\323\014yjy\030\030\246\201JW\212\211\226\230\026\203\214TLj6\250\361F1H\0015 N)\254\265\021\030\245Q\232\260\221\346\254$5:\307\212\235#\315[\212:\270\221\324\242:]\224\361\030\247*T\241)\352\2252GRc\024\326Zf\312M\224l\243m8-=V\236\005;\024\273h\305\030\244\333HV\232V\230E0\255f\342\222\220\212i\031\244\"\230E7\024\204S\010\243\024\302)\245j2)1HV\233\266\233\266\225S\223H\313\3157m\005j6Zc-@\347\025\021\250\230\322\016i\304S\220\n\221\230\001P3f\230FjX\243\315\\\216<U\224Z\231S5f8\252\334q\212\262\211S\254|S\035v\322)\251\000\251TT\250\234\325\205N)\n\342\220\255&\332n\332\002\363K\262\227e(Z\220\n]\264\270\243m.\332B\264\322\264\322\264\302)\214\265\225\2121I\212LSH\246\221M\"\223\024\322\264\335\264\204S\010\246\025\244\333I\266\223m7o5 L\niJn\312\nT.\2705\033t\252\3169\250XTei1K\214\323\225M)\214\232A\001\251\004\0252E\212\262\211R\205\305K\020\346\256 \342\254GVR\246\007\002\243\220f\242PA\253(3S\252\324\310\265:\216(\"\231\266\214S@\245T\251\004y\247\210\263M1b\232\026\234\0058-.\3326\322\025\244+L+L\"\243aYX\346\223\024\230\244\"\223\024\322)\245i6\322b\232V\232V\232V\233\266\215\264l\246\224\244\331\315H\023\"\232\311HR\233\266\232\321f\252J\230\252\314*\026Zi\025\0369\251\2213R\252\n\235c\024\024\024\004\024\270\305I\035ZT\310\247*`\325\210\305[\215ju\247\n~\320EF\311\203O\214\342\254\241\251T\324\300\321\232\\f\232\374p)\025x\247\252\340\324\312\265(Z\0313P\262`\322\005\311\245\000\347\024\360\264m\244\333M+L+L+Q\262\326F(\305&)1HE!\024\233i\245i\245i\010\246\225\246\225\243m\033)Lt\335\224\276VjA\036\0055\243\246\224\250\366sJW\002\251N:\3257Z\201\205&\334\322yx4\365\030\251\024f\246Piv\223K\214R\355\315*\2575z\001\220*\307\227OH\361V\024b\236)\342\2363K\264\232@\270\251V\244S\315YS\221F)\340`SB\344\344\324\212\264\355\265*\212\224\n]\264\307L\212\213f)\3120y\247\355\346\215\264\205i\205i\205i\204Tl+\037\024b\223\024\230\366\244\305\033i6\322\025\246\225\246\225\244+I\262\200\224\355\224\273)\273*E\217\212pJC\036j6\212\231\345\342\243\227\201Y\362\214\232\254\353P\224\247*P\311L\3075*T\312E8\221\212i\346\225EJ\211\223W\240\217\025mR\244\013\212x\024\241jEL\324\311\0259\223\024\300\2315\"\305S,5*\304E=c\251V>\016j=\2704\365\024\375\264\345Z\231V\237\267\212M\264\315\234\323\n\342\244\333\362\203AZaZiZaZ\215\205F\302\261\261F)\270\243\024\230\245\305!\024\230\244\"\232V\223m\033iBS\202\322\354\245X\362jA\035;\313\243\313\250\335@\252\322\034UII5U\320\232\201\2434\317.\232W\024\021\221L\331\315<Fi\353\031\247\2244\251\021&\254,<T\311\026\rZE\000T\302\237R*\323\302T\350\2252\256)\031sO\216,\324\342*\262\221\002)\306,Rm\305!<`S6f\234\027\024\340)\300T\250*R\0061M\333J\0274yt\246<-0\212iZaZ\215\205F\302\243aX\300Q\212B)\247\212i4\224s\351Hr(\006\227\031\245\331J\026\234\022\227e8%H\261\324\201)\257\305B\317P;f\240u\315B\321\324m\025B\321\324f:\212H\361L\013\232<\263\232z\256*E\025(\214R\205\000\324\212EJ\274\324\312*E\025*\214\325\204Z\224-J\242\236\0058.jdZ\235W5b4\342\225\226\230\313P\343$\342\236\005.)B\322\343\024\365\342\2369\247\342\225EH\026\224\247\025\013/&\230E1\205F\302\242aQ\262\3268Z1Q\265\'\226Z\244X\t\352)\342\000)Lb\2436\340\3645\004\260\2249\024\211\351S*\346\237\262\224%8%8GS,t\245p*\274\2435]\226\241\220\005\025_\314\346\224\020z\320\312*\026Ni\236^j)b\342\253\252`\324\342,\212C\r\013\021\025(\030\024\335\244\323\225\rX\215\rN\026\244QS\"\325\205Z\225V\245T\251\002S\325*eJ\231R\254\"\361C\n\215\207\025\010\0304\354Q\212p\024\340\264\354S\220sR\020\0059EH\203&\236W\025\023\256~\265\t\025\033\n\211\205F\302\243aX\324c4\242.y\251\200P0\005\033I\351A\030\035j2i\t\305!\001\3075\031\267\364\247F9\301\353S\005\247\205\245\331\212@~lU\205Zd\202\2532\346\242\223\n*\224\274\325r\244\032L\322\026\"\243g4$\274\363S\262\007Z\254a\301\247\205\300\247*\344\324\242.)\246,R\010\375\252d\216\246\010\005;mH\261\346\247H\352\302\307\305=W\025:-H\022\235\214T\211S\245Y@1Lu\246\021\305BW\232\\R\355\245\003\024\360)H\342\235\030\317J~9\245\305H\215\203R1\365\2460\250\030sQ\260\250\330TL*6\025\214\027\361\251Q=i\342<\363I\267\007\245\014\307\030\250Y\252\"rh\353R\'\002\244\002\232@\317J\225E<\n\\S#\\\271\253J\274Tn*\007\030\025J^M@V\242t\250Yi\204TL\264\314\02552\271\3059>c\203S\371y\024\320\2305f5\004S\214Y\240AO\021b\236#\245\021\324\250\230\251\221j\302\247\024\204`\324\321\221R\323s\223S\306\001\024\376\2254oRu\246?J\213\024\001K\212\\P)\325,k\201Hz\323\2513\203S!\336>\224\036\265\033\016j&\025\023\n\215\205B\302\263\025*M\240\n:\212a5\033\232\205\2174\312z\322\223\216i\301\370\342\234\274\324\252\265(ZR\274S!\341\315Y\003\212\216A\200MRv\'5\013.j\"\265\023\255DV\243d\250\212\323\nf\200\270\247\257\006\256Dw\n$\\sI\023`\342\257F\273\205K\260\n<\274\323\204F\234\"5\"\305O\n\027\255J\035@\250$q\232X\344\251\374\314\212n\376j\304r\020)\373\362jd\247\356\244\334qL\357O\024\242\234\005.)@\251\001\3055\251i\010\247+\025\351S*\356\346\221\222\242d\342\240qP\260\250\330U\000\270\240\340Td\322\032\205\315B{\323i\340P\307\212E<U\204\025:\212~8\245\347\024\221\240\337\232\230\374\242\243s\221U\0359\246\024\250\331*&Z\210\255F\313P\262\3236\322\354\243mM\017\006\255\024\334\265\017\225\264\325\230\344\332*_754L;\325\215\353@\221hi@\351P<\271\351M\014\306\234\020\236\265*\307\212\220%<GR\252\201\326\234\000\365\251\024\343\245J\017\024\204\322\nx\247S\200\247b\227\024\264\032@9\305-8\n\236#\306)\314*\'\025Y\305B\302\242aT\rF\306\243cLg\342\241&\232i@\247\342\232\343\"\210\200<U\225\\qR\255<\032ZU\340\346\234[ \324f\230\302\242e\250\231j&Z\210\212c-D\311L+I\217j6\323\227\203\305Z\211\370\301\251\366\006\246\371T\341\035H\024\216\224\034\322\000\324\340\204\323\304t\274\npjQ!\025\"K\353R\356\310\310\245\334M9sS(\342\236)\373sJ\006)@\247\201N\002\234\005;\024b\227\024\322)@\247/\314i\377\000t\361O\316E1\371\025Y\352\026\250\232\263\230\324Lj\026j\214\234\323M&9\247\216\00586i\257\351P\243lz\274\247#5 jpjZp4\200\201\301\247\210\375i\256\230\250H\250\330Tl*&\025\021\024\302)\204Sv\321\266\227\024\345\342\246Y\rI\346R\211i\302Zp\2234\360\364\340\374Q\346\032n\354\321\232PsOZ\221IS\355S\251\317J\225EL\265 \024\340)\330\245\002\234\0058\nx\024\340(\305\024\204Q\212T8jRrh\017\212Fj\201\315@\324\306\254\206j\211\232\242c\232BqM\242\236)qM5\024\243\034\323\240\270\354j\322I\232\2247\255;9\247\nF\007\250\247\307q\3742\017\306\236\314\276\265\001\031\246\260\250\231j&Z\215\226\243+M+L\"\214Q\2121N\024\360)v\323\200\247\201R\001O\035(\002\214S\266\322\205\247\201R\001OPGJ\235:T\353R\250\247\201N\002\234\026\234\026\234\026\234\026\234\005.\3326SJ\321\212n(5\031\342\230Z\230y\250\3150\326\031j\215\2156\220\232J3J\r8\032By\250\2449\025\014C\347\253\253\362\324\352jA\355N\006\235\232f\321\232v\320p{\322\201\305#.FEF\313Q\262\324L\265\031Za\024\302\264\334R\342\214R\201O\002\234\005=P\323\302\323\200\247\250\247\205\030\240\256)@\247\001N\002\244\002\236\005H\243\0252\324\313R-<\nx\024\340\264\340\264\354T\2423\212i\\Q\212n)\n\323J\323H\250\315F\302\243\246\032i\256|\232a4\332BqH[\024\302\364\241\351\333\250\r\232k\232\205N\032\255+\344T\361\265J\017\024\360iA\247u\245\035)\312)\330\3151\226\243e\364\250\231j&Z\214\2550\212n)1K\212P)\300S\224T\352)\373h\333J\005<\n~(\331J\026\236\0058\n\221EH\005H\242\245Z\221jP*@)\340T\201iv\340\212pbh#\024\230\244\"\232E4\212k\n\211\205D\302\243\"\230i\206\271\322i\244\322TO%D_4\205\351\003\342\235\346P$\346\234[5\033t\342\235n\371m\254j\370\030\251\025\251\340\323\3058S\324S\205<R5V\226P\247\024\335\331\355HT\032\215\226\242+M\333M\333K\266\227\024\001N\025\"\234T\201\251\302\234\005H\253O\013N\333K\266\224\nx\025\"\212x\024\345\251\205H\242\246QR(\251Uj@\270\245\003\232VQ\214\212i\311\034\322Rb\223\024\204S\010\250\331j\026Z\211\205FEFk\234\244<T.\335\205Ws\3150\2750\275&\372\013R\253\363\315J\257RpED\312U\262\265b+\223\300z\270\2370\342\245\034S\325\252AO\034\323\305>\232\325\237u\301\247\301\312\214\324\333\007jk!\250\231)\205)6\322m\244\333F\3321N\247\212\221MH*E\251\005-8R\201N\003\322\234*AR(\251\005H\2652\324\350*eZ~\332\220\240\t\232\217\024\302)1F)\r!\024\322)\214*\027\025\013-D\302\242a\\\331\353Lv\300\252\254\325\0136j3M&\233\2327S\263R+T\310sO\333L+\203\232\225.\031:U\313k\201\'\r\326\257\252\251\035\205\007`\007\232\024\203\322\244\006\224\265C$\241G\275R\226O9\300\035\005Y\215p\005J)OJaZaZaZiZM\264m\244\305.)@\247\212x5\"\232\22058\032vi\302\236:S\2058T\253R\255J\242\245Z\235\rN\206\245\003=($\3644\303L4\224Q\214\323\274\2763Q\021Lj\211\205B\302\242aP\260\256d\232\2573UGja4\302i\204\323I\244\31585H\246\246\215\271\253jF)\255\3157fjH\243 \344U\261+\001\201Mb\344pi\251<\221q\326\245K\341\2347\024\367\277\215W\206\346\251\231\344\270l(\300\253pC\264s\326\254\201\212Z\0014\204\2323\352(\3004\322\264\233(\331I\345\321\262\215\224\270\245\002\226\236\016)\352i\342\245QR\001N\013\351O\002\236\0075\"\324\212je5*\232\235MH\255\212R\324\302i\231\346\226\212T\344\325\223\235\234UV\0305\031\025\023\n\205\205D\325\013\n\345\\\340U)\033&\240&\230M4\234\323M4\322P*E\251\220\324\353%.\374\232\225Njej\225\006j\\\niAPI\000jbY\202y\253\321@\2508\025>1K\364\240\nZ1\232]\264\340\202\227`\240\306)\273\005&\3126\322l\3154\255\030\305\030\315.)\352*U\025*\212\225V\244QO\013\232v\302)\3123O\003\024\365\251T\324\252\325 j\\\322SM(4\3602)\007\312r*_;#\232\205\216M0\323\030T\016*\026\025\013\n\343fz\250\315\232\214\232a4\224\206\232i)@\247\003OSR\203J\t\006\247F\251\224\214\325\204z\225H\247\022(\306i\350\2650\030\034R\321\214\322\205\247b\214R\201N\002\234\005.)\010\244\305&\3326\322\024\246\354>\224m=\305.\332r\255H\253R\250\251TT\252\265*\257\2558\014PW\373\264\003\353O\003\322\234)\340\323\324\323\203R\203A\244\006\244CO#5\031\244=i\244S\032\242a\232\205\205D\302\270)\0375\t4\302i\264R\023M4\224\023@4\3655\"\265J\246\244Z\231ML\206\246\006\236\274\324\300qO\035)\324\352v)@\247b\224\np\024\270\247\001K\212\000\245\332)\245Gj\002\203K\264\nLz\322\021\352(\013N\tO\013R*\324\252\265\"\373S\305;u\031\346\203\203\365\241\01685(\347\2458S\300\245\242\235\326\234\200g\2321\203\305;4\322i3Hi\206\243j\211\205B\302\274\355\215FM4\232m\031\244\315!4\334\321@\247\212z\232\231\rL\265(\036\225\"\261\251\326\246Z\220\032x\247\212p\024\361N\024\340)\300S\261@\024\354R\342\212Bh\244\2434\023FE\002\244\002\236\277J\220})\340\372\323\263F\352\\\322\346\224\032p9\353N\\\212\231H>\306\236\005?\024\252\007~izg\024\001A\024\224\206\222\220\232i\246\032\215\207\245D\302\274\321\2150\232i4\231\367\244\315!ji4\231\245\006\234)\342\236\265*\n\235je\251\224T\252*E\251\224T\200S\200\247\201O\002\236\005.)pi\324\240R\321Hi)3F\352JL\322\322\203N\r\212\221\\T\201\251wR\203N\315(4\271\245\315(jz\265J\0105*\277@\177:\224\032x\366\245\002\220\320y\024\322\010\246\322u\244\244\"\230E4\212k.k\313\t\246\223M&\232M!4\231\244\242\234\0058T\200T\212*e\0252\n\231EL\242\246QR\001R(\251\005<\nx\024\360)\340R\212ZZ3Fi3Fi\t\246\223I\2328\307\275\007\2123\212\001\247\003\232^\247\212\220\034S\303S\203S\201\247Q\232\\\321\232p8\247\253\324\253&jU\223\037J\225^\244\017\232RE&\352BsM4\235\250\372\323M\030\246\342\232Ey94\204\323i\264\332\r\024\240S\300\247\201R(\251\024T\310\265:-L\242\245QR\250\251\024T\252*@)\340S\300\247\001O\002\227\024b\203HM4\232L\322\023I\232L\321\272\214\323\272\320\t\034v\241\207qJ\275)\303\245<\021J\r<S\201\247R\216\264\264R\346\200i\301\252E\222\245W\251\003\323\304\236\264\273\2517Q\272\214\363\305\005\251\013Rn\244$SI\025\344\331\244\244&\232M%\024\240S\300\247\001R\001R(\251\225jeZ\225EL\242\245QR(\251TT\212*@*@)\340S\261K\214t\245\006\203M&\232i\244\322f\233\232\t\244\2434\231\247\003N\004\366\247\014\343\236iW\234\346\227\214P)\300\323\305<S\2058R\321E\024S\263N\r\212\221^\236\036\224=.\3727R\357\245\335I\272\232M!jaj\362\274\322\023M&\222\212P)\300S\300\247\201R(\251\024T\352*U\025*\212\225EJ\242\245QR\250\251\000\247\201O\002\236)sE\031\246\2264\026\3154\232L\323sIE!4\206\200sJ\r=i\331\305\024\2434\341N\247\003O\006\236\r8\032Z)GJR(\244\"\200is\212P\364\355\364\273\350\337J\036\227u\033\350\335A4\302k\313(\'\024\332)E-<\nx\024\360*E\0252\255L\242\245QR\250\251\024T\312*U\025\"\324\202\236\005>\2274\231\245\315&i3IHE4\212L\322f\215\302\223\"\223\255\030\305/\326\224\023N\025 \245\247R\212u8\032p4\340isN\024\242\224\n\\PE2\227&\214PM&qF\352\003R\357\240\275 zx|\322\023_\377\331"
+byte_png: "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\002\000\000\000\002\000\010\000\000\000\000\321\023\213&\000\000\003,IDATx^\355\335\335r\263 \020\000\320\214\357\377\314\231\357\247^\264:QQ(\260{\316]\2155\215\254\013,\306\276^\324\360\336n\000\000\000\000\242R\006\000\000\200r\306\321P`\331n\210@\026\270+d8\000\000\000\000\300\240\324\343\000\000fb\035\032\000\000\000 \253\307\353zJK\220\316\343\274\001\000\000\000\000@\037\n\274@\t9#\261\326\215\357F\003\000\000\000\"2\337\375\311\371\000\230C\353z0\000\320\231\311\331=\261\007I\261?\035\000\000\000\337\230\002\002\000\000@\036ow\010\000@J\326\002 )\343\377\334\264?\000\000\000\300\324\324\366\001\000\000 0\367u\000\000\000\000\000\000\000\300)\313\353y\371N\00506Y*=!\000\000\211x\252?\000\000\000\307L\034\271\302\322\002\000\000\000\000\000\000\000\000\000\000\000\304\347\373\003\000\000\000\000\000\323Q\332\005\000\200\346\026\343n\000\000\000\000\000\000\000\306gu\033\000\000\000\000\000\000\000\000\350\316\215L\000\000$b\370\233\331{\273\001\000\000\000\000\000\022\361\317|\000V\026\0169\262\215\217\355\317\3718\003\000\000\000\000\000\000\201Y\014\312\300MC\214\357N\224\272\037\016\000\000\000Z0\341\006\000\000\000\202Q\356\000\000\000\000\210F\305\007\000\000(\342\271\032\344 \322\001\000\000B3\355\003\370D\206\004\000\200\021\271\343\233j\004\023\3004\244l\000\000\332\263>\014\000\000\220K\257\325\207\306\357kz\013<\3208C\001\000\300\2573\306\005\000\000\340\216O\363\311\375r\354\317-\333\337\333\357?\276\345\257\355\266i\254\'\374\376\007(j\261\363\2679\337\003\310K\206\000\350\257h\360\307\320\264e \006I1]\276Hk\004@\215cp\317\345\206\006\000\000\000xB\021\002\200\036\364?\311\t\000\340\036\353\327\305$\\\000f\243\273\007\000\010\242h`W\2643\000\014M\257\266\223k\251B\000\000_r\345\276\007\244\315\272v\201\267\333P\231\006\344\221\377\001\324:J\031\334[\004\260\022\n\311\t\000\200tL\'!%\227>\000[\372\006 \275\2654\232\257B\252\007\000\000\000\000\000RQ\024\005HNG\000\337\344[ \377]\303\236\337\257?\314\327I\233\321\325\000\000\00000\005\201\030\224\037\000\370Hg\237\233\366\207C\023_\"\2139\000\'&\016o\036\221\034r8\270\302\017^b<\325\233\253\372\001\211L\2171\213\253\027\266\026\315\355j\234\000\323\223\356os\352\206\246#\003\000\200r\2469\311M\033\000\255g\200N\0140\245i\223\027Uh\177`X\022TX\0361GWM\002\260\3051y\350\270\0279~\025\200n\364\251\000\3008L\035\353i5\312ku\\xI\001\2759\377\364!\362\206\241\217ON\000\000\000P\235\t\037+\217\320\316N\000d\367/\002N\352\016\313\351\036\244 \n !\027>@R:\000\000\200F\024\3449d$\016\000\361U\350\357+\034\002\200N\344p\000\000\000\000\000\000\000\000\000\000\000\006\362\007Wv]\t\272\351Z\n\000\000\000\000IEND\256B`\202"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 7946493..2d832bc 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -8955,9 +8955,6 @@
         <!-- Flag indicating whether a recognition service can be selected as default. The default
              value of this flag is true. -->
         <attr name="selectableAsDefault" format="boolean" />
-        <!-- The maximal number of recognition sessions ongoing at the same time.
-             The default value is 1, meaning no concurrency. -->
-        <attr name="maxConcurrentSessionsCount" format="integer" />
     </declare-styleable>
 
     <!-- Use <code>voice-interaction-service</code> as the root tag of the XML resource that
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index c8a65a7..d4644c5 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -294,6 +294,9 @@
         <!-- Additional flag from base permission type: this permission can be automatically
             granted to the system app predictor -->
         <flag name="appPredictor" value="0x200000" />
+        <!-- Additional flag from base permission type: this permission can also be granted if the
+             requesting application is included in the mainline module}. -->
+        <flag name="module" value="0x400000" />
         <!-- Additional flag from base permission type: this permission can be automatically
             granted to the system companion device manager service -->
         <flag name="companion" value="0x800000" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 9a585a1..cead34b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1371,30 +1371,6 @@
          Must be in the range specified by minimum and maximum. -->
     <item name="config_screenBrightnessSettingDefaultFloat" format="float" type="dimen">-2</item>
 
-    <!-- Note: This setting is deprecated, please use
-    config_screenBrightnessSettingForVrDefaultFloat instead -->
-    <integer name="config_screenBrightnessForVrSettingDefault">86</integer>
-
-    <!-- Note: This setting is deprecated, please use
-    config_screenBrightnessSettingForVrMinimumFloat instead -->
-    <integer name="config_screenBrightnessForVrSettingMinimum">79</integer>
-
-    <!-- Note: This setting is deprecated, please use
-    config_screenBrightnessSettingForVrMaximumFloat instead -->
-    <integer name="config_screenBrightnessForVrSettingMaximum">255</integer>
-
-    <!-- Default screen brightness for VR setting as a float.
-    Equivalent to 86/255-->
-    <item name="config_screenBrightnessSettingForVrDefaultFloat" format="float" type="dimen">0.33464</item>
-
-    <!-- Minimum screen brightness setting allowed for VR. Device panels start increasing pulse
-     width as brightness decreases below this threshold as float.
-     Equivalent to 79/255 -->
-    <item name="config_screenBrightnessSettingForVrMinimumFloat" format="float" type="dimen">0.307087</item>
-
-    <!-- Maximum screen brightness setting allowed for VR as float. -->
-    <item name="config_screenBrightnessSettingForVrMaximumFloat" format="float" type="dimen">1.0</item>
-
     <!-- Screen brightness used to dim the screen while dozing in a very low power state.
          May be less than the minimum allowed brightness setting
          that can be set by the user. -->
@@ -2507,6 +2483,10 @@
          states. -->
     <bool name="config_dozeAlwaysOnDisplayAvailable">false</bool>
 
+    <!-- Control whether the pickup gesture is enabled by default. This value will be used
+     during initialization when the setting is still null. -->
+    <bool name="config_dozePickupGestureEnabled">true</bool>
+
     <!-- Control whether the always on display mode is enabled by default. This value will be used
          during initialization when the setting is still null. -->
     <bool name="config_dozeAlwaysOnEnabled">true</bool>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index f2a16d3..d40adf5 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -118,6 +118,11 @@
     <bool name="config_using_subscription_manager_service">false</bool>
     <java-symbol type="bool" name="config_using_subscription_manager_service" />
 
+    <!-- Whether asynchronously update the subscription database or not. Async mode increases
+         the performance, but sync mode reduces the chance of database/cache out-of-sync. -->
+    <bool name="config_subscription_database_async_update">true</bool>
+    <java-symbol type="bool" name="config_subscription_database_async_update" />
+
     <!-- Boolean indicating whether the emergency numbers for a country, sourced from modem/config,
          should be ignored if that country is 'locked' (i.e. ignore_modem_config set to true) in
          Android Emergency DB. If this value is true, emergency numbers for a country, sourced from
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index a9bec7a9..90141e5 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -117,7 +117,7 @@
     <public name="accessibilityDataPrivate" />
     <public name="enableTextStylingShortcuts" />
     <public name="requiredDisplayCategory"/>
-    <public name="maxConcurrentSessionsCount" />
+    <public name="removed_maxConcurrentSessionsCount" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 6aea32f..18a5c72 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1133,6 +1133,16 @@
     <string name="permdesc_useDataInBackground">This app can use data in the background. This may increase data usage.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_schedule_exact_alarm">Schedule precisely timed actions</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_schedule_exact_alarm">This app can schedule work to happen at a desired time in the future. This also means that the app can run when you\u2019re not actively using the device.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_use_exact_alarm">Schedule alarms or event reminders</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_use_exact_alarm">This app can schedule actions like alarms and reminders to notify you at a desired time in the future.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_persistentActivity">make app always run</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_persistentActivity" product="tablet">Allows the app to make parts of itself persistent in memory.  This can limit memory available to other apps slowing down the tablet.</string>
@@ -6407,4 +6417,22 @@
 
     <!-- Display content to tell the user the sim card name and number-->
     <string name="default_card_name">CARD <xliff:g id="cardNumber" example="1">%d</xliff:g></string>
+
+    <!-- Strings for CompanionDeviceManager -->
+    <!-- Title of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permlab_companionProfileWatch">Companion Watch profile permission to manage watches</string>
+    <!-- Description of Watch profile permission, which allows a companion app to manage watches. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_companionProfileWatch">Allows a companion app to manage watches.</string>
+    <!-- Title of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_observeCompanionDevicePresence">Observe companion device presence</string>
+    <!-- Description of observing device presence permission, which allows a companion app to observe the presence of the associated devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_observeCompanionDevicePresence">Allows a companion app to observe companion device presence when the devices are nearby or far-away.</string>
+    <!-- Title of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permlab_deliverCompanionMessages">Deliver companion messages</string>
+    <!-- Description of delivering companion messages permission, which allows a companion app to deliver messages to other devices. [CHAR LIMIT=NONE] -->
+    <string name="permdesc_deliverCompanionMessages">Allows a companion app to deliver companion messages to other devices.</string>
+    <!-- Title of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permlab_startForegroundServicesFromBackground">Start foreground services from background</string>
+    <!-- Description of start foreground services from background permission [CHAR LIMIT=NONE] -->
+    <string name="permdesc_startForegroundServicesFromBackground">Allows a companion app to start foreground services from background.</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ace7e4c..cd0024f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2062,12 +2062,6 @@
   <java-symbol type="integer" name="config_screenBrightnessSettingMinimum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingMaximum" />
   <java-symbol type="integer" name="config_screenBrightnessSettingDefault" />
-  <java-symbol type="integer" name="config_screenBrightnessForVrSettingDefault" />
-  <java-symbol type="integer" name="config_screenBrightnessForVrSettingMaximum" />
-  <java-symbol type="integer" name="config_screenBrightnessForVrSettingMinimum" />
-  <java-symbol type="dimen" name="config_screenBrightnessSettingForVrMinimumFloat" />
-  <java-symbol type="dimen" name="config_screenBrightnessSettingForVrMaximumFloat" />
-  <java-symbol type="dimen" name="config_screenBrightnessSettingForVrDefaultFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMinimumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingMaximumFloat" />
   <java-symbol type="dimen" name="config_screenBrightnessSettingDefaultFloat" />
@@ -3826,6 +3820,7 @@
   <java-symbol type="string" name="config_cameraShutterSound" />
   <java-symbol type="integer" name="config_autoGroupAtCount" />
   <java-symbol type="bool" name="config_dozeAlwaysOnDisplayAvailable" />
+  <java-symbol type="bool" name="config_dozePickupGestureEnabled" />
   <java-symbol type="bool" name="config_dozeAlwaysOnEnabled" />
   <java-symbol type="bool" name="config_dozeSupportsAodWallpaper" />
   <java-symbol type="bool" name="config_displayBlanksAfterDoze" />
diff --git a/core/res/res/xml/bookmarks.xml b/core/res/res/xml/bookmarks.xml
index 454f456..3087aaa 100644
--- a/core/res/res/xml/bookmarks.xml
+++ b/core/res/res/xml/bookmarks.xml
@@ -19,23 +19,20 @@
      Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
 
      Typical shortcuts (not necessarily defined here):
-       'a': Calculator
        'b': Browser
        'c': Contacts
        'e': Email
        'g': GMail
-       'l': Calendar
+       'k': Calendar
        'm': Maps
        'p': Music
        's': SMS
        't': Talk
+       'u': Calculator
        'y': YouTube
 -->
 <bookmarks>
     <bookmark
-        category="android.intent.category.APP_CALCULATOR"
-        shortcut="a" />
-    <bookmark
         category="android.intent.category.APP_BROWSER"
         shortcut="b" />
     <bookmark
@@ -46,7 +43,7 @@
         shortcut="e" />
     <bookmark
         category="android.intent.category.APP_CALENDAR"
-        shortcut="l" />
+        shortcut="k" />
     <bookmark
         category="android.intent.category.APP_MAPS"
         shortcut="m" />
@@ -56,4 +53,7 @@
     <bookmark
         category="android.intent.category.APP_MESSAGING"
         shortcut="s" />
+    <bookmark
+        category="android.intent.category.APP_CALCULATOR"
+        shortcut="u" />
 </bookmarks>
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
index 7462bcf..b3e74d3 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeConfigurationTest.java
@@ -77,10 +77,10 @@
     }
 
     @Test
-    public void testToBuilder() {
+    public void testBuilderConstructor() {
         GameModeConfiguration config = new GameModeConfiguration
                 .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
-        GameModeConfiguration newConfig = config.toBuilder().build();
+        GameModeConfiguration newConfig = new GameModeConfiguration.Builder(config).build();
         assertEquals(config, newConfig);
     }
 
diff --git a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
index ecd9b6b8..5fa6084 100644
--- a/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
+++ b/core/tests/GameManagerTests/src/android/app/GameModeInfoTest.java
@@ -43,7 +43,7 @@
         int[] availableGameModes =
                 new int[]{GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_PERFORMANCE,
                         GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_CUSTOM};
-        int[] optedInGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
+        int[] overriddenGameModes = new int[]{GameManager.GAME_MODE_PERFORMANCE};
         GameModeConfiguration batteryConfig = new GameModeConfiguration
                 .Builder().setFpsOverride(40).setScalingFactor(0.5f).build();
         GameModeConfiguration performanceConfig = new GameModeConfiguration
@@ -51,7 +51,7 @@
         GameModeInfo gameModeInfo = new GameModeInfo.Builder()
                 .setActiveGameMode(activeGameMode)
                 .setAvailableGameModes(availableGameModes)
-                .setOptedInGameModes(optedInGameModes)
+                .setOverriddenGameModes(overriddenGameModes)
                 .setDownscalingAllowed(true)
                 .setFpsOverrideAllowed(false)
                 .setGameModeConfiguration(GameManager.GAME_MODE_BATTERY, batteryConfig)
@@ -59,7 +59,7 @@
                 .build();
 
         assertArrayEquals(availableGameModes, gameModeInfo.getAvailableGameModes());
-        assertArrayEquals(optedInGameModes, gameModeInfo.getOptedInGameModes());
+        assertArrayEquals(overriddenGameModes, gameModeInfo.getOverriddenGameModes());
         assertEquals(activeGameMode, gameModeInfo.getActiveGameMode());
         assertTrue(gameModeInfo.isDownscalingAllowed());
         assertFalse(gameModeInfo.isFpsOverrideAllowed());
@@ -75,8 +75,8 @@
         assertEquals(gameModeInfo.getActiveGameMode(), newGameModeInfo.getActiveGameMode());
         assertArrayEquals(gameModeInfo.getAvailableGameModes(),
                 newGameModeInfo.getAvailableGameModes());
-        assertArrayEquals(gameModeInfo.getOptedInGameModes(),
-                newGameModeInfo.getOptedInGameModes());
+        assertArrayEquals(gameModeInfo.getOverriddenGameModes(),
+                newGameModeInfo.getOverriddenGameModes());
         assertTrue(newGameModeInfo.isDownscalingAllowed());
         assertFalse(newGameModeInfo.isFpsOverrideAllowed());
         assertEquals(performanceConfig,
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 352c6a7..aa1853f 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -857,7 +857,10 @@
         ContentResolver resolver = InstrumentationRegistry.getContext().getContentResolver();
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI,
+                Settings.CALL_METHOD_DELETE_CONFIG,
+                compositeName,
+                null);
         assertThat(result).isNotNull();
         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
     }
diff --git a/core/tests/coretests/src/android/provider/NameValueCacheTest.java b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
index ee0b127..2e31bb5 100644
--- a/core/tests/coretests/src/android/provider/NameValueCacheTest.java
+++ b/core/tests/coretests/src/android/provider/NameValueCacheTest.java
@@ -76,7 +76,7 @@
         when(mMockContentProvider.getIContentProvider()).thenReturn(mMockIContentProvider);
         mMockContentResolver = new MockContentResolver(InstrumentationRegistry
                 .getInstrumentation().getContext());
-        mMockContentResolver.addProvider(DeviceConfig.CONTENT_URI.getAuthority(),
+        mMockContentResolver.addProvider(Settings.Config.CONTENT_URI.getAuthority(),
                 mMockContentProvider);
         mCacheGenerationStore = new MemoryIntArray(1);
         mStorage = new HashMap<>();
@@ -84,7 +84,7 @@
         // Stores keyValues for a given prefix and increments the generation. (Note that this
         // increments the generation no matter what, it doesn't pay attention to if anything
         // actually changed).
-        when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+        when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
                 eq(Settings.CALL_METHOD_SET_ALL_CONFIG),
                 any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
@@ -104,7 +104,7 @@
         // Returns the keyValues corresponding to a namespace, or an empty map if the namespace
         // doesn't have anything stored for it. Returns the generation key if the caller asked
         // for one.
-        when(mMockIContentProvider.call(any(), eq(DeviceConfig.CONTENT_URI.getAuthority()),
+        when(mMockIContentProvider.call(any(), eq(Settings.Config.CONTENT_URI.getAuthority()),
                 eq(Settings.CALL_METHOD_LIST_CONFIG),
                 any(), any(Bundle.class))).thenAnswer(invocationOnMock -> {
                     Bundle incomingBundle = invocationOnMock.getArgument(4);
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index 4adbc91..1331779 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -345,27 +345,33 @@
         try {
             // value is empty
             Bundle results =
-                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(Settings.Config.CONTENT_URI,
+                           Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
 
             // save value
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_PUT_CONFIG, name, args);
             assertNull(results);
 
             // value is no longer empty
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // save new value
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI,
+                    Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // new value is returned
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                             Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(newValue, results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI,
+                    Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -379,23 +385,25 @@
 
         try {
             // save value
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
 
             // get value
             Bundle results =
-                    r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+                    r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertEquals(value, results.get(Settings.NameValueTable.VALUE));
 
             // delete value
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
+            results = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name,
                     null);
 
             // value is empty now
-            results = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, name, null);
+            results = r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_GET_CONFIG, name, null);
             assertNull(results.get(Settings.NameValueTable.VALUE));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
         }
     }
 
@@ -413,12 +421,12 @@
 
         try {
             // save both values
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, name, args);
             args.putString(Settings.NameValueTable.VALUE, newValue);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, newName, args);
 
             // list all values
-            Bundle result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
+            Bundle result = r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG,
                     null, null);
             Map<String, String> keyValueMap =
                     (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
@@ -428,14 +436,15 @@
 
             // list values for prefix
             args.putString(Settings.CALL_METHOD_PREFIX_KEY, prefix);
-            result = r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_LIST_CONFIG, null, args);
+            result = r.call(Settings.Config.CONTENT_URI,
+                            Settings.CALL_METHOD_LIST_CONFIG, null, args);
             keyValueMap = (HashMap) result.getSerializable(Settings.NameValueTable.VALUE);
             assertThat(keyValueMap, aMapWithSize(1));
             assertEquals(value, keyValueMap.get(name));
         } finally {
             // clean up
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
-            r.call(DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, name, null);
+            r.call(Settings.Config.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, newName, null);
         }
     }
 }
diff --git a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java b/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.java
deleted file mode 100644
index 5664e0b..0000000
--- a/core/tests/coretests/src/android/view/InsetsVisibilitiesTest.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 android.view;
-
-import static android.view.InsetsState.FIRST_TYPE;
-import static android.view.InsetsState.LAST_TYPE;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.view.InsetsState.InternalInsetsType;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@link InsetsVisibilities}.
- *
- * <p>Build/Install/Run:
- *  atest FrameworksCoreTests:InsetsVisibilities
- *
- * <p>This test class is a part of Window Manager Service tests and specified in
- * {@link com.android.server.wm.test.filters.FrameworksTestsFilter}.
- */
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class InsetsVisibilitiesTest {
-
-    @Test
-    public void testEquals() {
-        final InsetsVisibilities v1 = new InsetsVisibilities();
-        final InsetsVisibilities v2 = new InsetsVisibilities();
-        final InsetsVisibilities v3 = new InsetsVisibilities();
-        assertEquals(v1, v2);
-        assertEquals(v1, v3);
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, false);
-            v2.setVisibility(type, false);
-        }
-        assertEquals(v1, v2);
-        assertNotEquals(v1, v3);
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, true);
-            v2.setVisibility(type, true);
-        }
-        assertEquals(v1, v2);
-        assertNotEquals(v1, v3);
-    }
-
-    @Test
-    public void testSet() {
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            final InsetsVisibilities v1 = new InsetsVisibilities();
-            final InsetsVisibilities v2 = new InsetsVisibilities();
-
-            v1.setVisibility(type, true);
-            assertNotEquals(v1, v2);
-
-            v2.set(v1);
-            assertEquals(v1, v2);
-
-            v2.setVisibility(type, false);
-            assertNotEquals(v1, v2);
-
-            v1.set(v2);
-            assertEquals(v1, v2);
-        }
-    }
-
-    @Test
-    public void testCopyConstructor() {
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            final InsetsVisibilities v1 = new InsetsVisibilities();
-            v1.setVisibility(type, true);
-            final InsetsVisibilities v2 = new InsetsVisibilities(v1);
-            assertEquals(v1, v2);
-
-            v2.setVisibility(type, false);
-            assertNotEquals(v1, v2);
-        }
-    }
-
-    @Test
-    public void testGetterAndSetter() {
-        final InsetsVisibilities v1 = new InsetsVisibilities();
-        final InsetsVisibilities v2 = new InsetsVisibilities();
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            assertEquals(InsetsState.getDefaultVisibility(type), v1.getVisibility(type));
-        }
-
-        for (@InternalInsetsType int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
-            v1.setVisibility(type, true);
-            assertTrue(v1.getVisibility(type));
-
-            v2.setVisibility(type, false);
-            assertFalse(v2.getVisibility(type));
-        }
-    }
-}
diff --git a/core/tests/fuzzers/FuzzService/FuzzBinder.java b/core/tests/fuzzers/FuzzService/FuzzBinder.java
index 52aafeb..7fd199a 100644
--- a/core/tests/fuzzers/FuzzService/FuzzBinder.java
+++ b/core/tests/fuzzers/FuzzService/FuzzBinder.java
@@ -34,12 +34,12 @@
         fuzzServiceInternal(binder, data);
     }
 
-    // This API creates random parcel object
-    public static void createRandomParcel(Parcel parcel, byte[] data) {
-        getRandomParcel(parcel, data);
+    // This API fills parcel object
+    public static void fillRandomParcel(Parcel parcel, byte[] data) {
+        fillParcelInternal(parcel, data);
     }
 
     private static native void fuzzServiceInternal(IBinder binder, byte[] data);
-    private static native void getRandomParcel(Parcel parcel, byte[] data);
+    private static native void fillParcelInternal(Parcel parcel, byte[] data);
     private static native int registerNatives();
 }
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
index dbeae87..264aa5f 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.cpp
@@ -38,7 +38,7 @@
     return registerFrameworkNatives(env);
 }
 
-JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
+JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject jparcel, jbyteArray fuzzData) {
     size_t len = static_cast<size_t>(env->GetArrayLength(fuzzData));
     uint8_t data[len];
     env->GetByteArrayRegion(fuzzData, 0, len, reinterpret_cast<jbyte*>(data));
diff --git a/core/tests/fuzzers/FuzzService/random_parcel_jni.h b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
index bc18b2f..c96354a 100644
--- a/core/tests/fuzzers/FuzzService/random_parcel_jni.h
+++ b/core/tests/fuzzers/FuzzService/random_parcel_jni.h
@@ -24,5 +24,5 @@
     // Function from AndroidRuntime
     jint registerFrameworkNatives(JNIEnv* env);
 
-    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_getRandomParcel(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
+    JNIEXPORT void JNICALL Java_randomparcel_FuzzBinder_fillParcelInternal(JNIEnv *env, jobject thiz, jobject parcel, jbyteArray fuzzData);
 }
diff --git a/core/tests/fuzzers/ParcelFuzzer/Android.bp b/core/tests/fuzzers/ParcelFuzzer/Android.bp
new file mode 100644
index 0000000..b71a06e
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/Android.bp
@@ -0,0 +1,40 @@
+package {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_fuzz {
+    name: "java_binder_parcel_fuzzer",
+    srcs: [
+        "ParcelFuzzer.java",
+        "ReadUtils.java",
+        "FuzzUtils.java",
+        "FuzzOperation.java",
+        "ReadOperation.java",
+        ":framework-core-sources-for-fuzzers",
+    ],
+    static_libs: [
+        "jazzer",
+        "random_parcel_lib",
+        "binderReadParcelIface-java",
+    ],
+    jni_libs: [
+        "librandom_parcel_jni",
+        "libc++",
+        "libandroid_runtime",
+    ],
+    libs: [
+        "framework",
+        "unsupportedappusage",
+        "ext",
+        "framework-res",
+    ],
+    native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
+}
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
similarity index 65%
rename from core/java/android/view/InsetsVisibilities.aidl
rename to core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
index bd573ea..033231d 100644
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzOperation.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package parcelfuzzer;
 
-package android.view;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
 
-parcelable InsetsVisibilities;
+public interface FuzzOperation {
+    void doFuzz(FuzzedDataProvider data);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
new file mode 100644
index 0000000..5e9e5ec
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/FuzzUtils.java
@@ -0,0 +1,87 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Parcel;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class FuzzUtils {
+    public static FuzzOperation[] FUZZ_OPERATIONS =
+            new FuzzOperation[] {
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Append
+                        int start = provider.consumeInt();
+                        int len = provider.consumeInt();
+                        Parcel p1 = null;
+                        Parcel p2 = null;
+
+                        try {
+                            p1 = Parcel.obtain();
+                            p2 = Parcel.obtain();
+
+                            byte[] data =
+                                    provider.consumeBytes(
+                                            provider.consumeInt(0, provider.remainingBytes()));
+                            FuzzBinder.fillRandomParcel(p1, data);
+                            FuzzBinder.fillRandomParcel(p2, provider.consumeRemainingAsBytes());
+
+                            p1.appendFrom(p2, start, len);
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            p1.recycle();
+                            p2.recycle();
+                        }
+                    }
+                },
+                new FuzzOperation() {
+                    @java.lang.Override
+                    public void doFuzz(FuzzedDataProvider provider) {
+                        // Fuzz Read
+                        // Use maximum bytes to generate read instructions and remaining for parcel
+                        // creation
+                        int maxParcelBytes = provider.remainingBytes() / 3;
+                        byte[] data = provider.consumeBytes(maxParcelBytes);
+                        Parcel randomParcel = null;
+
+                        try {
+                            randomParcel = Parcel.obtain();
+                            FuzzBinder.fillRandomParcel(randomParcel, data);
+
+                            while (provider.remainingBytes() > 0) {
+                                provider.pickValue(ReadUtils.READ_OPERATIONS)
+                                        .readParcel(randomParcel, provider);
+                            }
+
+                        } catch (Exception e) {
+                            // Rethrow exception as runtime exceptions are catched
+                            // at highest level.
+                            throw e;
+                        } finally {
+                            randomParcel.recycle();
+                        }
+                    }
+                },
+            };
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
new file mode 100644
index 0000000..688c812
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ParcelFuzzer.java
@@ -0,0 +1,42 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.util.Log;
+
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+import randomparcel.FuzzBinder;
+
+public class ParcelFuzzer {
+
+    static {
+        // Initialize JNI dependencies
+        FuzzBinder.init();
+    }
+
+    public static void fuzzerTestOneInput(FuzzedDataProvider provider) {
+        // Default behavior for Java APIs is to throw RuntimeException.
+        // We need to fuzz to detect other problems which are not handled explicitly.
+        // TODO(b/150808347): Change known API exceptions to subclass of
+        // RuntimeExceptions and catch those only.
+        try {
+            provider.pickValue(FuzzUtils.FUZZ_OPERATIONS).doFuzz(provider);
+        } catch (RuntimeException e) {
+            Log.e("ParcelFuzzer", "Exception occurred while fuzzing ", e);
+        }
+    }
+}
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
similarity index 61%
copy from core/java/android/view/InsetsVisibilities.aidl
copy to core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
index bd573ea..5c227e3 100644
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadOperation.java
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -13,7 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package parcelfuzzer;
 
-package android.view;
+import android.os.Parcel;
 
-parcelable InsetsVisibilities;
+import com.code_intelligence.jazzer.api.FuzzedDataProvider;
+
+public interface ReadOperation {
+    void readParcel(Parcel parcel, FuzzedDataProvider provider);
+}
diff --git a/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
new file mode 100644
index 0000000..0eff5f2
--- /dev/null
+++ b/core/tests/fuzzers/ParcelFuzzer/ReadUtils.java
@@ -0,0 +1,435 @@
+/*
+ * 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 parcelfuzzer;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import parcelables.EmptyParcelable;
+import parcelables.GenericDataParcelable;
+import parcelables.SingleDataParcelable;
+
+public class ReadUtils {
+    public static int MAX_LEN = 1000000;
+    public static int MIN_LEN = 0;
+
+    private static class SomeParcelable implements Parcelable {
+        private final int mValue;
+
+        private SomeParcelable(Parcel in) {
+            this.mValue = in.readInt();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            out.writeInt(mValue);
+        }
+
+        public static Parcelable.Creator<SomeParcelable> CREATOR =
+                new Parcelable.Creator<SomeParcelable>() {
+
+                    @Override
+                    public SomeParcelable createFromParcel(Parcel source) {
+                        return new SomeParcelable(source);
+                    }
+
+                    @Override
+                    public SomeParcelable[] newArray(int size) {
+                        return new SomeParcelable[size];
+                    }
+                };
+    }
+
+    private static class TestClassLoader extends ClassLoader {
+        TestClassLoader() {
+            super();
+        }
+    }
+
+    private static class TestInterface implements IInterface {
+        public Binder binder;
+        private static final String DESCRIPTOR = "TestInterface";
+
+        TestInterface() {
+            binder = new Binder();
+            binder.attachInterface(this, DESCRIPTOR);
+        }
+
+        public IBinder asBinder() {
+            return binder;
+        }
+
+        public static TestInterface asInterface(IBinder binder) {
+            if (binder != null) {
+                IInterface iface = binder.queryLocalInterface(DESCRIPTOR);
+                if (iface != null && iface instanceof TestInterface) {
+                    return (TestInterface) iface;
+                }
+            }
+            return null;
+        }
+    }
+
+    public static ReadOperation[] READ_OPERATIONS =
+            new ReadOperation[] {
+                    (parcel, provider) -> {
+                        parcel.setDataPosition(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataCapacity(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.setDataSize(provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataSize();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataPosition();
+                    },
+                    (parcel, provider) -> {
+                        parcel.dataCapacity();
+                    },
+
+                    // read basic types
+                    (parcel, provider) -> {
+                        parcel.readByte();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBoolean();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readInt();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readLong();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readFloat();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readDouble();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString8();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readString16();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBlob();
+                    },
+                    (parcel, provider) -> {
+                        parcel.readStrongBinder();
+                    },
+
+                    // read arrays of random length
+                    (parcel, provider) -> {
+                        byte[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new byte[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new byte[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readByteArray(array);
+                    },
+                    (parcel, provider) -> {
+                        char[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new char[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new char[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readCharArray(array);
+                    },
+                    (parcel, provider) -> {
+                        int[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new int[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new int[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readIntArray(array);
+                    },
+                    (parcel, provider) -> {
+                        double[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new double[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new double[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readDoubleArray(array);
+                    },
+                    (parcel, provider) -> {
+                        float[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new float[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new float[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readFloatArray(array);
+                    },
+                    (parcel, provider) -> {
+                        boolean[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new boolean[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new boolean[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBooleanArray(array);
+                    },
+                    (parcel, provider) -> {
+                        long[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new long[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new long[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readLongArray(array);
+                    },
+                    (parcel, provider) -> {
+                        IBinder[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new IBinder[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new IBinder[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readBinderArray(array);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<IBinder> arrayList = new ArrayList<IBinder>();
+                        parcel.readBinderList(arrayList);
+                    },
+
+                    // unmarshall from random parcel data and random bytes
+                    (parcel, provider) -> {
+                        byte[] data = parcel.marshall();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        byte[] data = provider.consumeRemainingAsBytes();
+                        Parcel p = Parcel.obtain();
+                        p.unmarshall(data, provider.consumeInt(), provider.consumeInt());
+                        p.recycle();
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+
+                    // read AIDL generated parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SingleDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SingleDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SingleDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SingleDataParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SingleDataParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, EmptyParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        EmptyParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new EmptyParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new EmptyParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, EmptyParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, GenericDataParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        GenericDataParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new GenericDataParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            int len = provider.consumeInt(MIN_LEN, MAX_LEN);
+                            array = new GenericDataParcelable[len];
+                        }
+                        parcel.readTypedArray(array, GenericDataParcelable.CREATOR);
+                    },
+
+                    // read parcelables
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelable(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        SomeParcelable[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new SomeParcelable[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new SomeParcelable[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readTypedArray(array, SomeParcelable.CREATOR);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.hasFileDescriptors(provider.consumeInt(), provider.consumeInt());
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readParcelableArray(loader);
+                    },
+
+                    // read lists
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readArrayList(loader, SomeParcelable.class);
+                    },
+
+                    // read sparse arrays
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, Object.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSparseArray(loader, SomeParcelable.class);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readSerializable(loader, Object.class);
+                    },
+
+                    // read interface
+                    (parcel, provider) -> {
+                        TestInterface[] array;
+                        if (provider.consumeBoolean()) {
+                            int pos = parcel.dataPosition();
+                            array = new TestInterface[Math.min(MAX_LEN, parcel.readInt())];
+                            parcel.setDataPosition(pos);
+                        } else {
+                            array = new TestInterface[provider.consumeInt(MIN_LEN, MAX_LEN)];
+                        }
+                        parcel.readInterfaceArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        int w = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        int h = provider.consumeInt(MIN_LEN, MAX_LEN);
+                        TestInterface[][] array = new TestInterface[w][h];
+                        parcel.readFixedArray(array, TestInterface::asInterface);
+                    },
+                    (parcel, provider) -> {
+                        ArrayList<TestInterface> array = new ArrayList<TestInterface>();
+                        parcel.readInterfaceList(array, TestInterface::asInterface);
+                    },
+
+                    // read bundle
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readBundle(loader);
+                    },
+                    (parcel, provider) -> {
+                        parcel.readBundle();
+                    },
+
+                    // read HashMap
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader);
+                    },
+                    (parcel, provider) -> {
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readHashMap(loader, String.class, String.class);
+                    },
+                    (parcel, provider) -> {
+                        HashMap<String, String> hashMap = new HashMap<>();
+                        TestClassLoader loader = new TestClassLoader();
+                        parcel.readMap(hashMap, loader, String.class, String.class);
+                    },
+            };
+}
diff --git a/core/tests/fuzzers/java_service_fuzzer/Android.bp b/core/tests/fuzzers/java_service_fuzzer/Android.bp
index 625de14..6acb198 100644
--- a/core/tests/fuzzers/java_service_fuzzer/Android.bp
+++ b/core/tests/fuzzers/java_service_fuzzer/Android.bp
@@ -37,4 +37,12 @@
         "framework-res",
     ],
     native_bridge_supported: true,
+    fuzz_config: {
+        cc: [
+            "smoreland@google.com",
+            "waghpawan@google.com",
+        ],
+        // Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
+        hotlists: ["4637097"],
+    },
 }
diff --git a/keystore/java/android/security/KeyStoreException.java b/keystore/java/android/security/KeyStoreException.java
index 1a81dda..6536e43 100644
--- a/keystore/java/android/security/KeyStoreException.java
+++ b/keystore/java/android/security/KeyStoreException.java
@@ -138,6 +138,16 @@
      * provisioning server refuses key issuance, this is a permanent error.</p>
      */
     public static final int ERROR_ATTESTATION_KEYS_UNAVAILABLE = 16;
+    /**
+     * This device requires a software upgrade to use the key provisioning server. The software
+     * is outdated and this error is returned only on devices that rely solely on
+     * remotely-provisioned keys (see <a href=
+     * "https://android-developers.googleblog.com/2022/03/upgrading-android-attestation-remote.html"
+     * >Remote Key Provisioning</a>).
+     *
+     * @hide
+     */
+    public static final int ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION = 17;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -157,7 +167,8 @@
             ERROR_INCORRECT_USAGE,
             ERROR_KEY_NOT_TEMPORALLY_VALID,
             ERROR_KEY_OPERATION_EXPIRED,
-            ERROR_ATTESTATION_KEYS_UNAVAILABLE
+            ERROR_ATTESTATION_KEYS_UNAVAILABLE,
+            ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION,
     })
     public @interface PublicErrorCode {
     }
@@ -184,6 +195,16 @@
      * This value is returned when {@link #isTransientFailure()} is {@code true}.
      */
     public static final int RETRY_WHEN_CONNECTIVITY_AVAILABLE = 3;
+    /**
+     * Re-try the operation that led to this error when the device has a software update
+     * downloaded and on the next reboot. The Remote provisioning server recognizes
+     * the device, but refuses issuance of attestation keys because it contains a software
+     * version that could potentially be vulnerable and needs an update. Re-trying after the
+     * device has upgraded and rebooted may alleviate the problem.
+     *
+     * <p>This value is returned when {@link #isTransientFailure()} is {@code true}.
+     */
+    public static final int RETRY_AFTER_NEXT_REBOOT = 4;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -191,6 +212,7 @@
             RETRY_NEVER,
             RETRY_WITH_EXPONENTIAL_BACKOFF,
             RETRY_WHEN_CONNECTIVITY_AVAILABLE,
+            RETRY_AFTER_NEXT_REBOOT,
     })
     public @interface RetryPolicy {
     }
@@ -217,6 +239,13 @@
      * when the device has connectivity again.
      * @hide */
     public static final int RKP_FETCHING_PENDING_CONNECTIVITY = 3;
+    /**
+     * The RKP server recognizes the device, but the device may be running vulnerable software,
+     * and thus refusing issuance of RKP keys to it.
+     *
+     * @hide
+     */
+    public static final int RKP_FETCHING_PENDING_SOFTWARE_REBOOT = 4;
 
     // Constants for encoding information about the error encountered:
     // Whether the error relates to the system state/implementation as a whole, or a specific key.
@@ -236,7 +265,7 @@
     private static int initializeRkpStatusForRegularErrors(int errorCode) {
         // Check if the system code mistakenly called a constructor of KeyStoreException with
         // the OUT_OF_KEYS error code but without RKP status.
-        if (errorCode == ResponseCode.OUT_OF_KEYS) {
+        if (isRkpRelatedError(errorCode)) {
             Log.e(TAG, "RKP error code without RKP status");
             // Set RKP status to RKP_SERVER_REFUSED_ISSUANCE so that the caller never retries.
             return RKP_SERVER_REFUSED_ISSUANCE;
@@ -272,7 +301,7 @@
         super(message);
         mErrorCode = errorCode;
         mRkpStatus = rkpStatus;
-        if (mErrorCode != ResponseCode.OUT_OF_KEYS) {
+        if (!isRkpRelatedError(mErrorCode)) {
             Log.e(TAG, "Providing RKP status for error code " + errorCode + " has no effect.");
         }
     }
@@ -309,10 +338,11 @@
     public boolean isTransientFailure() {
         PublicErrorInformation failureInfo = getErrorInformation(mErrorCode);
         // Special-case handling for RKP failures:
-        if (mRkpStatus != RKP_SUCCESS && mErrorCode == ResponseCode.OUT_OF_KEYS) {
+        if (mRkpStatus != RKP_SUCCESS && isRkpRelatedError(mErrorCode)) {
             switch (mRkpStatus) {
                 case RKP_TEMPORARILY_UNAVAILABLE:
                 case RKP_FETCHING_PENDING_CONNECTIVITY:
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
                     return true;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                 default:
@@ -346,6 +376,11 @@
         return (failureInfo.indicators & IS_SYSTEM_ERROR) != 0;
     }
 
+    private static boolean isRkpRelatedError(int errorCode) {
+        return errorCode == ResponseCode.OUT_OF_KEYS
+                  || errorCode == ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE;
+    }
+
     /**
      * Returns the re-try policy for transient failures. Valid only if
      * {@link #isTransientFailure()} returns {@code True}.
@@ -362,6 +397,8 @@
                     return RETRY_WHEN_CONNECTIVITY_AVAILABLE;
                 case RKP_SERVER_REFUSED_ISSUANCE:
                     return RETRY_NEVER;
+                case RKP_FETCHING_PENDING_SOFTWARE_REBOOT:
+                    return RETRY_AFTER_NEXT_REBOOT;
                 default:
                     return (failureInfo.indicators & IS_TRANSIENT_ERROR) != 0
                             ? RETRY_WITH_EXPONENTIAL_BACKOFF : RETRY_NEVER;
@@ -620,5 +657,8 @@
                 new PublicErrorInformation(0, ERROR_KEY_DOES_NOT_EXIST));
         sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS,
                 new PublicErrorInformation(IS_SYSTEM_ERROR, ERROR_ATTESTATION_KEYS_UNAVAILABLE));
+        sErrorCodeToFailureInfo.put(ResponseCode.OUT_OF_KEYS_REQUIRES_UPGRADE,
+                new PublicErrorInformation(IS_SYSTEM_ERROR | IS_TRANSIENT_ERROR,
+                        ERROR_DEVICE_REQUIRES_UPGRADE_FOR_ATTESTATION));
     }
 }
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
index afec830..2830d7eff 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -648,6 +648,7 @@
         // {@link android.security.KeyStoreException#RKP_TEMPORARILY_UNAVAILABLE},
         // {@link android.security.KeyStoreException#RKP_SERVER_REFUSED_ISSUANCE},
         // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_CONNECTIVITY}
+        // {@link android.security.KeyStoreException#RKP_FETCHING_PENDING_SOFTWARE_REBOOT}
         public final int rkpStatus;
         @Nullable
         public final KeyPair keyPair;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index d7d43aa..b910287 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -133,8 +133,18 @@
         }
 
         // Create a TaskFragment for the secondary activity.
-        createTaskFragmentAndStartActivity(wct, secondaryFragmentToken, ownerToken,
-                secondaryFragmentBounds, windowingMode, activityIntent,
+        final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+                getOrganizerToken(), secondaryFragmentToken, ownerToken)
+                .setInitialBounds(secondaryFragmentBounds)
+                .setWindowingMode(windowingMode)
+                // Make sure to set the paired fragment token so that the new TaskFragment will be
+                // positioned right above the paired TaskFragment.
+                // This is needed in case we need to launch a placeholder Activity to split below a
+                // transparent always-expand Activity.
+                .setPairedPrimaryFragmentToken(launchingFragmentToken)
+                .build();
+        createTaskFragment(wct, fragmentOptions);
+        wct.startActivityInTaskFragment(secondaryFragmentToken, ownerToken, activityIntent,
                 activityOptions);
 
         // Set adjacent to each other so that the containers below will be invisible.
@@ -173,8 +183,21 @@
      */
     void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
             @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
-        final TaskFragmentCreationParams fragmentOptions =
-                createFragmentOptions(fragmentToken, ownerToken, bounds, windowingMode);
+        final TaskFragmentCreationParams fragmentOptions = new TaskFragmentCreationParams.Builder(
+                getOrganizerToken(), fragmentToken, ownerToken)
+                .setInitialBounds(bounds)
+                .setWindowingMode(windowingMode)
+                .build();
+        createTaskFragment(wct, fragmentOptions);
+    }
+
+    void createTaskFragment(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentCreationParams fragmentOptions) {
+        if (mFragmentInfos.containsKey(fragmentOptions.getFragmentToken())) {
+            throw new IllegalArgumentException(
+                    "There is an existing TaskFragment with fragmentToken="
+                            + fragmentOptions.getFragmentToken());
+        }
         wct.createTaskFragment(fragmentOptions);
     }
 
@@ -189,18 +212,6 @@
         wct.reparentActivityToTaskFragment(fragmentToken, activity.getActivityToken());
     }
 
-    /**
-     * @param ownerToken The token of the activity that creates this task fragment. It does not
-     *                   have to be a child of this task fragment, but must belong to the same task.
-     */
-    private void createTaskFragmentAndStartActivity(@NonNull WindowContainerTransaction wct,
-            @NonNull IBinder fragmentToken, @NonNull IBinder ownerToken, @NonNull Rect bounds,
-            @WindowingMode int windowingMode, @NonNull Intent activityIntent,
-            @Nullable Bundle activityOptions) {
-        createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
-        wct.startActivityInTaskFragment(fragmentToken, ownerToken, activityIntent, activityOptions);
-    }
-
     void setAdjacentTaskFragments(@NonNull WindowContainerTransaction wct,
             @NonNull IBinder primary, @Nullable IBinder secondary, @Nullable SplitRule splitRule) {
         WindowContainerTransaction.TaskFragmentAdjacentParams adjacentParams = null;
@@ -238,22 +249,6 @@
         wct.setCompanionTaskFragment(secondary, finishSecondaryWithPrimary ? primary : null);
     }
 
-    TaskFragmentCreationParams createFragmentOptions(@NonNull IBinder fragmentToken,
-            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
-        if (mFragmentInfos.containsKey(fragmentToken)) {
-            throw new IllegalArgumentException(
-                    "There is an existing TaskFragment with fragmentToken=" + fragmentToken);
-        }
-
-        return new TaskFragmentCreationParams.Builder(
-                getOrganizerToken(),
-                fragmentToken,
-                ownerToken)
-                .setInitialBounds(bounds)
-                .setWindowingMode(windowingMode)
-                .build();
-    }
-
     void resizeTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
             @Nullable Rect bounds) {
         if (!mFragmentInfos.containsKey(fragmentToken)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 6f9a4ff8..d3dc05f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1218,14 +1218,14 @@
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
-                activityInTask, taskId);
+                activityInTask, taskId, null /* pairedPrimaryContainer */);
     }
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId);
+                activityInTask, taskId, null /* pairedPrimaryContainer */);
     }
 
     /**
@@ -1237,10 +1237,13 @@
      * @param activityInTask            activity in the same Task so that we can get the Task bounds
      *                                  if needed.
      * @param taskId                    parent Task of the new TaskFragment.
+     * @param pairedPrimaryContainer    the paired primary {@link TaskFragmentContainer}. When it is
+     *                                  set, the new container will be added right above it.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
-            @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
+            @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
+            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1249,7 +1252,7 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this);
+                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer);
         return container;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 5395fb2..47253d3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -36,6 +36,7 @@
 import android.view.View;
 import android.view.WindowInsets;
 import android.view.WindowMetrics;
+import android.window.TaskFragmentCreationParams;
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.GuardedBy;
@@ -307,10 +308,13 @@
         }
 
         final int taskId = primaryContainer.getTaskId();
-        final TaskFragmentContainer secondaryContainer = mController.newContainer(activityIntent,
-                launchingActivity, taskId);
-        final int windowingMode = mController.getTaskContainer(taskId)
-                .getWindowingModeForSplitTaskFragment(primaryRectBounds);
+        final TaskFragmentContainer secondaryContainer = mController.newContainer(
+                null /* pendingAppearedActivity */, activityIntent, launchingActivity, taskId,
+                // Pass in the primary container to make sure it is added right above the primary.
+                primaryContainer);
+        final TaskContainer taskContainer = mController.getTaskContainer(taskId);
+        final int windowingMode = taskContainer.getWindowingModeForSplitTaskFragment(
+                primaryRectBounds);
         mController.registerSplit(wct, primaryContainer, launchingActivity, secondaryContainer,
                 rule, splitAttributes);
         startActivityToSide(wct, primaryContainer.getTaskFragmentToken(), primaryRectBounds,
@@ -412,17 +416,18 @@
     }
 
     @Override
-    void createTaskFragment(@NonNull WindowContainerTransaction wct, @NonNull IBinder fragmentToken,
-            @NonNull IBinder ownerToken, @NonNull Rect bounds, @WindowingMode int windowingMode) {
-        final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+    void createTaskFragment(@NonNull WindowContainerTransaction wct,
+            @NonNull TaskFragmentCreationParams fragmentOptions) {
+        final TaskFragmentContainer container = mController.getContainer(
+                fragmentOptions.getFragmentToken());
         if (container == null) {
             throw new IllegalStateException(
                     "Creating a task fragment that is not registered with controller.");
         }
 
-        container.setLastRequestedBounds(bounds);
-        container.setLastRequestedWindowingMode(windowingMode);
-        super.createTaskFragment(wct, fragmentToken, ownerToken, bounds, windowingMode);
+        container.setLastRequestedBounds(fragmentOptions.getInitialBounds());
+        container.setLastRequestedWindowingMode(fragmentOptions.getWindowingMode());
+        super.createTaskFragment(wct, fragmentOptions);
     }
 
     @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
index 322f854..dcc12ac 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationRunner.java
@@ -19,6 +19,7 @@
 import static android.os.Process.THREAD_PRIORITY_DISPLAY;
 import static android.view.RemoteAnimationTarget.MODE_CHANGING;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
 import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
 import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -253,6 +254,10 @@
     @NonNull
     private List<TaskFragmentAnimationAdapter> createChangeAnimationAdapters(
             @NonNull RemoteAnimationTarget[] targets) {
+        if (shouldUseJumpCutForChangeAnimation(targets)) {
+            return new ArrayList<>();
+        }
+
         final List<TaskFragmentAnimationAdapter> adapters = new ArrayList<>();
         for (RemoteAnimationTarget target : targets) {
             if (target.mode == MODE_CHANGING) {
@@ -282,4 +287,24 @@
         }
         return adapters;
     }
+
+    /**
+     * Whether we should use jump cut for the change transition.
+     * This normally happens when opening a new secondary with the existing primary using a
+     * different split layout. This can be complicated, like from horizontal to vertical split with
+     * new split pairs.
+     * Uses a jump cut animation to simplify.
+     */
+    private boolean shouldUseJumpCutForChangeAnimation(@NonNull RemoteAnimationTarget[] targets) {
+        boolean hasOpeningWindow = false;
+        boolean hasClosingWindow = false;
+        for (RemoteAnimationTarget target : targets) {
+            if (target.hasAnimatingParent) {
+                continue;
+            }
+            hasOpeningWindow |= target.mode == MODE_OPENING;
+            hasClosingWindow |= target.mode == MODE_CLOSING;
+        }
+        return hasOpeningWindow && hasClosingWindow;
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index e31792a..fcf0ac7 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -118,10 +118,12 @@
     /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
+     * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
-            @NonNull SplitController controller) {
+            @NonNull SplitController controller,
+            @Nullable TaskFragmentContainer pairedPrimaryContainer) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -130,7 +132,16 @@
         mController = controller;
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
-        taskContainer.mContainers.add(this);
+        if (pairedPrimaryContainer != null) {
+            if (pairedPrimaryContainer.getTaskContainer() != taskContainer) {
+                throw new IllegalArgumentException(
+                        "pairedPrimaryContainer must be in the same Task");
+            }
+            final int primaryIndex = taskContainer.mContainers.indexOf(pairedPrimaryContainer);
+            taskContainer.mContainers.add(primaryIndex + 1, this);
+        } else {
+            taskContainer.mContainers.add(this);
+        }
         if (pendingAppearedActivity != null) {
             addPendingAppearedActivity(pendingAppearedActivity);
         }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
index 31aa09c..bbb454d 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizerTest.java
@@ -102,7 +102,7 @@
     public void testExpandTaskFragment() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mSplitController);
+                new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
         final TaskFragmentInfo info = createMockInfo(container);
         mOrganizer.mFragmentInfos.put(container.getTaskFragmentToken(), info);
         container.setInfo(mTransaction, info);
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 221c764..6725dfd 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -169,7 +169,7 @@
         final TaskContainer taskContainer = createTestTaskContainer();
         // tf1 has no running activity so is not active.
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mSplitController);
+                new Intent(), taskContainer, mSplitController, null /* pairedPrimaryContainer */);
         // tf2 has running activity so is active.
         final TaskFragmentContainer tf2 = mock(TaskFragmentContainer.class);
         doReturn(1).when(tf2).getRunningActivityCount();
@@ -375,7 +375,7 @@
         final Intent intent = new Intent();
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                intent, taskContainer, mSplitController);
+                intent, taskContainer, mSplitController, null /* pairedPrimaryContainer */);
         final SplitController.ActivityStartMonitor monitor =
                 mSplitController.getActivityStartMonitor();
 
@@ -609,7 +609,7 @@
                 false /* isOnReparent */);
 
         assertFalse(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
     }
 
     @Test
@@ -771,7 +771,7 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -813,7 +813,7 @@
                 false /* isOnReparent */);
 
         assertTrue(result);
-        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt());
+        verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 95328ce..13e7092 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -122,7 +122,7 @@
         assertTrue(taskContainer.isEmpty());
 
         final TaskFragmentContainer tf = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mController);
+                new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
 
         assertFalse(taskContainer.isEmpty());
 
@@ -138,11 +138,11 @@
         assertNull(taskContainer.getTopTaskFragmentContainer());
 
         final TaskFragmentContainer tf0 = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mController);
+                new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
         assertEquals(tf0, taskContainer.getTopTaskFragmentContainer());
 
         final TaskFragmentContainer tf1 = new TaskFragmentContainer(null /* activity */,
-                new Intent(), taskContainer, mController);
+                new Intent(), taskContainer, mController, null /* pairedPrimaryContainer */);
         assertEquals(tf1, taskContainer.getTopTaskFragmentContainer());
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 99f56b4..5c3ba72 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -94,18 +94,21 @@
 
         // One of the activity and the intent must be non-null
         assertThrows(IllegalArgumentException.class,
-                () -> new TaskFragmentContainer(null, null, taskContainer, mController));
+                () -> new TaskFragmentContainer(null, null, taskContainer, mController,
+                        null /* pairedPrimaryContainer */));
 
         // One of the activity and the intent must be null.
         assertThrows(IllegalArgumentException.class,
-                () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController));
+                () -> new TaskFragmentContainer(mActivity, mIntent, taskContainer, mController,
+                        null /* pairedPrimaryContainer */));
     }
 
     @Test
     public void testFinish() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(mActivity,
-                null /* pendingAppearedIntent */, taskContainer, mController);
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
         doReturn(container).when(mController).getContainerWithActivity(mActivity);
 
         // Only remove the activity, but not clear the reference until appeared.
@@ -137,12 +140,14 @@
     public void testFinish_notFinishActivityThatIsReparenting() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(mActivity,
-                null /* pendingAppearedIntent */, taskContainer, mController);
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
         final TaskFragmentInfo info = createMockTaskFragmentInfo(container0, mActivity);
         container0.setInfo(mTransaction, info);
         // Request to reparent the activity to a new TaskFragment.
         final TaskFragmentContainer container1 = new TaskFragmentContainer(mActivity,
-                null /* pendingAppearedIntent */, taskContainer, mController);
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
         doReturn(container1).when(mController).getContainerWithActivity(mActivity);
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
@@ -159,7 +164,8 @@
         final TaskContainer taskContainer = createTestTaskContainer();
         // Pending activity should be cleared when it has appeared on server side.
         final TaskFragmentContainer pendingActivityContainer = new TaskFragmentContainer(mActivity,
-                null /* pendingAppearedIntent */, taskContainer, mController);
+                null /* pendingAppearedIntent */, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
 
         assertTrue(pendingActivityContainer.mPendingAppearedActivities.contains(
                 mActivity.getActivityToken()));
@@ -172,7 +178,8 @@
 
         // Pending intent should be cleared when the container becomes non-empty.
         final TaskFragmentContainer pendingIntentContainer = new TaskFragmentContainer(
-                null /* pendingAppearedActivity */, mIntent, taskContainer, mController);
+                null /* pendingAppearedActivity */, mIntent, taskContainer, mController,
+                null /* pairedPrimaryContainer */);
 
         assertEquals(mIntent, pendingIntentContainer.getPendingAppearedIntent());
 
@@ -187,7 +194,7 @@
     public void testIsWaitingActivityAppear() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
 
         assertTrue(container.isWaitingActivityAppear());
 
@@ -209,7 +216,7 @@
         doNothing().when(mController).onTaskFragmentAppearEmptyTimeout(any(), any());
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
 
         assertNull(container.mAppearEmptyTimeout);
 
@@ -249,7 +256,7 @@
     public void testCollectNonFinishingActivities() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         List<Activity> activities = container.collectNonFinishingActivities();
 
         assertTrue(activities.isEmpty());
@@ -277,7 +284,7 @@
     public void testAddPendingActivity() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         container.addPendingAppearedActivity(mActivity);
 
         assertEquals(1, container.collectNonFinishingActivities().size());
@@ -291,9 +298,9 @@
     public void testIsAbove() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container0 = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
 
         assertTrue(container1.isAbove(container0));
         assertFalse(container0.isAbove(container1));
@@ -303,7 +310,7 @@
     public void testGetBottomMostActivity() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         container.addPendingAppearedActivity(mActivity);
 
         assertEquals(mActivity, container.getBottomMostActivity());
@@ -320,7 +327,7 @@
     public void testOnActivityDestroyed() {
         final TaskContainer taskContainer = createTestTaskContainer(mController);
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         container.addPendingAppearedActivity(mActivity);
         final List<IBinder> activities = new ArrayList<>();
         activities.add(mActivity.getActivityToken());
@@ -340,7 +347,7 @@
         // True if no info set.
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         spyOn(taskContainer);
         doReturn(true).when(taskContainer).isVisible();
 
@@ -403,7 +410,7 @@
     public void testHasAppearedActivity() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         container.addPendingAppearedActivity(mActivity);
 
         assertFalse(container.hasAppearedActivity(mActivity.getActivityToken()));
@@ -420,7 +427,7 @@
     public void testHasPendingAppearedActivity() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         container.addPendingAppearedActivity(mActivity);
 
         assertTrue(container.hasPendingAppearedActivity(mActivity.getActivityToken()));
@@ -437,9 +444,9 @@
     public void testHasActivity() {
         final TaskContainer taskContainer = createTestTaskContainer(mController);
         final TaskFragmentContainer container1 = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
         final TaskFragmentContainer container2 = new TaskFragmentContainer(null /* activity */,
-                mIntent, taskContainer, mController);
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
 
         // Activity is pending appeared on container2.
         container2.addPendingAppearedActivity(mActivity);
@@ -472,6 +479,27 @@
         assertTrue(container2.hasActivity(mActivity.getActivityToken()));
     }
 
+    @Test
+    public void testNewContainerWithPairedPrimaryContainer() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer tf0 = new TaskFragmentContainer(
+                null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+                null /* pairedPrimaryTaskFragment */);
+        final TaskFragmentContainer tf1 = new TaskFragmentContainer(
+                null /* pendingAppearedActivity */, new Intent(), taskContainer, mController,
+                null /* pairedPrimaryTaskFragment */);
+        taskContainer.mContainers.add(tf0);
+        taskContainer.mContainers.add(tf1);
+
+        // When tf2 is created with using tf0 as pairedPrimaryContainer, tf2 should be inserted
+        // right above tf0.
+        final TaskFragmentContainer tf2 = new TaskFragmentContainer(
+                null /* pendingAppearedActivity */, new Intent(), taskContainer, mController, tf0);
+        assertEquals(0, taskContainer.indexOf(tf0));
+        assertEquals(1, taskContainer.indexOf(tf2));
+        assertEquals(2, taskContainer.indexOf(tf1));
+    }
+
     /** Creates a mock activity in the organizer process. */
     private Activity createMockActivity() {
         final Activity activity = mock(Activity.class);
diff --git a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
index e6ae282..2994593 100644
--- a/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
+++ b/libs/WindowManager/Shell/res/drawable/size_compat_restart_button.xml
@@ -1,19 +1,19 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-     Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
+  ~ 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.
+  -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="48dp"
         android:height="48dp"
@@ -25,13 +25,12 @@
         android:fillAlpha="0.8"
         android:pathData="M0,24 a24,24 0 1,0 48,0 a24,24 0 1,0 -48,0"/>
     <group
-        android:translateX="12"
-        android:translateY="12">
+        android:scaleX="0.8"
+        android:scaleY="0.8"
+        android:translateX="10"
+        android:translateY="10">
         <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M6,13c0,-1.65 0.67,-3.15 1.76,-4.24L6.34,7.34C4.9,8.79 4,10.79 4,13c0,4.08 3.05,7.44 7,7.93v-2.02C8.17,18.43 6,15.97 6,13z"/>
-        <path
-            android:fillColor="@color/compat_controls_text"
-            android:pathData="M20,13c0,-4.42 -3.58,-8 -8,-8c-0.06,0 -0.12,0.01 -0.18,0.01v0l1.09,-1.09L11.5,2.5L8,6l3.5,3.5l1.41,-1.41l-1.08,-1.08C11.89,7.01 11.95,7 12,7c3.31,0 6,2.69 6,6c0,2.97 -2.17,5.43 -5,5.91v2.02C16.95,20.44 20,17.08 20,13z"/>
+            android:pathData="M0,36V24.5H3V30.85L10.4,23.45L12.55,25.6L5.15,33H11.5V36H0ZM24.5,36V33H30.85L23.5,25.65L25.65,23.5L33,30.85V24.5H36V36H24.5ZM10.35,12.5L3,5.15V11.5H0V0H11.5V3H5.15L12.5,10.35L10.35,12.5ZM25.65,12.5L23.5,10.35L30.85,3H24.5V0H36V11.5H33V5.15L25.65,12.5Z"
+            android:fillColor="@color/compat_controls_text"/>
     </group>
 </vector>
diff --git a/libs/WindowManager/Shell/res/values-am/strings.xml b/libs/WindowManager/Shell/res/values-am/strings.xml
index 51de2e5..8b46704 100644
--- a/libs/WindowManager/Shell/res/values-am/strings.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings.xml
@@ -22,8 +22,8 @@
     <string name="pip_phone_settings" msgid="5468987116750491918">"ቅንብሮች"</string>
     <string name="pip_phone_enter_split" msgid="7042877263880641911">"የተከፈለ ማያ ገጽን አስገባ"</string>
     <string name="pip_menu_title" msgid="5393619322111827096">"ምናሌ"</string>
-    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"የስዕል-ላይ-ስዕል ምናሌ"</string>
-    <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በስዕል-ላይ-ስዕል ውስጥ ነው"</string>
+    <string name="pip_menu_accessibility_title" msgid="8129016817688656249">"የሥዕል-ላይ-ሥዕል ምናሌ"</string>
+    <string name="pip_notification_title" msgid="1347104727641353453">"<xliff:g id="NAME">%s</xliff:g> በሥዕል-ላይ-ሥዕል ውስጥ ነው"</string>
     <string name="pip_notification_message" msgid="8854051911700302620">"<xliff:g id="NAME">%s</xliff:g> ይህን ባህሪ እንዲጠቀም ካልፈለጉ ቅንብሮችን ለመክፈት መታ ያድርጉና ያጥፉት።"</string>
     <string name="pip_play" msgid="3496151081459417097">"አጫውት"</string>
     <string name="pip_pause" msgid="690688849510295232">"ባለበት አቁም"</string>
diff --git a/libs/WindowManager/Shell/res/values-am/strings_tv.xml b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
index d64c051..a6be578 100644
--- a/libs/WindowManager/Shell/res/values-am/strings_tv.xml
+++ b/libs/WindowManager/Shell/res/values-am/strings_tv.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ስዕል-ላይ-ስዕል"</string>
+    <string name="notification_channel_tv_pip" msgid="2576686079160402435">"ሥዕል-ላይ-ሥዕል"</string>
     <string name="pip_notification_unknown_title" msgid="2729870284350772311">"(ርዕስ የሌለው ፕሮግራም)"</string>
     <string name="pip_close" msgid="2955969519031223530">"ዝጋ"</string>
     <string name="pip_fullscreen" msgid="7278047353591302554">"ሙሉ ማያ ገጽ"</string>
@@ -25,7 +25,7 @@
     <string name="pip_expand" msgid="1051966011679297308">"ዘርጋ"</string>
     <string name="pip_collapse" msgid="3903295106641385962">"ሰብስብ"</string>
     <string name="pip_edu_text" msgid="7930546669915337998">"ለመቆጣጠሪያዎች "<annotation icon="home_icon">"መነሻ"</annotation>"ን ሁለቴ ይጫኑ"</string>
-    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"የስዕል-ላይ-ስዕል ምናሌ።"</string>
+    <string name="a11y_pip_menu_entered" msgid="5106343214776801614">"የሥዕል-ላይ-ሥዕል ምናሌ።"</string>
     <string name="a11y_action_pip_move_left" msgid="6612980937817141583">"ወደ ግራ ውሰድ"</string>
     <string name="a11y_action_pip_move_right" msgid="1119409122645529936">"ወደ ቀኝ ውሰድ"</string>
     <string name="a11y_action_pip_move_up" msgid="98502616918621959">"ወደ ላይ ውሰድ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ml/strings.xml b/libs/WindowManager/Shell/res/values-ml/strings.xml
index 1f2ee77..0923312 100644
--- a/libs/WindowManager/Shell/res/values-ml/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ml/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"അൺസ്റ്റാഷ് ചെയ്യൽ"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"സ്‌ക്രീൻ വിഭജന മോഡിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"സ്പ്ലിറ്റ്-സ്ക്രീനിനെ ആപ്പ് പിന്തുണയ്ക്കുന്നില്ല."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"ഈ ആപ്പ് ഒരു വിൻഡോയിൽ മാത്രമേ തുറക്കാനാകൂ."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"രണ്ടാം ഡിസ്‌പ്ലേയിൽ ആപ്പ് പ്രവർത്തിച്ചേക്കില്ല."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"രണ്ടാം ഡിസ്‌പ്ലേകളിൽ സമാരംഭിക്കുന്നതിനെ ആപ്പ് അനുവദിക്കുന്നില്ല."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"സ്പ്ലിറ്റ്-സ്ക്രീൻ ഡിവൈഡർ"</string>
diff --git a/libs/WindowManager/Shell/res/values-ta/strings.xml b/libs/WindowManager/Shell/res/values-ta/strings.xml
index 2039685..525f2ea 100644
--- a/libs/WindowManager/Shell/res/values-ta/strings.xml
+++ b/libs/WindowManager/Shell/res/values-ta/strings.xml
@@ -34,8 +34,7 @@
     <string name="accessibility_action_pip_unstash" msgid="7467499339610437646">"Unstash"</string>
     <string name="dock_forced_resizable" msgid="1749750436092293116">"திரைப் பிரிப்பு அம்சத்தில் ஆப்ஸ் செயல்படாமல் போகக்கூடும்."</string>
     <string name="dock_non_resizeble_failed_to_dock_text" msgid="7408396418008948957">"திரையைப் பிரிப்பதைப் ஆப்ஸ் ஆதரிக்கவில்லை."</string>
-    <!-- no translation found for dock_multi_instances_not_supported_text (5242868470666346929) -->
-    <skip />
+    <string name="dock_multi_instances_not_supported_text" msgid="5242868470666346929">"இந்த ஆப்ஸை 1 சாளரத்தில் மட்டுமே திறக்க முடியும்."</string>
     <string name="forced_resizable_secondary_display" msgid="1768046938673582671">"இரண்டாம்நிலைத் திரையில் ஆப்ஸ் வேலை செய்யாமல் போகக்கூடும்."</string>
     <string name="activity_launch_on_secondary_display_failed_text" msgid="4226485344988071769">"இரண்டாம்நிலைத் திரைகளில் பயன்பாட்டைத் தொடங்க முடியாது."</string>
     <string name="accessibility_divider" msgid="703810061635792791">"திரையைப் பிரிக்கும் பிரிப்பான்"</string>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index c0a6456..164d2f1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -17,6 +17,7 @@
 package com.android.wm.shell.activityembedding;
 
 import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
@@ -112,23 +113,30 @@
             @NonNull List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks) {
         final List<ActivityEmbeddingAnimationAdapter> adapters = createAnimationAdapters(info,
                 startTransaction);
-        addEdgeExtensionIfNeeded(startTransaction, finishTransaction, postStartTransactionCallbacks,
-                adapters);
-        addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
-        long duration = 0;
-        for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
-            duration = Math.max(duration, adapter.getDurationHint());
-        }
         final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-        animator.setDuration(duration);
-        animator.addUpdateListener((anim) -> {
-            // Update all adapters in the same transaction.
-            final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        long duration = 0;
+        if (adapters.isEmpty()) {
+            // Jump cut
+            // No need to modify the animator, but to update the startTransaction with the changes'
+            // ending states.
+            prepareForJumpCut(info, startTransaction);
+        } else {
+            addEdgeExtensionIfNeeded(startTransaction, finishTransaction,
+                    postStartTransactionCallbacks, adapters);
+            addBackgroundColorIfNeeded(info, startTransaction, finishTransaction, adapters);
             for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
-                adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+                duration = Math.max(duration, adapter.getDurationHint());
             }
-            t.apply();
-        });
+            animator.addUpdateListener((anim) -> {
+                // Update all adapters in the same transaction.
+                final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+                for (ActivityEmbeddingAnimationAdapter adapter : adapters) {
+                    adapter.onAnimationUpdate(t, animator.getCurrentPlayTime());
+                }
+                t.apply();
+            });
+        }
+        animator.setDuration(duration);
         animator.addListener(new Animator.AnimatorListener() {
             @Override
             public void onAnimationStart(Animator animation) {}
@@ -292,6 +300,10 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createChangeAnimationAdapters(
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
+        if (shouldUseJumpCutForChangeTransition(info)) {
+            return new ArrayList<>();
+        }
+
         final List<ActivityEmbeddingAnimationAdapter> adapters = new ArrayList<>();
         final Set<TransitionInfo.Change> handledChanges = new ArraySet<>();
 
@@ -374,9 +386,11 @@
             }
 
             final Animation animation;
-            if (change.getParent() != null
-                    && handledChanges.contains(info.getChange(change.getParent()))) {
-                // No-op if it will be covered by the changing parent window.
+            if ((change.getParent() != null
+                    && handledChanges.contains(info.getChange(change.getParent())))
+                    || change.getMode() == TRANSIT_CHANGE) {
+                // No-op if it will be covered by the changing parent window, or it is a changing
+                // window without bounds change.
                 animation = ActivityEmbeddingAnimationSpec.createNoopAnimation(change);
             } else if (Transitions.isClosingType(change.getMode())) {
                 animation = mAnimationSpec.createChangeBoundsCloseAnimation(change, parentBounds);
@@ -421,6 +435,74 @@
                 animationChange.getLeash(), cropBounds, Integer.MAX_VALUE);
     }
 
+    /**
+     * Whether we should use jump cut for the change transition.
+     * This normally happens when opening a new secondary with the existing primary using a
+     * different split layout. This can be complicated, like from horizontal to vertical split with
+     * new split pairs.
+     * Uses a jump cut animation to simplify.
+     */
+    private boolean shouldUseJumpCutForChangeTransition(@NonNull TransitionInfo info) {
+        // There can be reparenting of changing Activity to new open TaskFragment, so we need to
+        // exclude both in the first iteration.
+        final List<TransitionInfo.Change> changingChanges = new ArrayList<>();
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (change.getMode() != TRANSIT_CHANGE
+                    || change.getStartAbsBounds().equals(change.getEndAbsBounds())) {
+                continue;
+            }
+            changingChanges.add(change);
+            final WindowContainerToken parentToken = change.getParent();
+            if (parentToken != null) {
+                // When the parent window is also included in the transition as an opening window,
+                // we would like to animate the parent window instead.
+                final TransitionInfo.Change parentChange = info.getChange(parentToken);
+                if (parentChange != null && Transitions.isOpeningType(parentChange.getMode())) {
+                    changingChanges.add(parentChange);
+                }
+            }
+        }
+        if (changingChanges.isEmpty()) {
+            // No changing target found.
+            return true;
+        }
+
+        // Check if the transition contains both opening and closing windows.
+        boolean hasOpeningWindow = false;
+        boolean hasClosingWindow = false;
+        for (TransitionInfo.Change change : info.getChanges()) {
+            if (changingChanges.contains(change)) {
+                continue;
+            }
+            if (change.getParent() != null
+                    && changingChanges.contains(info.getChange(change.getParent()))) {
+                // No-op if it will be covered by the changing parent window.
+                continue;
+            }
+            hasOpeningWindow |= Transitions.isOpeningType(change.getMode());
+            hasClosingWindow |= Transitions.isClosingType(change.getMode());
+        }
+        return hasOpeningWindow && hasClosingWindow;
+    }
+
+    /** Updates the changes to end states in {@code startTransaction} for jump cut animation. */
+    private void prepareForJumpCut(@NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction) {
+        for (TransitionInfo.Change change : info.getChanges()) {
+            final SurfaceControl leash = change.getLeash();
+            startTransaction.setPosition(leash,
+                    change.getEndRelOffset().x, change.getEndRelOffset().y);
+            startTransaction.setWindowCrop(leash,
+                    change.getEndAbsBounds().width(), change.getEndAbsBounds().height());
+            if (change.getMode() == TRANSIT_CLOSE) {
+                startTransaction.hide(leash);
+            } else {
+                startTransaction.show(leash);
+                startTransaction.setAlpha(leash, 1f);
+            }
+        }
+    }
+
     /** To provide an {@link Animation} based on the transition infos. */
     private interface AnimationProvider {
         Animation get(@NonNull TransitionInfo info, @NonNull TransitionInfo.Change change,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
index d3a9a67..56b13b8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleBadgeIconFactory.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.drawable.AdaptiveIconDrawable;
@@ -59,7 +58,8 @@
     private class CircularRingDrawable extends CircularAdaptiveIcon {
 
         final int mImportantConversationColor;
-        final Rect mTempBounds = new Rect();
+        final int mRingWidth;
+        final Rect mInnerBounds = new Rect();
 
         final Drawable mDr;
 
@@ -68,6 +68,8 @@
             mDr = dr;
             mImportantConversationColor = mContext.getResources().getColor(
                     R.color.important_conversation, null);
+            mRingWidth = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.importance_ring_stroke_width);
         }
 
         @Override
@@ -75,11 +77,10 @@
             int save = canvas.save();
             canvas.clipPath(getIconMask());
             canvas.drawColor(mImportantConversationColor);
-            int ringStrokeWidth = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.importance_ring_stroke_width);
-            mTempBounds.set(getBounds());
-            mTempBounds.inset(ringStrokeWidth, ringStrokeWidth);
-            mDr.setBounds(mTempBounds);
+            mInnerBounds.set(getBounds());
+            mInnerBounds.inset(mRingWidth, mRingWidth);
+            canvas.translate(mInnerBounds.left, mInnerBounds.top);
+            mDr.setBounds(0, 0, mInnerBounds.width(), mInnerBounds.height());
             mDr.draw(canvas);
             canvas.restoreToCount(save);
         }
@@ -106,7 +107,6 @@
             int save = canvas.save();
             canvas.clipPath(getIconMask());
 
-            canvas.drawColor(Color.BLACK);
             Drawable d;
             if ((d = getBackground()) != null) {
                 d.draw(canvas);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 6e116b9..c836b95 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -51,8 +51,6 @@
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.SurfaceUtils;
 
-import java.util.function.Consumer;
-
 /**
  * Handles split decor like showing resizing hint for a specific split.
  */
@@ -72,17 +70,18 @@
     private SurfaceControl mIconLeash;
     private SurfaceControl mBackgroundLeash;
     private SurfaceControl mGapBackgroundLeash;
+    private SurfaceControl mScreenshot;
 
     private boolean mShown;
     private boolean mIsResizing;
     private final Rect mBounds = new Rect();
-    private final Rect mResizingBounds = new Rect();
     private final Rect mTempRect = new Rect();
     private ValueAnimator mFadeAnimator;
 
     private int mIconSize;
     private int mOffsetX;
     private int mOffsetY;
+    private int mRunningAnimationCount = 0;
 
     public SplitDecorManager(Configuration configuration, IconProvider iconProvider,
             SurfaceSession surfaceSession) {
@@ -173,7 +172,6 @@
             mIsResizing = true;
             mBounds.set(newBounds);
         }
-        mResizingBounds.set(newBounds);
         mOffsetX = offsetX;
         mOffsetY = offsetY;
 
@@ -227,33 +225,41 @@
                 t.setVisibility(mBackgroundLeash, show);
                 t.setVisibility(mIconLeash, show);
             } else {
-                startFadeAnimation(show, null /* finishedConsumer */);
+                startFadeAnimation(show, false, null);
             }
             mShown = show;
         }
     }
 
     /** Stops showing resizing hint. */
-    public void onResized(SurfaceControl.Transaction t) {
-        if (!mShown && mIsResizing) {
-            mTempRect.set(mResizingBounds);
-            mTempRect.offsetTo(-mOffsetX, -mOffsetY);
-            final SurfaceControl screenshot = ScreenshotUtils.takeScreenshot(t,
-                    mHostLeash, mTempRect, Integer.MAX_VALUE - 1);
+    public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
+        if (mScreenshot != null) {
+            t.setPosition(mScreenshot, mOffsetX, mOffsetY);
 
             final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
             final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
             va.addUpdateListener(valueAnimator -> {
                 final float progress = (float) valueAnimator.getAnimatedValue();
-                animT.setAlpha(screenshot, progress);
+                animT.setAlpha(mScreenshot, progress);
                 animT.apply();
             });
             va.addListener(new AnimatorListenerAdapter() {
                 @Override
+                public void onAnimationStart(Animator animation) {
+                    mRunningAnimationCount++;
+                }
+
+                @Override
                 public void onAnimationEnd(@androidx.annotation.NonNull Animator animation) {
-                    animT.remove(screenshot);
+                    mRunningAnimationCount--;
+                    animT.remove(mScreenshot);
                     animT.apply();
                     animT.close();
+                    mScreenshot = null;
+
+                    if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                        animFinishedCallback.run();
+                    }
                 }
             });
             va.start();
@@ -285,10 +291,34 @@
             mFadeAnimator.cancel();
         }
         if (mShown) {
-            fadeOutDecor(null /* finishedCallback */);
+            fadeOutDecor(animFinishedCallback);
         } else {
             // Decor surface is hidden so release it directly.
             releaseDecor(t);
+            if (mRunningAnimationCount == 0 && animFinishedCallback != null) {
+                animFinishedCallback.run();
+            }
+        }
+    }
+
+    /** Screenshot host leash and attach on it if meet some conditions */
+    public void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (!mShown && mIsResizing) {
+            mTempRect.set(mBounds);
+            mTempRect.offsetTo(0, 0);
+            mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
+                    Integer.MAX_VALUE - 1);
+        }
+    }
+
+    /** Set screenshot and attach on host leash it if meet some conditions */
+    public void setScreenshotIfNeeded(SurfaceControl screenshot, SurfaceControl.Transaction t) {
+        if (screenshot == null || !screenshot.isValid()) return;
+
+        if (!mShown && mIsResizing) {
+            mScreenshot = screenshot;
+            t.reparent(screenshot, mHostLeash);
+            t.setLayer(screenshot, Integer.MAX_VALUE - 1);
         }
     }
 
@@ -296,18 +326,15 @@
      * directly. */
     public void fadeOutDecor(Runnable finishedCallback) {
         if (mShown) {
-            startFadeAnimation(false /* show */, transaction -> {
-                releaseDecor(transaction);
-                if (finishedCallback != null) finishedCallback.run();
-            });
+            startFadeAnimation(false /* show */, true, finishedCallback);
             mShown = false;
         } else {
             if (finishedCallback != null) finishedCallback.run();
         }
     }
 
-    private void startFadeAnimation(boolean show,
-            Consumer<SurfaceControl.Transaction> finishedConsumer) {
+    private void startFadeAnimation(boolean show, boolean releaseSurface,
+            Runnable finishedCallback) {
         final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
         mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
         mFadeAnimator.setDuration(FADE_DURATION);
@@ -324,6 +351,7 @@
         mFadeAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationStart(@NonNull Animator animation) {
+                mRunningAnimationCount++;
                 if (show) {
                     animT.show(mBackgroundLeash).show(mIconLeash);
                 }
@@ -335,6 +363,7 @@
 
             @Override
             public void onAnimationEnd(@NonNull Animator animation) {
+                mRunningAnimationCount--;
                 if (!show) {
                     if (mBackgroundLeash != null) {
                         animT.hide(mBackgroundLeash);
@@ -343,11 +372,15 @@
                         animT.hide(mIconLeash);
                     }
                 }
-                if (finishedConsumer != null) {
-                    finishedConsumer.accept(animT);
+                if (releaseSurface) {
+                    releaseDecor(animT);
                 }
                 animT.apply();
                 animT.close();
+
+                if (mRunningAnimationCount == 0 && finishedCallback != null) {
+                    finishedCallback.run();
+                }
             }
         });
         mFadeAnimator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4ea8a5d..661c08b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -697,10 +697,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeController> providesDesktopModeController(
-            @DynamicOverride Optional<DesktopModeController> desktopModeController) {
-        if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeController;
+    static Optional<DesktopModeController> provideDesktopModeController(
+            @DynamicOverride Optional<Lazy<DesktopModeController>> desktopModeController) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
+        if (DesktopModeStatus.isProto1Enabled()) {
+            return desktopModeController.map(Lazy::get);
         }
         return Optional.empty();
     }
@@ -711,10 +714,13 @@
 
     @WMSingleton
     @Provides
-    static Optional<DesktopModeTaskRepository> providesDesktopTaskRepository(
-            @DynamicOverride Optional<DesktopModeTaskRepository> desktopModeTaskRepository) {
-        if (DesktopModeStatus.IS_SUPPORTED) {
-            return desktopModeTaskRepository;
+    static Optional<DesktopModeTaskRepository> provideDesktopTaskRepository(
+            @DynamicOverride Optional<Lazy<DesktopModeTaskRepository>> desktopModeTaskRepository) {
+        // Use optional-of-lazy for the dependency that this provider relies on.
+        // Lazy ensures that this provider will not be the cause the dependency is created
+        // when it will not be returned due to the condition below.
+        if (DesktopModeStatus.isAnyEnabled()) {
+            return desktopModeTaskRepository.map(Lazy::get);
         }
         return Optional.empty();
     }
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 f1670cd..6be8305 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
@@ -189,7 +189,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            @DynamicOverride DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
         return new CaptionWindowDecorViewModel(
                     context,
                     mainHandler,
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 abc4024..7eb01a7 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
@@ -64,6 +64,7 @@
 
 import java.util.ArrayList;
 import java.util.Comparator;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -99,7 +100,9 @@
         mDesktopModeTaskRepository = desktopModeTaskRepository;
         mMainExecutor = mainExecutor;
         mSettingsObserver = new SettingsObserver(mContext, mainHandler);
-        shellInit.addInitCallback(this::onInit, this);
+        if (DesktopModeStatus.isProto1Enabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
     }
 
     private void onInit() {
@@ -258,18 +261,36 @@
 
     @NonNull
     private WindowContainerTransaction bringDesktopAppsToFront() {
-        ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final ArraySet<Integer> activeTasks = mDesktopModeTaskRepository.getActiveTasks();
         ProtoLog.d(WM_SHELL_DESKTOP_MODE, "bringDesktopAppsToFront: tasks=%s", activeTasks.size());
-        ArrayList<RunningTaskInfo> taskInfos = new ArrayList<>();
+
+        final List<RunningTaskInfo> taskInfos = new ArrayList<>();
         for (Integer taskId : activeTasks) {
             RunningTaskInfo taskInfo = mShellTaskOrganizer.getRunningTaskInfo(taskId);
             if (taskInfo != null) {
                 taskInfos.add(taskInfo);
             }
         }
-        // Order by lastActiveTime, descending
-        taskInfos.sort(Comparator.comparingLong(task -> -task.lastActiveTime));
-        WindowContainerTransaction wct = new WindowContainerTransaction();
+
+        if (taskInfos.isEmpty()) {
+            return wct;
+        }
+
+        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;
+        }
+        ProtoLog.d(WM_SHELL_DESKTOP_MODE,
+                "bringDesktopAppsToFront: reordering all active tasks to the front");
+        final List<Integer> allTasksInZOrder =
+                mDesktopModeTaskRepository.getFreeformTasksInZOrder();
+        // Sort by z-order, bottom to top, so that the top-most task is reordered to the top last
+        // in the WCT.
+        taskInfos.sort(Comparator.comparingInt(task -> -allTasksInZOrder.indexOf(task.taskId)));
         for (RunningTaskInfo task : taskInfos) {
             wct.reorder(task.token, true);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 2fafe67..67f4a19 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -33,10 +33,38 @@
     /**
      * Flag to indicate whether desktop mode is available on the device
      */
-    public static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
+    private static final boolean IS_SUPPORTED = SystemProperties.getBoolean(
             "persist.wm.debug.desktop_mode", false);
 
     /**
+     * Flag to indicate whether desktop mode proto 2 is available on the device
+     */
+    private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_mode_2", false);
+
+    /**
+     * Return {@code true} if desktop mode support is enabled
+     */
+    public static boolean isProto1Enabled() {
+        return IS_SUPPORTED;
+    }
+
+    /**
+     * Return {@code true} is desktop windowing proto 2 is enabled
+     */
+    public static boolean isProto2Enabled() {
+        return IS_PROTO2_ENABLED;
+    }
+
+    /**
+     * Return {@code true} if proto 1 or 2 is enabled.
+     * Can be used to guard logic that is common for both prototypes.
+     */
+    public static boolean isAnyEnabled() {
+        return isProto1Enabled() || isProto2Enabled();
+    }
+
+    /**
      * Check if desktop mode is active
      *
      * @return {@code true} if active
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index b7749fc..600ccc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -33,6 +33,8 @@
      */
     private val activeTasks = ArraySet<Int>()
     private val visibleTasks = ArraySet<Int>()
+    // Tasks currently in freeform mode, ordered from top to bottom (top is at index 0).
+    private val freeformTasksInZOrder = mutableListOf<Int>()
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
     // Track visible tasks separately because a task may be part of the desktop but not visible.
     private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
@@ -101,6 +103,13 @@
     }
 
     /**
+     * Whether a task is visible.
+     */
+    fun isVisibleTask(taskId: Int): Boolean {
+        return visibleTasks.contains(taskId)
+    }
+
+    /**
      * Get a set of the active tasks
      */
     fun getActiveTasks(): ArraySet<Int> {
@@ -108,6 +117,13 @@
     }
 
     /**
+     * Get a list of freeform tasks, ordered from top-bottom (top at index 0).
+     */
+    fun getFreeformTasksInZOrder(): List<Int> {
+        return freeformTasksInZOrder
+    }
+
+    /**
      * Updates whether a freeform task with this id is visible or not and notifies listeners.
      */
     fun updateVisibleFreeformTasks(taskId: Int, visible: Boolean) {
@@ -127,6 +143,23 @@
     }
 
     /**
+     * Add (or move if it already exists) the task to the top of the ordered list.
+     */
+    fun addOrMoveFreeformTaskToTop(taskId: Int) {
+        if (freeformTasksInZOrder.contains(taskId)) {
+            freeformTasksInZOrder.remove(taskId)
+        }
+        freeformTasksInZOrder.add(0, taskId)
+    }
+
+    /**
+     * Remove the task from the ordered list.
+     */
+    fun removeFreeformTask(taskId: Int) {
+        freeformTasksInZOrder.remove(taskId)
+    }
+
+    /**
      * Defines interface for classes that can listen to changes for active tasks in desktop mode.
      */
     interface ActiveTasksListener {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
new file mode 100644
index 0000000..926cfb3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module desktop owners
+madym@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 44bcdb2..793bad8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -38,7 +38,8 @@
  * {@link ShellTaskOrganizer.TaskListener} for {@link
  * ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
  */
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener,
+        ShellTaskOrganizer.FocusListener {
     private static final String TAG = "FreeformTaskListener";
 
     private final ShellTaskOrganizer mShellTaskOrganizer;
@@ -67,6 +68,9 @@
 
     private void onInit() {
         mShellTaskOrganizer.addListenerForType(this, TASK_LISTENER_TYPE_FREEFORM);
+        if (DesktopModeStatus.isAnyEnabled()) {
+            mShellTaskOrganizer.addFocusListener(this);
+        }
     }
 
     @Override
@@ -86,13 +90,16 @@
             t.apply();
         }
 
-        if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
+        if (DesktopModeStatus.isAnyEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
-                if (repository.addActiveTask(taskInfo.taskId)) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                            "Adding active freeform task: #%d", taskInfo.taskId);
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+                if (taskInfo.isVisible) {
+                    if (repository.addActiveTask(taskInfo.taskId)) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                                "Adding active freeform task: #%d", taskInfo.taskId);
+                    }
+                    repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
                 }
-                repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
             });
         }
     }
@@ -103,8 +110,9 @@
                 taskInfo.taskId);
         mTasks.remove(taskInfo.taskId);
 
-        if (DesktopModeStatus.IS_SUPPORTED) {
+        if (DesktopModeStatus.isAnyEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.removeFreeformTask(taskInfo.taskId);
                 if (repository.removeActiveTask(taskInfo.taskId)) {
                     ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
                             "Removing active freeform task: #%d", taskInfo.taskId);
@@ -126,7 +134,7 @@
                 taskInfo.taskId);
         mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
 
-        if (DesktopModeStatus.IS_SUPPORTED) {
+        if (DesktopModeStatus.isAnyEnabled()) {
             mDesktopModeTaskRepository.ifPresent(repository -> {
                 if (taskInfo.isVisible) {
                     if (repository.addActiveTask(taskInfo.taskId)) {
@@ -140,6 +148,18 @@
     }
 
     @Override
+    public void onFocusTaskChanged(RunningTaskInfo taskInfo) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG,
+                "Freeform Task Focus Changed: #%d focused=%b",
+                taskInfo.taskId, taskInfo.isFocused);
+        if (DesktopModeStatus.isAnyEnabled() && taskInfo.isFocused) {
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                repository.addOrMoveFreeformTaskToTop(taskInfo.taskId);
+            });
+        }
+    }
+
+    @Override
     public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
         b.setParent(findTaskSurface(taskId));
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
new file mode 100644
index 0000000..0c2d5c4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-module freeform owners
+madym@google.com
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 85bad17..e6c7e10 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
@@ -358,8 +358,10 @@
             WindowContainerTransaction wct = null;
             if (isOutPipDirection(direction)) {
                 // Only need to reset surface properties. The server-side operations were already
-                // done at the start.
-                if (tx != null) {
+                // done at the start. But if it is running fixed rotation, there will be a seamless
+                // display transition later. So the last rotation transform needs to be kept to
+                // avoid flickering, and then the display transition will reset the transform.
+                if (tx != null && !mInFixedRotation) {
                     mFinishTransaction.merge(tx);
                 }
             } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 7908f35..db0f0bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -377,6 +377,8 @@
     public void dump(@NonNull PrintWriter pw, String prefix) {
         final String innerPrefix = prefix + "  ";
         pw.println(prefix + TAG);
+        pw.println(prefix + " mListener=" + mListener);
+        pw.println(prefix + "Tasks:");
         ArrayList<GroupedRecentTaskInfo> recentTasks = getRecentTasks(Integer.MAX_VALUE,
                 ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
         for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index d86aadc..2f2bc77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -73,6 +73,9 @@
     /** Called when device waking up finished. */
     void onFinishedWakingUp();
 
+    /** Called when requested to go to fullscreen from the current active split app. */
+    void goToFullscreenFromSplit();
+
     /** Get a string representation of a stage type */
     static String stageTypeToString(@StageType int stage) {
         switch (stage) {
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 b20125d..9329d02 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
@@ -123,6 +123,7 @@
     public static final int EXIT_REASON_SCREEN_LOCKED = 7;
     public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
     public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+    public static final int EXIT_REASON_FULLSCREEN_SHORTCUT = 10;
     @IntDef(value = {
             EXIT_REASON_UNKNOWN,
             EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -134,6 +135,7 @@
             EXIT_REASON_SCREEN_LOCKED,
             EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP,
             EXIT_REASON_CHILD_TASK_ENTER_PIP,
+            EXIT_REASON_FULLSCREEN_SHORTCUT,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface ExitReason{}
@@ -418,6 +420,10 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    public void goToFullscreenFromSplit() {
+        mStageCoordinator.goToFullscreenFromSplit();
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -523,10 +529,24 @@
             @SplitPosition int splitPosition, float splitRatio, RemoteAnimationAdapter adapter,
             InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
-                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
-            fillInIntent = new Intent();
-            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+                fillInIntent = new Intent();
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                try {
+                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+                    ActivityTaskManager.getService().startActivityFromRecents(taskId, options2);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
         }
         mStageCoordinator.startIntentAndTaskWithLegacyTransition(pendingIntent, fillInIntent,
                 options1, taskId, options2, splitPosition, splitRatio, adapter, instanceId);
@@ -536,10 +556,17 @@
             int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition,
             float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) {
         Intent fillInIntent = null;
-        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)
-                && supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
-            fillInIntent = new Intent();
-            fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        if (launchSameComponentAdjacently(pendingIntent, splitPosition, taskId)) {
+            if (supportMultiInstancesSplit(pendingIntent.getIntent().getComponent())) {
+                fillInIntent = new Intent();
+                fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+            }
         }
         mStageCoordinator.startIntentAndTask(pendingIntent, fillInIntent, options1, taskId,
                 options2, splitPosition, splitRatio, remoteTransition, instanceId);
@@ -551,12 +578,26 @@
             float splitRatio, RemoteAnimationAdapter adapter, InstanceId instanceId) {
         Intent fillInIntent1 = null;
         Intent fillInIntent2 = null;
-        if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)
-                && supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
-            fillInIntent1 = new Intent();
-            fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
-            fillInIntent2 = new Intent();
-            fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        if (launchSameComponentAdjacently(pendingIntent1, pendingIntent2)) {
+            if (supportMultiInstancesSplit(pendingIntent1.getIntent().getComponent())) {
+                fillInIntent1 = new Intent();
+                fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                fillInIntent2 = new Intent();
+                fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+            } else {
+                try {
+                    adapter.getRunner().onAnimationCancelled(false /* isKeyguardOccluded */);
+                    pendingIntent1.send();
+                } catch (RemoteException | PendingIntent.CanceledException e) {
+                    Slog.e(TAG, "Error starting remote animation", e);
+                }
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
+                Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
         }
         mStageCoordinator.startIntentsWithLegacyTransition(pendingIntent1, fillInIntent1, options1,
                 pendingIntent2, fillInIntent2, options2, splitPosition, splitRatio, adapter,
@@ -595,6 +636,8 @@
                 mStageCoordinator.switchSplitPosition("startIntent");
                 return;
             } else {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Cancel entering split as not supporting multi-instances");
                 Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text,
                         Toast.LENGTH_SHORT).show();
                 return;
@@ -860,9 +903,12 @@
 
         @Override
         public void onFinishedWakingUp() {
-            mMainExecutor.execute(() -> {
-                SplitScreenController.this.onFinishedWakingUp();
-            });
+            mMainExecutor.execute(SplitScreenController.this::onFinishedWakingUp);
+        }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            mMainExecutor.execute(SplitScreenController.this::goToFullscreenFromSplit);
         }
     }
 
@@ -918,33 +964,25 @@
         @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
-                    (controller) -> {
-                        controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN);
-                    });
+                    (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
         }
 
         @Override
         public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
-                    (controller) -> {
-                        controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
-                    });
+                    (controller) -> controller.exitSplitScreenOnHide(exitSplitScreenOnHide));
         }
 
         @Override
         public void removeFromSideStage(int taskId) {
             executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
-                    (controller) -> {
-                        controller.removeFromSideStage(taskId);
-                    });
+                    (controller) -> controller.removeFromSideStage(taskId));
         }
 
         @Override
         public void startTask(int taskId, int position, @Nullable Bundle options) {
             executeRemoteCallWithTaskPermission(mController, "startTask",
-                    (controller) -> {
-                        controller.startTask(taskId, position, options);
-                    });
+                    (controller) -> controller.startTask(taskId, position, options));
         }
 
         @Override
@@ -1036,19 +1074,16 @@
         public void startShortcut(String packageName, String shortcutId, int position,
                 @Nullable Bundle options, UserHandle user, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startShortcut",
-                    (controller) -> {
-                        controller.startShortcut(packageName, shortcutId, position, options, user,
-                                instanceId);
-                    });
+                    (controller) -> controller.startShortcut(packageName, shortcutId, position,
+                            options, user, instanceId));
         }
 
         @Override
         public void startIntent(PendingIntent intent, Intent fillInIntent, int position,
                 @Nullable Bundle options, InstanceId instanceId) {
             executeRemoteCallWithTaskPermission(mController, "startIntent",
-                    (controller) -> {
-                        controller.startIntent(intent, fillInIntent, position, options, instanceId);
-                    });
+                    (controller) -> controller.startIntent(intent, fillInIntent, position, options,
+                            instanceId));
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index 21a1310..1cf3a89 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -47,6 +47,7 @@
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
@@ -64,6 +65,7 @@
     DismissTransition mPendingDismiss = null;
     TransitSession mPendingEnter = null;
     TransitSession mPendingRecent = null;
+    TransitSession mPendingResize = null;
 
     private IBinder mAnimatingTransition = null;
     OneShotRemoteHandler mPendingRemoteHandler = null;
@@ -177,6 +179,43 @@
         onFinish(null /* wct */, null /* wctCB */);
     }
 
+    void applyResizeTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback,
+            @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+            @NonNull SplitDecorManager mainDecor, @NonNull SplitDecorManager sideDecor) {
+        mFinishCallback = finishCallback;
+        mAnimatingTransition = transition;
+        mFinishTransaction = finishTransaction;
+
+        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+            final TransitionInfo.Change change = info.getChanges().get(i);
+            if (mainRoot.equals(change.getContainer()) || sideRoot.equals(change.getContainer())) {
+                final SurfaceControl leash = change.getLeash();
+                startTransaction.setPosition(leash, change.getEndAbsBounds().left,
+                        change.getEndAbsBounds().top);
+                startTransaction.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                        change.getEndAbsBounds().height());
+
+                SplitDecorManager decor = mainRoot.equals(change.getContainer())
+                        ? mainDecor : sideDecor;
+                ValueAnimator va = new ValueAnimator();
+                mAnimations.add(va);
+                decor.setScreenshotIfNeeded(change.getSnapshot(), startTransaction);
+                decor.onResized(startTransaction, () -> {
+                    mTransitions.getMainExecutor().execute(() -> {
+                        mAnimations.remove(va);
+                        onFinish(null /* wct */, null /* wctCB */);
+                    });
+                });
+            }
+        }
+
+        startTransaction.apply();
+        onFinish(null /* wct */, null /* wctCB */);
+    }
+
     boolean isPendingTransition(IBinder transition) {
         return getPendingTransition(transition) != null;
     }
@@ -193,6 +232,10 @@
         return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
     }
 
+    boolean isPendingResize(IBinder transition) {
+        return mPendingResize != null && mPendingResize.mTransition == transition;
+    }
+
     @Nullable
     private TransitSession getPendingTransition(IBinder transition) {
         if (isPendingEnter(transition)) {
@@ -201,11 +244,14 @@
             return mPendingRecent;
         } else if (isPendingDismiss(transition)) {
             return mPendingDismiss;
+        } else if (isPendingResize(transition)) {
+            return mPendingResize;
         }
 
         return null;
     }
 
+
     /** Starts a transition to enter split with a remote transition animator. */
     IBinder startEnterTransition(
             @WindowManager.TransitionType int transitType,
@@ -258,6 +304,21 @@
                 exitReasonToString(reason), stageTypeToString(dismissTop));
     }
 
+    IBinder startResizeTransition(WindowContainerTransaction wct,
+            Transitions.TransitionHandler handler,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
+        setResizeTransition(transition, finishCallback);
+        return transition;
+    }
+
+    void setResizeTransition(@NonNull IBinder transition,
+            @Nullable TransitionFinishedCallback finishCallback) {
+        mPendingResize = new TransitSession(transition, null /* consumedCb */, finishCallback);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "  splitTransition "
+                + " deduced Resize split screen");
+    }
+
     void setRecentTransition(@NonNull IBinder transition,
             @Nullable RemoteTransition remoteTransition,
             @Nullable TransitionFinishedCallback finishCallback) {
@@ -324,6 +385,9 @@
             mPendingRecent.onConsumed(aborted);
             mPendingRecent = null;
             mPendingRemoteHandler = null;
+        } else if (isPendingResize(transition)) {
+            mPendingResize.onConsumed(aborted);
+            mPendingResize = null;
         }
     }
 
@@ -340,6 +404,9 @@
         } else if (isPendingDismiss(mAnimatingTransition)) {
             mPendingDismiss.onFinished(wct, mFinishTransaction);
             mPendingDismiss = null;
+        } else if (isPendingResize(mAnimatingTransition)) {
+            mPendingResize.onFinished(wct, mFinishTransaction);
+            mPendingResize = null;
         }
 
         mPendingRemoteHandler = null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index 2dc4a04..1016e1b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -23,6 +23,7 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
+import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__ROOT_TASK_VANISHED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
@@ -38,6 +39,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_ROOT_TASK_VANISHED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED;
@@ -180,6 +182,8 @@
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 return SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                return SPLITSCREEN_UICHANGED__EXIT_REASON__FULLSCREEN_SHORTCUT;
             case EXIT_REASON_UNKNOWN:
                 // Fall through
             default:
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 eb7b0d7..da8dc87 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
@@ -49,6 +49,7 @@
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_FULLSCREEN_SHORTCUT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DEVICE_FOLDED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -455,8 +456,6 @@
     void startIntentLegacy(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
             @Nullable Bundle options) {
         final boolean isEnteringSplit = !isSplitActive();
-        final WindowContainerTransaction evictWct = new WindowContainerTransaction();
-        prepareEvictChildTasks(position, evictWct);
 
         LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
             @Override
@@ -464,22 +463,21 @@
                     RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                     IRemoteAnimationFinishedCallback finishedCallback,
                     SurfaceControl.Transaction t) {
-                if (isEnteringSplit) {
-                    boolean openingToSide = false;
-                    if (apps != null) {
-                        for (int i = 0; i < apps.length; ++i) {
-                            if (apps[i].mode == MODE_OPENING
-                                    && mSideStage.containsTask(apps[i].taskId)) {
-                                openingToSide = true;
-                                break;
-                            }
+                boolean openingToSide = false;
+                if (apps != null) {
+                    for (int i = 0; i < apps.length; ++i) {
+                        if (apps[i].mode == MODE_OPENING
+                                && mSideStage.containsTask(apps[i].taskId)) {
+                            openingToSide = true;
+                            break;
                         }
                     }
-                    if (!openingToSide) {
-                        mMainExecutor.execute(() -> exitSplitScreen(
-                                mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
-                                EXIT_REASON_UNKNOWN));
-                    }
+                }
+
+                if (isEnteringSplit && !openingToSide) {
+                    mMainExecutor.execute(() -> exitSplitScreen(
+                            mSideStage.getChildCount() == 0 ? mMainStage : mSideStage,
+                            EXIT_REASON_UNKNOWN));
                 }
 
                 if (apps != null) {
@@ -499,7 +497,12 @@
                     }
                 }
 
-                mSyncQueue.queue(evictWct);
+
+                if (!isEnteringSplit && openingToSide) {
+                    final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+                    prepareEvictNonOpeningChildTasks(position, apps, evictWct);
+                    mSyncQueue.queue(evictWct);
+                }
             }
         };
 
@@ -1115,15 +1118,8 @@
      * Exits the split screen by finishing one of the tasks.
      */
     protected void exitStage(@SplitPosition int stageToClose) {
-        if (ENABLE_SHELL_TRANSITIONS) {
-            StageTaskListener stageToTop = mSideStagePosition == stageToClose
-                    ? mMainStage
-                    : mSideStage;
-            exitSplitScreen(stageToTop, EXIT_REASON_APP_FINISHED);
-        } else {
-            boolean toEnd = stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT;
-            mSplitLayout.flingDividerToDismiss(toEnd, EXIT_REASON_APP_FINISHED);
-        }
+        mSplitLayout.flingDividerToDismiss(stageToClose == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+                EXIT_REASON_APP_FINISHED);
     }
 
     /**
@@ -1157,6 +1153,9 @@
             case EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP:
                 // User has unlocked the device after folded
             case EXIT_REASON_DEVICE_FOLDED:
+                // The device is folded
+            case EXIT_REASON_FULLSCREEN_SHORTCUT:
+                // User has used a keyboard shortcut to go back to fullscreen from split
                 return true;
             default:
                 return false;
@@ -1670,15 +1669,29 @@
     public void onLayoutSizeChanged(SplitLayout layout) {
         // Reset this flag every time onLayoutSizeChanged.
         mShowDecorImmediately = false;
+
+        if (!ENABLE_SHELL_TRANSITIONS) {
+            // Only need screenshot for legacy case because shell transition should screenshot
+            // itself during transition.
+            final SurfaceControl.Transaction startT = mTransactionPool.acquire();
+            mMainStage.screenshotIfNeeded(startT);
+            mSideStage.screenshotIfNeeded(startT);
+            mTransactionPool.release(startT);
+        }
+
         final WindowContainerTransaction wct = new WindowContainerTransaction();
         updateWindowBounds(layout, wct);
         sendOnBoundsChanged();
-        mSyncQueue.queue(wct);
-        mSyncQueue.runInSync(t -> {
-            updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
-            mMainStage.onResized(t);
-            mSideStage.onResized(t);
-        });
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mSplitTransitions.startResizeTransition(wct, this, null /* callback */);
+        } else {
+            mSyncQueue.queue(wct);
+            mSyncQueue.runInSync(t -> {
+                updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
+                mMainStage.onResized(t);
+                mSideStage.onResized(t);
+            });
+        }
         mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
     }
 
@@ -2032,6 +2045,12 @@
         } else if (mSplitTransitions.isPendingDismiss(transition)) {
             shouldAnimate = startPendingDismissAnimation(
                     mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
+        } else if (mSplitTransitions.isPendingResize(transition)) {
+            mSplitTransitions.applyResizeTransition(transition, info, startTransaction,
+                    finishTransaction, finishCallback, mMainStage.mRootTaskInfo.token,
+                    mSideStage.mRootTaskInfo.token, mMainStage.getSplitDecorManager(),
+                    mSideStage.getSplitDecorManager());
+            return true;
         }
         if (!shouldAnimate) return false;
 
@@ -2119,6 +2138,16 @@
         return true;
     }
 
+    public void goToFullscreenFromSplit() {
+        boolean leftOrTop;
+        if (mSideStage.isFocused()) {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT);
+        } else {
+            leftOrTop = (mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT);
+        }
+        mSplitLayout.flingDividerToDismiss(!leftOrTop, EXIT_REASON_FULLSCREEN_SHORTCUT);
+    }
+
     /** Synchronize split-screen state with transition and make appropriate preparations. */
     public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
             @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 358f712..8a52c87 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -292,7 +292,13 @@
 
     void onResized(SurfaceControl.Transaction t) {
         if (mSplitDecorManager != null) {
-            mSplitDecorManager.onResized(t);
+            mSplitDecorManager.onResized(t, null);
+        }
+    }
+
+    void screenshotIfNeeded(SurfaceControl.Transaction t) {
+        if (mSplitDecorManager != null) {
+            mSplitDecorManager.screenshotIfNeeded(t);
         }
     }
 
@@ -304,6 +310,10 @@
         }
     }
 
+    SplitDecorManager getSplitDecorManager() {
+        return mSplitDecorManager;
+    }
+
     void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
         // Clear overridden bounds and windowing mode to make sure the child task can inherit
         // windowing mode and bounds from split root.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e7036c7..afefd5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
 import com.android.wm.shell.transition.Transitions;
 
+import java.util.Optional;
 import java.util.function.Supplier;
 
 /**
@@ -74,7 +75,7 @@
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
-    private DesktopModeController mDesktopModeController;
+    private Optional<DesktopModeController> mDesktopModeController;
     private boolean mTransitionDragActive;
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
@@ -90,7 +91,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            DesktopModeController desktopModeController) {
+            Optional<DesktopModeController> desktopModeController) {
         this(
                 context,
                 mainHandler,
@@ -110,7 +111,7 @@
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
             SyncTransactionQueue syncQueue,
-            DesktopModeController desktopModeController,
+            Optional<DesktopModeController> desktopModeController,
             CaptionWindowDecoration.Factory captionWindowDecorFactory,
             Supplier<InputManager> inputManagerSupplier) {
 
@@ -246,10 +247,10 @@
             } else if (id == R.id.caption_handle) {
                 decoration.createHandleMenu();
             } else if (id == R.id.desktop_button) {
-                mDesktopModeController.setDesktopModeActive(true);
+                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                 decoration.closeHandleMenu();
             } else if (id == R.id.fullscreen_button) {
-                mDesktopModeController.setDesktopModeActive(false);
+                mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 decoration.closeHandleMenu();
                 decoration.setButtonVisibility();
             }
@@ -304,9 +305,9 @@
          */
         private void handleEventForMove(MotionEvent e) {
             RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
-            int windowingMode = mDesktopModeController
-                    .getDisplayAreaWindowingMode(taskInfo.displayId);
-            if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            if (mDesktopModeController.isPresent()
+                    && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
+                    == WINDOWING_MODE_FULLSCREEN) {
                 return;
             }
             switch (e.getActionMasked()) {
@@ -331,7 +332,7 @@
                             e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
                     if (e.getRawY(dragPointerIdx) <= statusBarHeight
                             && DesktopModeStatus.isActive(mContext)) {
-                        mDesktopModeController.setDesktopModeActive(false);
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                     }
                     break;
                 }
@@ -471,7 +472,7 @@
                     int statusBarHeight = mDisplayController
                             .getDisplayLayout(focusedDecor.mTaskInfo.displayId).stableInsets().top;
                     if (ev.getY() > statusBarHeight) {
-                        mDesktopModeController.setDesktopModeActive(true);
+                        mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(true));
                         return;
                     }
                 }
@@ -516,7 +517,7 @@
 
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
-        return DesktopModeStatus.IS_SUPPORTED
+        return DesktopModeStatus.isAnyEnabled()
                 && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
index 89bafcb..b3c9e23 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeControllerTest.java
@@ -36,7 +36,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -50,6 +50,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowContainerTransaction.Change;
+import android.window.WindowContainerTransaction.HierarchyOp;
 
 import androidx.test.filters.SmallTest;
 
@@ -99,15 +100,14 @@
     @Before
     public void setUp() {
         mMockitoSession = mockitoSession().mockStatic(DesktopModeStatus.class).startMocking();
+        when(DesktopModeStatus.isProto1Enabled()).thenReturn(true);
         when(DesktopModeStatus.isActive(any())).thenReturn(true);
 
         mShellInit = Mockito.spy(new ShellInit(mTestExecutor));
 
         mDesktopModeTaskRepository = new DesktopModeTaskRepository();
 
-        mController = new DesktopModeController(mContext, mShellInit, mShellController,
-                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
-                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+        mController = createController();
 
         when(mShellTaskOrganizer.getRunningTasks(anyInt())).thenReturn(new ArrayList<>());
 
@@ -124,7 +124,17 @@
 
     @Test
     public void instantiate_addInitCallback() {
-        verify(mShellInit, times(1)).addInitCallback(any(), any());
+        verify(mShellInit).addInitCallback(any(), any());
+    }
+
+    @Test
+    public void instantiate_flagOff_doNotAddInitCallback() {
+        when(DesktopModeStatus.isProto1Enabled()).thenReturn(false);
+        clearInvocations(mShellInit);
+
+        createController();
+
+        verify(mShellInit, never()).addInitCallback(any(), any());
     }
 
     @Test
@@ -222,25 +232,29 @@
         // Check that there are hierarchy changes for home task and visible task
         assertThat(wct.getHierarchyOps()).hasSize(2);
         // First show home task
-        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
         assertThat(op1.getContainer()).isEqualTo(homeTask.token.asBinder());
 
         // Then visible task on top of it
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
         assertThat(op2.getContainer()).isEqualTo(fullscreenTask1.token.asBinder());
     }
 
     @Test
-    public void testShowDesktopApps() {
-        // Set up two active tasks on desktop
+    public void testShowDesktopApps_allAppsInvisible_bringsToFront() {
+        // Set up two active tasks on desktop, task2 is on top of task1.
         RunningTaskInfo freeformTask1 = createFreeformTask();
-        freeformTask1.lastActiveTime = 100;
-        RunningTaskInfo freeformTask2 = createFreeformTask();
-        freeformTask2.lastActiveTime = 200;
         mDesktopModeTaskRepository.addActiveTask(freeformTask1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask1.taskId, false /* visible */);
+        RunningTaskInfo freeformTask2 = createFreeformTask();
         mDesktopModeTaskRepository.addActiveTask(freeformTask2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(freeformTask2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(
+                freeformTask2.taskId, false /* visible */);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask1.taskId)).thenReturn(
                 freeformTask1);
         when(mShellTaskOrganizer.getRunningTaskInfo(freeformTask2.taskId)).thenReturn(
@@ -248,27 +262,66 @@
 
         // Run show desktop apps logic
         mController.showDesktopApps();
-        ArgumentCaptor<WindowContainerTransaction> wctCaptor = ArgumentCaptor.forClass(
-                WindowContainerTransaction.class);
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), wctCaptor.capture(), any());
-        } else {
-            verify(mShellTaskOrganizer).applyTransaction(wctCaptor.capture());
-        }
-        WindowContainerTransaction wct = wctCaptor.getValue();
 
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
         // Check wct has reorder calls
         assertThat(wct.getHierarchyOps()).hasSize(2);
 
-        // Task 2 has activity later, must be first
-        WindowContainerTransaction.HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        // Task 1 appeared first, must be first reorder to top.
+        HierarchyOp op1 = wct.getHierarchyOps().get(0);
         assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op1.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+        assertThat(op1.getContainer()).isEqualTo(freeformTask1.token.asBinder());
 
-        // Task 1 should be second
-        WindowContainerTransaction.HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        // Task 2 appeared last, must be last reorder to top.
+        HierarchyOp op2 = wct.getHierarchyOps().get(1);
         assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
-        assertThat(op2.getContainer()).isEqualTo(freeformTask1.token.asBinder());
+        assertThat(op2.getContainer()).isEqualTo(freeformTask2.token.asBinder());
+    }
+
+    @Test
+    public void testShowDesktopApps_appsAlreadyVisible_doesNothing() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // No reordering needed.
+        assertThat(wct.getHierarchyOps()).isEmpty();
+    }
+
+    @Test
+    public void testShowDesktopApps_someAppsInvisible_reordersAll() {
+        final RunningTaskInfo task1 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task1.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task1.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task1.taskId, false /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task1.taskId)).thenReturn(task1);
+        final RunningTaskInfo task2 = createFreeformTask();
+        mDesktopModeTaskRepository.addActiveTask(task2.taskId);
+        mDesktopModeTaskRepository.addOrMoveFreeformTaskToTop(task2.taskId);
+        mDesktopModeTaskRepository.updateVisibleFreeformTasks(task2.taskId, true /* visible */);
+        when(mShellTaskOrganizer.getRunningTaskInfo(task2.taskId)).thenReturn(task2);
+
+        mController.showDesktopApps();
+
+        final WindowContainerTransaction wct = getBringAppsToFrontTransaction();
+        // Both tasks should be reordered to top, even if one was already visible.
+        assertThat(wct.getHierarchyOps()).hasSize(2);
+        final HierarchyOp op1 = wct.getHierarchyOps().get(0);
+        assertThat(op1.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op1.getContainer()).isEqualTo(task1.token.asBinder());
+        final HierarchyOp op2 = wct.getHierarchyOps().get(1);
+        assertThat(op2.getType()).isEqualTo(HIERARCHY_OP_TYPE_REORDER);
+        assertThat(op2.getContainer()).isEqualTo(task2.token.asBinder());
     }
 
     @Test
@@ -309,6 +362,12 @@
         assertThat(wct).isNotNull();
     }
 
+    private DesktopModeController createController() {
+        return new DesktopModeController(mContext, mShellInit, mShellController,
+                mShellTaskOrganizer, mRootTaskDisplayAreaOrganizer, mTransitions,
+                mDesktopModeTaskRepository, mMockHandler, new TestShellExecutor());
+    }
+
     private DisplayAreaInfo createMockDisplayArea() {
         DisplayAreaInfo displayAreaInfo = new DisplayAreaInfo(new MockToken().mToken,
                 mContext.getDisplayId(), 0);
@@ -355,6 +414,17 @@
         return arg.getValue();
     }
 
+    private WindowContainerTransaction getBringAppsToFrontTransaction() {
+        final ArgumentCaptor<WindowContainerTransaction> arg = ArgumentCaptor.forClass(
+                WindowContainerTransaction.class);
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            verify(mTransitions).startTransition(eq(TRANSIT_TO_FRONT), arg.capture(), any());
+        } else {
+            verify(mShellTaskOrganizer).applyTransaction(arg.capture());
+        }
+        return arg.getValue();
+    }
+
     private void assertThatBoundsCleared(Change change) {
         assertThat((change.getWindowSetMask() & WINDOW_CONFIG_BOUNDS) != 0).isTrue();
         assertThat(change.getConfiguration().windowConfiguration.getBounds().isEmpty()).isTrue();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
index aaa5c8a..1e43a59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepositoryTest.kt
@@ -140,6 +140,32 @@
         assertThat(listener.visibleFreeformTaskChangedCalls).isEqualTo(3)
     }
 
+    @Test
+    fun addOrMoveFreeformTaskToTop_didNotExist_addsToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks[0]).isEqualTo(7)
+        assertThat(tasks[1]).isEqualTo(6)
+        assertThat(tasks[2]).isEqualTo(5)
+    }
+
+    @Test
+    fun addOrMoveFreeformTaskToTop_alreadyExists_movesToTop() {
+        repo.addOrMoveFreeformTaskToTop(5)
+        repo.addOrMoveFreeformTaskToTop(6)
+        repo.addOrMoveFreeformTaskToTop(7)
+
+        repo.addOrMoveFreeformTaskToTop(6)
+
+        val tasks = repo.getFreeformTasksInZOrder()
+        assertThat(tasks.size).isEqualTo(3)
+        assertThat(tasks.first()).isEqualTo(6)
+    }
+
     class TestListener : DesktopModeTaskRepository.ActiveTasksListener {
         var activeTaskChangedCalls = 0
         override fun onActiveTasksChanged() {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
new file mode 100644
index 0000000..736d4cf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/OWNERS
@@ -0,0 +1,3 @@
+# WM shell sub-module TV pip owners
+galinap@google.com
+bronger@google.com
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
index 9b37b97..ad6fced 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModelTests.java
@@ -54,6 +54,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.Supplier;
 
 /** Tests of {@link CaptionWindowDecorViewModel} */
@@ -101,7 +102,7 @@
                 mTaskOrganizer,
                 mDisplayController,
                 mSyncQueue,
-                mDesktopModeController,
+                Optional.of(mDesktopModeController),
                 mCaptionWindowDecorFactory,
                 new MockObjectSupplier<>(mMockInputManagers, () -> mock(InputManager.class)));
         mCaptionWindowDecorViewModel.setEventReceiverFactory(mEventReceiverFactory);
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 277955e..6affc6a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -82,7 +82,7 @@
 int Properties::contextPriority = 0;
 float Properties::defaultSdrWhitePoint = 200.f;
 
-bool Properties::useHintManager = true;
+bool Properties::useHintManager = false;
 int Properties::targetCpuTimePercentage = 70;
 
 bool Properties::enableWebViewOverlays = true;
@@ -142,7 +142,7 @@
 
     runningInEmulator = base::GetBoolProperty(PROPERTY_IS_EMULATOR, false);
 
-    useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, true);
+    useHintManager = base::GetBoolProperty(PROPERTY_USE_HINT_MANAGER, false);
     targetCpuTimePercentage = base::GetIntProperty(PROPERTY_TARGET_CPU_TIME_PERCENTAGE, 70);
     if (targetCpuTimePercentage <= 0 || targetCpuTimePercentage > 100) targetCpuTimePercentage = 70;
 
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 109aac3..7c732d7 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -37,6 +37,16 @@
     return indexBuffer;
 }
 
+// TODO(b/260252882): undefine SK_LEGACY_MESH_MAKE and remove this.
+template <typename T>
+SkMesh get_mesh_from_result(T&& result) {
+#ifdef SK_LEGACY_MESH_MAKE
+    return result;
+#else
+    return result.mesh;
+#endif
+}
+
 static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
                   jboolean isDirect, jint vertexCount, jint vertexOffset, jint left, jint top,
                   jint right, jint bottom) {
@@ -44,8 +54,8 @@
     sk_sp<SkMesh::VertexBuffer> skVertexBuffer =
             genVertexBuffer(env, vertexBuffer, vertexCount * skMeshSpec->stride(), isDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
-                             vertexOffset, nullptr, skRect);
+    auto mesh = get_mesh_from_result(SkMesh::Make(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer,
+                                                  vertexCount, vertexOffset, nullptr, skRect));
     auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
@@ -60,9 +70,9 @@
     sk_sp<SkMesh::IndexBuffer> skIndexBuffer =
             genIndexBuffer(env, indexBuffer, indexCount * gIndexByteSize, isIndexDirect);
     auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
-    auto mesh = SkMesh::MakeIndexed(skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount,
-                                    vertexOffset, skIndexBuffer, indexCount, indexOffset, nullptr,
-                                    skRect);
+    auto mesh = get_mesh_from_result(SkMesh::MakeIndexed(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            skIndexBuffer, indexCount, indexOffset, nullptr, skRect));
     auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
@@ -71,14 +81,15 @@
     auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     auto mesh = wrapper->mesh;
     if (indexed) {
-        wrapper->mesh = SkMesh::MakeIndexed(
+        wrapper->mesh = get_mesh_from_result(SkMesh::MakeIndexed(
                 sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
                 mesh.vertexCount(), mesh.vertexOffset(), sk_ref_sp(mesh.indexBuffer()),
-                mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+                mesh.indexCount(), mesh.indexOffset(), wrapper->builder.fUniforms, mesh.bounds()));
     } else {
-        wrapper->mesh = SkMesh::Make(
-                sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
-                mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms, mesh.bounds());
+        wrapper->mesh = get_mesh_from_result(
+                SkMesh::Make(sk_ref_sp(mesh.spec()), mesh.mode(), sk_ref_sp(mesh.vertexBuffer()),
+                             mesh.vertexCount(), mesh.vertexOffset(), wrapper->builder.fUniforms,
+                             mesh.bounds()));
     }
 }
 
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 0faa8f4..fd596d9 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -31,10 +31,8 @@
         const std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
         LOG_ALWAYS_FATAL_IF(ids.empty(), "%s: No displays", __FUNCTION__);
 
-        const sp<IBinder> token = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
-        LOG_ALWAYS_FATAL_IF(!token, "%s: No internal display", __FUNCTION__);
-
-        const status_t status = SurfaceComposerClient::getStaticDisplayInfo(token, &info);
+        const status_t status =
+                SurfaceComposerClient::getStaticDisplayInfo(ids.front().value, &info);
         LOG_ALWAYS_FATAL_IF(status, "%s: Failed to get display info", __FUNCTION__);
 #endif
         return info;
diff --git a/location/java/android/location/Country.java b/location/java/android/location/Country.java
index 8e1bb1f0..53cc943 100644
--- a/location/java/android/location/Country.java
+++ b/location/java/android/location/Country.java
@@ -16,6 +16,7 @@
 
 package android.location;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
@@ -24,6 +25,8 @@
 import android.os.Parcelable;
 import android.os.SystemClock;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Locale;
 
 /**
@@ -54,8 +57,22 @@
     public static final int COUNTRY_SOURCE_LOCALE = 3;
 
     /**
-     * The ISO 3166-1 two letters country code.
+     * Country source type
+     *
+     * @hide
      */
+    @IntDef(
+            prefix = {"COUNTRY_SOURCE_"},
+            value = {
+                    COUNTRY_SOURCE_NETWORK,
+                    COUNTRY_SOURCE_LOCATION,
+                    COUNTRY_SOURCE_SIM,
+                    COUNTRY_SOURCE_LOCALE
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CountrySource {}
+
+    /** The ISO 3166-1 two letters country code. */
     private final String mCountryIso;
 
     /**
@@ -73,21 +90,18 @@
 
     /**
      * @param countryIso the ISO 3166-1 two letters country code.
-     * @param source where the countryIso came from, could be one of below
-     *        values
-     *        <p>
-     *        <ul>
-     *        <li>{@link #COUNTRY_SOURCE_NETWORK}</li>
-     *        <li>{@link #COUNTRY_SOURCE_LOCATION}</li>
-     *        <li>{@link #COUNTRY_SOURCE_SIM}</li>
-     *        <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
-     *        </ul>
-     *
-     * @hide
+     * @param source where the countryIso came from, could be one of below values
+     *     <p>
+     *     <ul>
+     *       <li>{@link #COUNTRY_SOURCE_NETWORK}
+     *       <li>{@link #COUNTRY_SOURCE_LOCATION}
+     *       <li>{@link #COUNTRY_SOURCE_SIM}
+     *       <li>{@link #COUNTRY_SOURCE_LOCALE}
+     *     </ul>
      */
-    @UnsupportedAppUsage
-    public Country(final String countryIso, final int source) {
-        if (countryIso == null || source < COUNTRY_SOURCE_NETWORK
+    public Country(@NonNull final String countryIso, @CountrySource final int source) {
+        if (countryIso == null
+                || source < COUNTRY_SOURCE_NETWORK
                 || source > COUNTRY_SOURCE_LOCALE) {
             throw new IllegalArgumentException();
         }
@@ -115,9 +129,24 @@
 
     /**
      * @return the ISO 3166-1 two letters country code
+     *
+     * @hide
+     *
+     * @deprecated clients using getCountryIso should use the {@link #getCountryCode()} API instead.
+     */
+    @UnsupportedAppUsage
+    @Deprecated
+    public String getCountryIso() {
+        return mCountryIso;
+    }
+
+    /**
+     * Retrieves country code.
+     *
+     * @return country code in ISO 3166-1:alpha2
      */
     @NonNull
-    public String getCountryIso() {
+    public String getCountryCode() {
         return mCountryIso;
     }
 
@@ -131,6 +160,7 @@
      *         <li>{@link #COUNTRY_SOURCE_LOCALE}</li>
      *         </ul>
      */
+    @CountrySource
     public int getSource() {
         return mSource;
     }
diff --git a/location/java/android/location/CountryDetector.java b/location/java/android/location/CountryDetector.java
index 3a0edfc..6abb350 100644
--- a/location/java/android/location/CountryDetector.java
+++ b/location/java/android/location/CountryDetector.java
@@ -24,28 +24,32 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Handler;
+import android.os.HandlerExecutor;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 
 import java.util.HashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
- * This class provides access to the system country detector service. This
- * service allows applications to obtain the country that the user is in.
- * <p>
- * The country will be detected in order of reliability, like
+ * This class provides access to the system country detector service. This service allows
+ * applications to obtain the country that the user is in.
+ *
+ * <p>The country will be detected in order of reliability, like
+ *
  * <ul>
- * <li>Mobile network</li>
- * <li>Location</li>
- * <li>SIM's country</li>
- * <li>Phone's locale</li>
+ *   <li>Mobile network
+ *   <li>Location
+ *   <li>SIM's country
+ *   <li>Phone's locale
  * </ul>
- * <p>
- * Call the {@link #detectCountry()} to get the available country immediately.
- * <p>
- * To be notified of the future country change, use the
- * {@link #addCountryListener}
+ *
+ * <p>Call the {@link #detectCountry()} to get the available country immediately.
+ *
+ * <p>To be notified of the future country change, use the {@link #addCountryListener}
+ *
  * <p>
  *
  * @hide
@@ -55,57 +59,52 @@
 public class CountryDetector {
 
     /**
-     * The class to wrap the ICountryListener.Stub and CountryListener objects
-     * together. The CountryListener will be notified through the specific
-     * looper once the country changed and detected.
+     * The class to wrap the ICountryListener.Stub , CountryListener & {@code Consumer<Country>}
+     * objects together.
+     *
+     * <p>The CountryListener will be notified through the Handler Executor once the country changed
+     * and detected.
+     *
+     * <p>{@code Consumer<Country>} callback interface is notified through the specific executor
+     * once the country changed and detected.
      */
-    private final static class ListenerTransport extends ICountryListener.Stub {
+    private static final class ListenerTransport extends ICountryListener.Stub {
 
-        private final CountryListener mListener;
+        private final Consumer<Country> mListener;
+        private final Executor mExecutor;
 
-        private final Handler mHandler;
-
-        public ListenerTransport(CountryListener listener, Looper looper) {
-            mListener = listener;
-            if (looper != null) {
-                mHandler = new Handler(looper);
-            } else {
-                mHandler = new Handler();
-            }
+        ListenerTransport(Consumer<Country> consumer, Executor executor) {
+            mListener = consumer;
+            mExecutor = executor;
         }
 
         public void onCountryDetected(final Country country) {
-            mHandler.post(new Runnable() {
-                public void run() {
-                    mListener.onCountryDetected(country);
-                }
-            });
+            mExecutor.execute(() -> mListener.accept(country));
         }
     }
 
-    private final static String TAG = "CountryDetector";
+    private static final String TAG = "CountryDetector";
     private final ICountryDetector mService;
-    private final HashMap<CountryListener, ListenerTransport> mListeners;
+    private final HashMap<Consumer<Country>, ListenerTransport> mListeners;
 
     /**
-     * @hide - hide this constructor because it has a parameter of type
-     *       ICountryDetector, which is a system private class. The right way to
-     *       create an instance of this class is using the factory
-     *       Context.getSystemService.
+     * @hide - hide this constructor because it has a parameter of type ICountryDetector, which is a
+     *     system private class. The right way to create an instance of this class is using the
+     *     factory Context.getSystemService.
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     public CountryDetector(ICountryDetector service) {
         mService = service;
-        mListeners = new HashMap<CountryListener, ListenerTransport>();
+        mListeners = new HashMap<>();
     }
 
     /**
      * Start detecting the country that the user is in.
      *
-     * @return the country if it is available immediately, otherwise null will
-     *         be returned.
+     * @return the country if it is available immediately, otherwise null will be returned.
+     * @hide
      */
-    @Nullable
+    @UnsupportedAppUsage
     public Country detectCountry() {
         try {
             return mService.detectCountry();
@@ -116,40 +115,64 @@
     }
 
     /**
-     * Add a listener to receive the notification when the country is detected
-     * or changed.
+     * Add a listener to receive the notification when the country is detected or changed.
      *
      * @param listener will be called when the country is detected or changed.
-     * @param looper a Looper object whose message queue will be used to
-     *        implement the callback mechanism. If looper is null then the
-     *        callbacks will be called on the main thread.
+     * @param looper a Looper object whose message queue will be used to implement the callback
+     *     mechanism. If looper is null then the callbacks will be called on the main thread.
+     * @hide
+     * @deprecated client using this api should use {@link
+     *     #registerCountryDetectorCallback(Executor, Consumer)} }
      */
+    @UnsupportedAppUsage
+    @Deprecated
     public void addCountryListener(@NonNull CountryListener listener, @Nullable Looper looper) {
-        synchronized (mListeners) {
-            if (!mListeners.containsKey(listener)) {
-                ListenerTransport transport = new ListenerTransport(listener, looper);
-                try {
-                    mService.addCountryListener(transport);
-                    mListeners.put(listener, transport);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "addCountryListener: RemoteException", e);
-                }
-            }
-        }
+        Handler handler = looper != null ? new Handler(looper) : new Handler();
+        registerCountryDetectorCallback(new HandlerExecutor(handler), listener);
     }
 
     /**
      * Remove the listener
+     *
+     * @hide
+     * @deprecated client using this api should use {@link
+     *     #unregisterCountryDetectorCallback(Consumer)}
      */
-    public void removeCountryListener(@NonNull CountryListener listener) {
+    @UnsupportedAppUsage
+    @Deprecated
+    public void removeCountryListener(CountryListener listener) {
+        unregisterCountryDetectorCallback(listener);
+    }
+
+    /**
+     * Add a callback interface, to be notified when country code is added or changes.
+     *
+     * @param executor The callback executor for the response.
+     * @param consumer {@link Consumer} callback to receive the country code when changed/detected
+     */
+    public void registerCountryDetectorCallback(
+            @NonNull Executor executor, @NonNull Consumer<Country> consumer) {
         synchronized (mListeners) {
-            ListenerTransport transport = mListeners.get(listener);
+            unregisterCountryDetectorCallback(consumer);
+            ListenerTransport transport = new ListenerTransport(consumer, executor);
+            try {
+                mService.addCountryListener(transport);
+                mListeners.put(consumer, transport);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /** Remove the callback subscribed to Update country code */
+    public void unregisterCountryDetectorCallback(@NonNull Consumer<Country> consumer) {
+        synchronized (mListeners) {
+            ListenerTransport transport = mListeners.remove(consumer);
             if (transport != null) {
                 try {
-                    mListeners.remove(listener);
                     mService.removeCountryListener(transport);
                 } catch (RemoteException e) {
-                    Log.e(TAG, "removeCountryListener: RemoteException", e);
+                    throw e.rethrowFromSystemServer();
                 }
             }
         }
diff --git a/location/java/android/location/CountryListener.java b/location/java/android/location/CountryListener.java
index 5c06d82..0ca6962 100644
--- a/location/java/android/location/CountryListener.java
+++ b/location/java/android/location/CountryListener.java
@@ -16,8 +16,9 @@
 
 package android.location;
 
-import android.annotation.NonNull;
-import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.util.function.Consumer;
 
 /**
  * The listener for receiving the notification when the country is detected or
@@ -25,11 +26,17 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
-public interface CountryListener {
+public interface CountryListener extends Consumer<Country> {
     /**
      * @param country the changed or detected country.
-     *
      */
-    void onCountryDetected(@NonNull Country country);
+    @UnsupportedAppUsage
+    void onCountryDetected(Country country);
+
+    /**
+     * @param country the changed or detected country.
+     */
+    default void accept(Country country) {
+        onCountryDetected(country);
+    }
 }
diff --git a/location/java/android/location/altitude/AltitudeConverter.java b/location/java/android/location/altitude/AltitudeConverter.java
new file mode 100644
index 0000000..d46b4d2
--- /dev/null
+++ b/location/java/android/location/altitude/AltitudeConverter.java
@@ -0,0 +1,172 @@
+/*
+ * 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.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+import android.content.Context;
+import android.location.Location;
+
+import com.android.internal.location.altitude.GeoidHeightMap;
+import com.android.internal.location.altitude.S2CellIdUtils;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+
+/**
+ * Converts altitudes reported above the World Geodetic System 1984 (WGS84) reference ellipsoid
+ * into ones above Mean Sea Level.
+ */
+public final class AltitudeConverter {
+
+    private static final double MAX_ABS_VALID_LATITUDE = 90;
+    private static final double MAX_ABS_VALID_LONGITUDE = 180;
+
+    /** Manages a mapping of geoid heights associated with S2 cells. */
+    private final GeoidHeightMap mGeoidHeightMap = new GeoidHeightMap();
+
+    /**
+     * Creates an instance that manages an independent cache to optimized conversions of locations
+     * in proximity to one another.
+     */
+    public AltitudeConverter() {
+    }
+
+    /**
+     * Throws an {@link IllegalArgumentException} if the {@code location} has an invalid latitude,
+     * longitude, or altitude above WGS84.
+     */
+    private static void validate(@NonNull Location location) {
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLatitude(), MAX_ABS_VALID_LATITUDE),
+                "Invalid latitude: %f", location.getLatitude());
+        Preconditions.checkArgument(
+                isFiniteAndAtAbsMost(location.getLongitude(), MAX_ABS_VALID_LONGITUDE),
+                "Invalid longitude: %f", location.getLongitude());
+        Preconditions.checkArgument(location.hasAltitude(), "Missing altitude above WGS84");
+        Preconditions.checkArgument(Double.isFinite(location.getAltitude()),
+                "Invalid altitude above WGS84: %f", location.getAltitude());
+    }
+
+    private static boolean isFiniteAndAtAbsMost(double value, double rhs) {
+        return Double.isFinite(value) && Math.abs(value) <= rhs;
+    }
+
+    /**
+     * Returns the four S2 cell IDs for the map square associated with the {@code location}.
+     *
+     * <p>The first map cell contains the location, while the others are located horizontally,
+     * vertically, and diagonally, in that order, with respect to the S2 (i,j) coordinate system. If
+     * the diagonal map cell does not exist (i.e., the location is near an S2 cube vertex), its
+     * corresponding ID is set to zero.
+     */
+    @NonNull
+    private static long[] findMapSquare(@NonNull MapParamsProto params,
+            @NonNull Location location) {
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+
+        // (0,0) cell.
+        long s0 = S2CellIdUtils.getParent(s2CellId, params.mapS2Level);
+        long[] edgeNeighbors = new long[4];
+        S2CellIdUtils.getEdgeNeighbors(s0, edgeNeighbors);
+
+        // (1,0) cell.
+        int i1 = S2CellIdUtils.getI(s2CellId) > S2CellIdUtils.getI(s0) ? -1 : 1;
+        long s1 = edgeNeighbors[i1 + 2];
+
+        // (0,1) cell.
+        int i2 = S2CellIdUtils.getJ(s2CellId) > S2CellIdUtils.getJ(s0) ? 1 : -1;
+        long s2 = edgeNeighbors[i2 + 1];
+
+        // (1,1) cell.
+        S2CellIdUtils.getEdgeNeighbors(s1, edgeNeighbors);
+        long s3 = 0;
+        for (int i = 0; i < edgeNeighbors.length; i++) {
+            if (edgeNeighbors[i] == s0) {
+                int i3 = (i + i1 * i2 + edgeNeighbors.length) % edgeNeighbors.length;
+                s3 = edgeNeighbors[i3] == s2 ? 0 : edgeNeighbors[i3];
+                break;
+            }
+        }
+
+        // Reuse edge neighbors' array to avoid an extra allocation.
+        edgeNeighbors[0] = s0;
+        edgeNeighbors[1] = s1;
+        edgeNeighbors[2] = s2;
+        edgeNeighbors[3] = s3;
+        return edgeNeighbors;
+    }
+
+    /**
+     * Adds to {@code location} the bilinearly interpolated Mean Sea Level altitude. In addition, a
+     * Mean Sea Level altitude accuracy is added if the {@code location} has a valid vertical
+     * accuracy; otherwise, does not add a corresponding accuracy.
+     */
+    private static void addMslAltitude(@NonNull MapParamsProto params, @NonNull long[] s2CellIds,
+            @NonNull double[] geoidHeightsMeters, @NonNull Location location) {
+        long s0 = s2CellIds[0];
+        double h0 = geoidHeightsMeters[0];
+        double h1 = geoidHeightsMeters[1];
+        double h2 = geoidHeightsMeters[2];
+        double h3 = s2CellIds[3] == 0 ? h0 : geoidHeightsMeters[3];
+
+        // Bilinear interpolation on an S2 square of size equal to that of a map cell. wi and wj
+        // are the normalized [0,1] weights in the i and j directions, respectively, allowing us to
+        // employ the simplified unit square formulation.
+        long s2CellId = S2CellIdUtils.fromLatLngDegrees(location.getLatitude(),
+                location.getLongitude());
+        double sizeIj = 1 << (S2CellIdUtils.MAX_LEVEL - params.mapS2Level);
+        double wi = Math.abs(S2CellIdUtils.getI(s2CellId) - S2CellIdUtils.getI(s0)) / sizeIj;
+        double wj = Math.abs(S2CellIdUtils.getJ(s2CellId) - S2CellIdUtils.getJ(s0)) / sizeIj;
+        double offsetMeters = h0 + (h1 - h0) * wi + (h2 - h0) * wj + (h3 - h1 - h2 + h0) * wi * wj;
+
+        location.setMslAltitudeMeters(location.getAltitude() - offsetMeters);
+        if (location.hasVerticalAccuracy()) {
+            double verticalAccuracyMeters = location.getVerticalAccuracyMeters();
+            if (Double.isFinite(verticalAccuracyMeters) && verticalAccuracyMeters >= 0) {
+                location.setMslAltitudeAccuracyMeters(
+                        (float) Math.hypot(verticalAccuracyMeters, params.modelRmseMeters));
+            }
+        }
+    }
+
+    /**
+     * Adds a Mean Sea Level altitude to the {@code location}. In addition, adds a Mean Sea Level
+     * altitude accuracy if the {@code location} has a finite and non-negative vertical accuracy;
+     * otherwise, does not add a corresponding accuracy.
+     *
+     * <p>Must be called off the main thread as data may be loaded from raw assets.
+     *
+     * @throws IOException              if an I/O error occurs when loading data from raw assets.
+     * @throws IllegalArgumentException if the {@code location} has an invalid latitude, longitude,
+     *                                  or altitude above WGS84. Specifically, the latitude must be
+     *                                  between -90 and 90 (both inclusive), the longitude must be
+     *                                  between -180 and 180 (both inclusive), and the altitude
+     *                                  above WGS84 must be finite.
+     */
+    @WorkerThread
+    public void addMslAltitudeToLocation(@NonNull Context context, @NonNull Location location)
+            throws IOException {
+        validate(location);
+        MapParamsProto params = GeoidHeightMap.getParams(context);
+        long[] s2CellIds = findMapSquare(params, location);
+        double[] geoidHeightsMeters = mGeoidHeightMap.readGeoidHeights(params, context, s2CellIds);
+        addMslAltitude(params, s2CellIds, geoidHeightsMeters, location);
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/GeoidHeightMap.java b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
new file mode 100644
index 0000000..6430eb4
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/GeoidHeightMap.java
@@ -0,0 +1,367 @@
+/*
+ * 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.internal.location.altitude;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.util.LruCache;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.altitude.nano.MapParamsProto;
+import com.android.internal.location.altitude.nano.S2TileProto;
+import com.android.internal.util.Preconditions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Objects;
+
+/**
+ * Manages a mapping of geoid heights associated with S2 cells, referred to as MAP CELLS.
+ *
+ * <p>Tiles are used extensively to reduce the number of entries needed to be stored in memory and
+ * on disk. A tile associates geoid heights with all map cells of a common parent at a specified S2
+ * level.
+ *
+ * <p>Since bilinear interpolation considers at most four map cells at a time, at most four tiles
+ * are simultaneously stored in memory. These tiles, referred to as CACHE TILES, are each keyed by
+ * its common parent's S2 cell ID, referred to as a CACHE KEY.
+ *
+ * <p>Absent cache tiles needed for interpolation are constructed from larger tiles stored on disk.
+ * The latter tiles, referred to as DISK TILES, are each keyed by its common parent's S2 cell token,
+ * referred to as a DISK TOKEN.
+ */
+public final class GeoidHeightMap {
+
+    private static final Object sLock = new Object();
+
+    @GuardedBy("sLock")
+    @Nullable
+    private static MapParamsProto sParams;
+
+    /** Defines a cache large enough to hold all cache tiles needed for interpolation. */
+    private final LruCache<Long, S2TileProto> mCacheTiles = new LruCache<>(4);
+
+    /**
+     * Returns the singleton parameter instance for a spherically projected geoid height map and its
+     * corresponding tile management.
+     */
+    @NonNull
+    public static MapParamsProto getParams(@NonNull Context context) throws IOException {
+        synchronized (sLock) {
+            if (sParams == null) {
+                try (InputStream is = context.getApplicationContext().getAssets().open(
+                        "geoid_height_map/map-params.pb")) {
+                    sParams = MapParamsProto.parseFrom(is.readAllBytes());
+                }
+            }
+            return sParams;
+        }
+    }
+
+    private static long getCacheKey(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getParent(s2CellId, params.cacheTileS2Level);
+    }
+
+    @NonNull
+    private static String getDiskToken(@NonNull MapParamsProto params, long s2CellId) {
+        return S2CellIdUtils.getToken(
+                S2CellIdUtils.getParent(s2CellId, params.diskTileS2Level));
+    }
+
+    /**
+     * Adds to {@code values} values in the unit interval [0, 1] for the map cells identified by
+     * {@code s2CellIds}. Returns true if values are present for all non-zero IDs; otherwise,
+     * returns false and adds NaNs for absent values.
+     */
+    private static boolean getUnitIntervalValues(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction,
+            @NonNull long[] s2CellIds, @NonNull double[] values) {
+        int len = s2CellIds.length;
+
+        S2TileProto[] tiles = new S2TileProto[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] != 0) {
+                tiles[i] = tileFunction.getTile(s2CellIds[i]);
+            }
+            values[i] = Double.NaN;
+        }
+
+        for (int i = 0; i < len; i++) {
+            if (tiles[i] == null || !Double.isNaN(values[i])) {
+                continue;
+            }
+
+            mergeByteBufferValues(params, s2CellIds, tiles, i, values);
+            mergeByteJpegValues(params, s2CellIds, tiles, i, values);
+            mergeBytePngValues(params, s2CellIds, tiles, i, values);
+        }
+
+        boolean allFound = true;
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            if (Double.isNaN(values[i])) {
+                allFound = false;
+            } else {
+                values[i] = (((int) values[i]) & 0xFF) / 255.0;
+            }
+        }
+        return allFound;
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteBufferValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        byte[] bytes = tiles[tileIndex].byteBuffer;
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+
+        ByteBuffer byteBuffer = ByteBuffer.wrap(bytes).asReadOnlyBuffer();
+        int tileS2Level = params.mapS2Level - Integer.numberOfTrailingZeros(byteBuffer.limit()) / 2;
+        int numBitsLeftOfTile = 2 * tileS2Level + 3;
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            long maskedS2CellId = s2CellIds[i] & (-1L >>> numBitsLeftOfTile);
+            int numBitsRightOfMap = 2 * (S2CellIdUtils.MAX_LEVEL - params.mapS2Level) + 1;
+            int bufferIndex = (int) (maskedS2CellId >>> numBitsRightOfMap);
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += ((int) byteBuffer.get(bufferIndex)) & 0xFF;
+        }
+    }
+
+    private static void mergeByteJpegValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].byteJpeg, s2CellIds, tiles, tileIndex,
+                values);
+    }
+
+    private static void mergeBytePngValues(@NonNull MapParamsProto params,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles,
+            int tileIndex, @NonNull double[] values) {
+        mergeByteImageValues(params, tiles[tileIndex].bytePng, s2CellIds, tiles, tileIndex, values);
+    }
+
+    @SuppressWarnings("ReferenceEquality")
+    private static void mergeByteImageValues(@NonNull MapParamsProto params, @NonNull byte[] bytes,
+            @NonNull long[] s2CellIds,
+            @NonNull S2TileProto[] tiles, int tileIndex, @NonNull double[] values) {
+        if (bytes == null || bytes.length == 0) {
+            return;
+        }
+        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
+        if (bitmap == null) {
+            return;
+        }
+
+        for (int i = tileIndex; i < tiles.length; i++) {
+            if (s2CellIds[i] == 0 || tiles[i] != tiles[tileIndex]) {
+                continue;
+            }
+
+            values[i] = Double.isNaN(values[i]) ? 0 : values[i];
+            values[i] += bitmap.getPixel(getIndexX(params, s2CellIds[i], bitmap.getWidth()),
+                    getIndexY(params, s2CellIds[i], bitmap.getHeight())) & 0xFF;
+        }
+    }
+
+    /** Returns the X index for an S2 cell within an S2 tile image of specified width. */
+    private static int getIndexX(@NonNull MapParamsProto params, long s2CellId, int width) {
+        return getIndexXOrY(params, S2CellIdUtils.getI(s2CellId), width);
+    }
+
+    /** Returns the Y index for an S2 cell within an S2 tile image of specified height. */
+    private static int getIndexY(@NonNull MapParamsProto params, long s2CellId, int height) {
+        return getIndexXOrY(params, S2CellIdUtils.getJ(s2CellId), height);
+    }
+
+    private static int getIndexXOrY(@NonNull MapParamsProto params, int iOrJ, int widthOrHeight) {
+        return (iOrJ >> (S2CellIdUtils.MAX_LEVEL - params.mapS2Level)) % widthOrHeight;
+    }
+
+    /**
+     * Returns the geoid heights in meters associated with the map cells identified by
+     * {@code s2CellIds}. Throws an {@link IOException} if a geoid height cannot be calculated for a
+     * non-zero ID.
+     */
+    @NonNull
+    public double[] readGeoidHeights(@NonNull MapParamsProto params, @NonNull Context context,
+            @NonNull long[] s2CellIds) throws IOException {
+        Preconditions.checkArgument(s2CellIds.length == 4);
+        for (long s2CellId : s2CellIds) {
+            Preconditions.checkArgument(
+                    s2CellId == 0 || S2CellIdUtils.getLevel(s2CellId) == params.mapS2Level);
+        }
+
+        double[] heightsMeters = new double[s2CellIds.length];
+        if (getGeoidHeights(params, mCacheTiles::get, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+
+        TileFunction loadedTiles = loadFromCacheAndDisk(params, context, s2CellIds);
+        if (getGeoidHeights(params, loadedTiles, s2CellIds, heightsMeters)) {
+            return heightsMeters;
+        }
+        throw new IOException("Unable to calculate geoid heights from raw assets.");
+    }
+
+    /**
+     * Adds to {@code heightsMeters} the geoid heights in meters associated with the map cells
+     * identified by {@code s2CellIds}. Returns true if heights are present for all non-zero IDs;
+     * otherwise, returns false and adds NaNs for absent heights.
+     */
+    private boolean getGeoidHeights(@NonNull MapParamsProto params,
+            @NonNull TileFunction tileFunction, @NonNull long[] s2CellIds,
+            @NonNull double[] heightsMeters) {
+        boolean allFound = getUnitIntervalValues(params, tileFunction, s2CellIds, heightsMeters);
+        for (int i = 0; i < heightsMeters.length; i++) {
+            // NaNs are properly preserved.
+            heightsMeters[i] *= params.modelAMeters;
+            heightsMeters[i] += params.modelBMeters;
+        }
+        return allFound;
+    }
+
+    @NonNull
+    private TileFunction loadFromCacheAndDisk(@NonNull MapParamsProto params,
+            @NonNull Context context, @NonNull long[] s2CellIds) throws IOException {
+        int len = s2CellIds.length;
+
+        // Enable batch loading by finding all cache keys upfront.
+        long[] cacheKeys = new long[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0) {
+                continue;
+            }
+            cacheKeys[i] = getCacheKey(params, s2CellIds[i]);
+        }
+
+        // Attempt to load tiles from cache.
+        S2TileProto[] loadedTiles = new S2TileProto[len];
+        String[] diskTokens = new String[len];
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || diskTokens[i] != null) {
+                continue;
+            }
+            loadedTiles[i] = mCacheTiles.get(cacheKeys[i]);
+            diskTokens[i] = getDiskToken(params, cacheKeys[i]);
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                    diskTokens[j] = diskTokens[i];
+                }
+            }
+        }
+
+        // Attempt to load tiles from disk.
+        for (int i = 0; i < len; i++) {
+            if (s2CellIds[i] == 0 || loadedTiles[i] != null) {
+                continue;
+            }
+
+            S2TileProto tile;
+            try (InputStream is = context.getApplicationContext().getAssets().open(
+                    "geoid_height_map/tile-" + diskTokens[i] + ".pb")) {
+                tile = S2TileProto.parseFrom(is.readAllBytes());
+            }
+            mergeFromDiskTile(params, tile, cacheKeys, diskTokens, i, loadedTiles);
+        }
+
+        return s2CellId -> {
+            if (s2CellId == 0) {
+                return null;
+            }
+            long cacheKey = getCacheKey(params, s2CellId);
+            for (int i = 0; i < cacheKeys.length; i++) {
+                if (cacheKeys[i] == cacheKey) {
+                    return loadedTiles[i];
+                }
+            }
+            return null;
+        };
+    }
+
+    private void mergeFromDiskTile(@NonNull MapParamsProto params, @NonNull S2TileProto diskTile,
+            @NonNull long[] cacheKeys, @NonNull String[] diskTokens, int diskTokenIndex,
+            @NonNull S2TileProto[] loadedTiles) throws IOException {
+        int len = cacheKeys.length;
+        int numMapCellsPerCacheTile = 1 << (2 * (params.mapS2Level - params.cacheTileS2Level));
+
+        // Reusable arrays.
+        long[] s2CellIds = new long[numMapCellsPerCacheTile];
+        double[] values = new double[numMapCellsPerCacheTile];
+
+        // Each cache key identifies a different sub-tile of the disk tile.
+        TileFunction diskTileFunction = s2CellId -> diskTile;
+        for (int i = diskTokenIndex; i < len; i++) {
+            if (!Objects.equals(diskTokens[i], diskTokens[diskTokenIndex])
+                    || loadedTiles[i] != null) {
+                continue;
+            }
+
+            // Find all map cells within the current cache tile.
+            long s2CellId = S2CellIdUtils.getTraversalStart(cacheKeys[i], params.mapS2Level);
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                s2CellIds[j] = s2CellId;
+                s2CellId = S2CellIdUtils.getTraversalNext(s2CellId);
+            }
+
+            if (!getUnitIntervalValues(params, diskTileFunction, s2CellIds, values)) {
+                throw new IOException("Corrupted disk tile of disk token: " + diskTokens[i]);
+            }
+
+            loadedTiles[i] = new S2TileProto();
+            loadedTiles[i].byteBuffer = new byte[numMapCellsPerCacheTile];
+            for (int j = 0; j < numMapCellsPerCacheTile; j++) {
+                loadedTiles[i].byteBuffer[j] = (byte) Math.round(values[j] * 0xFF);
+            }
+
+            // Batch across common cache key.
+            for (int j = i + 1; j < len; j++) {
+                if (cacheKeys[j] == cacheKeys[i]) {
+                    loadedTiles[j] = loadedTiles[i];
+                }
+            }
+
+            // Side load into tile cache.
+            mCacheTiles.put(cacheKeys[i], loadedTiles[i]);
+        }
+    }
+
+    /** Defines a function-like object to retrieve tiles for map cells. */
+    private interface TileFunction {
+
+        @Nullable
+        S2TileProto getTile(long s2CellId);
+    }
+}
diff --git a/location/java/com/android/internal/location/altitude/S2CellIdUtils.java b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
new file mode 100644
index 0000000..5f11387
--- /dev/null
+++ b/location/java/com/android/internal/location/altitude/S2CellIdUtils.java
@@ -0,0 +1,653 @@
+/*
+ * 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.internal.location.altitude;
+
+import android.annotation.NonNull;
+
+import java.util.Arrays;
+import java.util.Locale;
+
+/**
+ * Provides lightweight S2 cell ID utilities without traditional geometry dependencies.
+ *
+ * <p>See <a href="https://s2geometry.io/">the S2 Geometry Library website</a> for more details.
+ */
+public final class S2CellIdUtils {
+
+    /** The level of all leaf S2 cells. */
+    public static final int MAX_LEVEL = 30;
+
+    private static final int MAX_SIZE = 1 << MAX_LEVEL;
+    private static final double ONE_OVER_MAX_SIZE = 1.0 / MAX_SIZE;
+    private static final int NUM_FACES = 6;
+    private static final int POS_BITS = 2 * MAX_LEVEL + 1;
+    private static final int SWAP_MASK = 0x1;
+    private static final int LOOKUP_BITS = 4;
+    private static final int LOOKUP_MASK = (1 << LOOKUP_BITS) - 1;
+    private static final int INVERT_MASK = 0x2;
+    private static final int LEAF_MASK = 0x1;
+    private static final int[] LOOKUP_POS = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] LOOKUP_IJ = new int[1 << (2 * LOOKUP_BITS + 2)];
+    private static final int[] POS_TO_ORIENTATION = {SWAP_MASK, 0, 0, INVERT_MASK + SWAP_MASK};
+    private static final int[][] POS_TO_IJ =
+            {{0, 1, 3, 2}, {0, 2, 3, 1}, {3, 2, 0, 1}, {3, 1, 0, 2}};
+    private static final double UV_LIMIT = calculateUvLimit();
+    private static final UvTransform[] UV_TRANSFORMS = createUvTransforms();
+    private static final XyzTransform[] XYZ_TRANSFORMS = createXyzTransforms();
+
+    // Used to encode (i, j, o) coordinates into primitive longs.
+    private static final int I_SHIFT = 33;
+    private static final int J_SHIFT = 2;
+    private static final long J_MASK = (1L << 31) - 1;
+
+    static {
+        initLookupCells();
+    }
+
+    /** Prevents instantiation. */
+    private S2CellIdUtils() {
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * degrees.
+     */
+    public static long fromLatLngDegrees(double latDegrees, double lngDegrees) {
+        return fromLatLngRadians(Math.toRadians(latDegrees), Math.toRadians(lngDegrees));
+    }
+
+    /**
+     * Returns the ID of the parent of the specified S2 cell at the specified parent level.
+     * Behavior is undefined for invalid S2 cell IDs or parent levels not in
+     * [0, {@code getLevel(s2CellId)}[.
+     */
+    public static long getParent(long s2CellId, int level) {
+        long newLsb = getLowestOnBitForLevel(level);
+        return (s2CellId & -newLsb) | newLsb;
+    }
+
+    /**
+     * Inserts into {@code neighbors} the four S2 cell IDs corresponding to the neighboring
+     * cells adjacent across the specified cell's four edges. This array must be of minimum
+     * length four, and elements at the tail end of the array not corresponding to a neighbor
+     * are set to zero. A reference to this array is returned.
+     *
+     * <p>Inserts in the order of down, right, up, and left directions, in that order. All
+     * neighbors are guaranteed to be distinct.
+     */
+    public static void getEdgeNeighbors(long s2CellId, @NonNull long[] neighbors) {
+        int level = getLevel(s2CellId);
+        int size = levelToSizeIj(level);
+        int face = getFace(s2CellId);
+        long ijo = toIjo(s2CellId);
+        int i = ijoToI(ijo);
+        int j = ijoToJ(ijo);
+
+        int iPlusSize = i + size;
+        int iMinusSize = i - size;
+        int jPlusSize = j + size;
+        int jMinusSize = j - size;
+        boolean iPlusSizeLtMax = iPlusSize < MAX_SIZE;
+        boolean iMinusSizeGteZero = iMinusSize >= 0;
+        boolean jPlusSizeLtMax = jPlusSize < MAX_SIZE;
+        boolean jMinusSizeGteZero = jMinusSize >= 0;
+
+        int index = 0;
+        // Down direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jMinusSize, jMinusSizeGteZero),
+                level);
+        // Right direction.
+        neighbors[index++] = getParent(fromFijSame(face, iPlusSize, j, iPlusSizeLtMax), level);
+        // Up direction.
+        neighbors[index++] = getParent(fromFijSame(face, i, jPlusSize, jPlusSizeLtMax), level);
+        // Left direction.
+        neighbors[index++] = getParent(fromFijSame(face, iMinusSize, j, iMinusSizeGteZero),
+                level);
+
+        // Pad end of neighbor array with zeros.
+        Arrays.fill(neighbors, index, neighbors.length, 0);
+    }
+
+    /** Returns the "i" coordinate for the specified S2 cell. */
+    public static int getI(long s2CellId) {
+        return ijoToI(toIjo(s2CellId));
+    }
+
+    /** Returns the "j" coordinate for the specified S2 cell. */
+    public static int getJ(long s2CellId) {
+        return ijoToJ(toIjo(s2CellId));
+    }
+
+    /**
+     * Returns the leaf S2 cell ID for the specified latitude and longitude, both measured in
+     * radians.
+     */
+    private static long fromLatLngRadians(double latRadians, double lngRadians) {
+        double cosLat = Math.cos(latRadians);
+        double x = Math.cos(lngRadians) * cosLat;
+        double y = Math.sin(lngRadians) * cosLat;
+        double z = Math.sin(latRadians);
+        return fromXyz(x, y, z);
+    }
+
+    /**
+     * Returns the level of the specified S2 cell. The returned level is in [0, 30] for valid
+     * S2 cell IDs. Behavior is undefined for invalid S2 cell IDs.
+     */
+    static int getLevel(long s2CellId) {
+        if (isLeaf(s2CellId)) {
+            return MAX_LEVEL;
+        }
+        return MAX_LEVEL - (Long.numberOfTrailingZeros(s2CellId) >> 1);
+    }
+
+    /** Returns the lowest-numbered bit that is on for the specified S2 cell. */
+    static long getLowestOnBit(long s2CellId) {
+        return s2CellId & -s2CellId;
+    }
+
+    /** Returns the lowest-numbered bit that is on for any S2 cell on the specified level. */
+    static long getLowestOnBitForLevel(int level) {
+        return 1L << (2 * (MAX_LEVEL - level));
+    }
+
+    /**
+     * Returns the ID of the first S2 cell in a traversal of the children S2 cells at the specified
+     * level, in Hilbert curve order.
+     */
+    static long getTraversalStart(long s2CellId, int level) {
+        return s2CellId - getLowestOnBit(s2CellId) + getLowestOnBitForLevel(level);
+    }
+
+    /** Returns the ID of the next S2 cell at the same level along the Hilbert curve. */
+    static long getTraversalNext(long s2CellId) {
+        return s2CellId + (getLowestOnBit(s2CellId) << 1);
+    }
+
+    /**
+     * Encodes the S2 cell id to compact text strings suitable for display or indexing. Cells at
+     * lower levels (i.e., larger cells) are encoded into fewer characters.
+     */
+    @NonNull
+    static String getToken(long s2CellId) {
+        if (s2CellId == 0) {
+            return "X";
+        }
+
+        // Convert to a hex string with as many digits as necessary.
+        String hex = Long.toHexString(s2CellId).toLowerCase(Locale.US);
+        // Prefix 0s to get a length 16 string.
+        String padded = padStart(hex);
+        // Trim zeroes off the end.
+        return padded.replaceAll("0*$", "");
+    }
+
+    private static String padStart(String string) {
+        if (string.length() >= 16) {
+            return string;
+        }
+        return "0".repeat(16 - string.length()) + string;
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (x, y, z) coordinate. */
+    private static long fromXyz(double x, double y, double z) {
+        int face = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[face];
+        double u = uvTransform.xyzToU(x, y, z);
+        double v = uvTransform.xyzToV(x, y, z);
+        return fromFuv(face, u, v);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, u, v) coordinate. */
+    private static long fromFuv(int face, double u, double v) {
+        int i = uToI(u);
+        int j = vToJ(v);
+        return fromFij(face, i, j);
+    }
+
+    /** Returns the leaf S2 cell ID of the specified (face, i, j) coordinate. */
+    private static long fromFij(int face, int i, int j) {
+        int bits = (face & SWAP_MASK);
+        // Update most significant bits.
+        long msb = ((long) face) << (POS_BITS - 33);
+        for (int k = 7; k >= 4; --k) {
+            bits = lookupBits(i, j, k, bits);
+            msb = updateBits(msb, k, bits);
+            bits = maskBits(bits);
+        }
+        // Update least significant bits.
+        long lsb = 0;
+        for (int k = 3; k >= 0; --k) {
+            bits = lookupBits(i, j, k, bits);
+            lsb = updateBits(lsb, k, bits);
+            bits = maskBits(bits);
+        }
+        return (((msb << 32) + lsb) << 1) + 1;
+    }
+
+    private static long fromFijWrap(int face, int i, int j) {
+        double u = iToU(i);
+        double v = jToV(j);
+
+        XyzTransform xyzTransform = XYZ_TRANSFORMS[face];
+        double x = xyzTransform.uvToX(u, v);
+        double y = xyzTransform.uvToY(u, v);
+        double z = xyzTransform.uvToZ(u, v);
+
+        int newFace = xyzToFace(x, y, z);
+        UvTransform uvTransform = UV_TRANSFORMS[newFace];
+        double newU = uvTransform.xyzToU(x, y, z);
+        double newV = uvTransform.xyzToV(x, y, z);
+
+        int newI = uShiftIntoI(newU);
+        int newJ = vShiftIntoJ(newV);
+        return fromFij(newFace, newI, newJ);
+    }
+
+    private static long fromFijSame(int face, int i, int j, boolean isSameFace) {
+        if (isSameFace) {
+            return fromFij(face, i, j);
+        }
+        return fromFijWrap(face, i, j);
+    }
+
+    /**
+     * Returns the face associated with the specified (x, y, z) coordinate. For a coordinate
+     * on a face boundary, the returned face is arbitrary but repeatable.
+     */
+    private static int xyzToFace(double x, double y, double z) {
+        double absX = Math.abs(x);
+        double absY = Math.abs(y);
+        double absZ = Math.abs(z);
+        if (absX > absY) {
+            if (absX > absZ) {
+                return (x < 0) ? 3 : 0;
+            }
+            return (z < 0) ? 5 : 2;
+        }
+        if (absY > absZ) {
+            return (y < 0) ? 4 : 1;
+        }
+        return (z < 0) ? 5 : 2;
+    }
+
+    private static int uToI(double u) {
+        double s;
+        if (u >= 0) {
+            s = 0.5 * Math.sqrt(1 + 3 * u);
+        } else {
+            s = 1 - 0.5 * Math.sqrt(1 - 3 * u);
+        }
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vToJ(double v) {
+        // Same calculation as uToI.
+        return uToI(v);
+    }
+
+    private static int lookupBits(int i, int j, int k, int bits) {
+        bits += ((i >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << (LOOKUP_BITS + 2);
+        bits += ((j >> (k * LOOKUP_BITS)) & LOOKUP_MASK) << 2;
+        return LOOKUP_POS[bits];
+    }
+
+    private static long updateBits(long sb, int k, int bits) {
+        return sb | ((((long) bits) >> 2) << ((k & 0x3) * 2 * LOOKUP_BITS));
+    }
+
+    private static int maskBits(int bits) {
+        return bits & (SWAP_MASK | INVERT_MASK);
+    }
+
+    private static int getFace(long s2CellId) {
+        return (int) (s2CellId >>> POS_BITS);
+    }
+
+    private static boolean isLeaf(long s2CellId) {
+        return ((int) s2CellId & LEAF_MASK) != 0;
+    }
+
+    private static double iToU(int i) {
+        int satI = Math.max(-1, Math.min(MAX_SIZE, i));
+        return Math.max(
+                -UV_LIMIT,
+                Math.min(UV_LIMIT, ONE_OVER_MAX_SIZE * ((satI << 1) + 1 - MAX_SIZE)));
+    }
+
+    private static double jToV(int j) {
+        // Same calculation as iToU.
+        return iToU(j);
+    }
+
+    private static long toIjo(long s2CellId) {
+        int face = getFace(s2CellId);
+        int bits = face & SWAP_MASK;
+        int i = 0;
+        int j = 0;
+        for (int k = 7; k >= 0; --k) {
+            int nbits = (k == 7) ? (MAX_LEVEL - 7 * LOOKUP_BITS) : LOOKUP_BITS;
+            bits += ((int) (s2CellId >>> (k * 2 * LOOKUP_BITS + 1)) & ((1 << (2 * nbits))
+                    - 1)) << 2;
+            bits = LOOKUP_IJ[bits];
+            i += (bits >> (LOOKUP_BITS + 2)) << (k * LOOKUP_BITS);
+            j += ((bits >> 2) & ((1 << LOOKUP_BITS) - 1)) << (k * LOOKUP_BITS);
+            bits &= (SWAP_MASK | INVERT_MASK);
+        }
+        int orientation =
+                ((getLowestOnBit(s2CellId) & 0x1111111111111110L) != 0) ? (bits ^ SWAP_MASK)
+                        : bits;
+        return (((long) i) << I_SHIFT) | (((long) j) << J_SHIFT) | orientation;
+    }
+
+    private static int ijoToI(long ijo) {
+        return (int) (ijo >>> I_SHIFT);
+    }
+
+    private static int ijoToJ(long ijo) {
+        return (int) ((ijo >>> J_SHIFT) & J_MASK);
+    }
+
+    private static int uShiftIntoI(double u) {
+        double s = 0.5 * (u + 1);
+        return Math.max(0, Math.min(MAX_SIZE - 1, (int) Math.round(MAX_SIZE * s - 0.5)));
+    }
+
+    private static int vShiftIntoJ(double v) {
+        // Same calculation as uShiftIntoI.
+        return uShiftIntoI(v);
+    }
+
+    private static int levelToSizeIj(int level) {
+        return 1 << (MAX_LEVEL - level);
+    }
+
+    private static void initLookupCells() {
+        initLookupCell(0, 0, 0, 0, 0, 0);
+        initLookupCell(0, 0, 0, SWAP_MASK, 0, SWAP_MASK);
+        initLookupCell(0, 0, 0, INVERT_MASK, 0, INVERT_MASK);
+        initLookupCell(0, 0, 0, SWAP_MASK | INVERT_MASK, 0, SWAP_MASK | INVERT_MASK);
+    }
+
+    private static void initLookupCell(
+            int level, int i, int j, int origOrientation, int pos, int orientation) {
+        if (level == LOOKUP_BITS) {
+            int ij = (i << LOOKUP_BITS) + j;
+            LOOKUP_POS[(ij << 2) + origOrientation] = (pos << 2) + orientation;
+            LOOKUP_IJ[(pos << 2) + origOrientation] = (ij << 2) + orientation;
+        } else {
+            level++;
+            i <<= 1;
+            j <<= 1;
+            pos <<= 2;
+            for (int subPos = 0; subPos < 4; subPos++) {
+                int ij = POS_TO_IJ[orientation][subPos];
+                int orientationMask = POS_TO_ORIENTATION[subPos];
+                initLookupCell(
+                        level,
+                        i + (ij >>> 1),
+                        j + (ij & 0x1),
+                        origOrientation,
+                        pos + subPos,
+                        orientation ^ orientationMask);
+            }
+        }
+    }
+
+    private static double calculateUvLimit() {
+        double machEps = 1.0;
+        do {
+            machEps /= 2.0f;
+        } while ((1.0 + (machEps / 2.0)) != 1.0);
+        return 1.0 + machEps;
+    }
+
+    @NonNull
+    private static UvTransform[] createUvTransforms() {
+        UvTransform[] uvTransforms = new UvTransform[NUM_FACES];
+        uvTransforms[0] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return y / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / x;
+                    }
+                };
+        uvTransforms[1] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return z / y;
+                    }
+                };
+        uvTransforms[2] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -x / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -y / z;
+                    }
+                };
+        uvTransforms[3] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / x;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return y / x;
+                    }
+                };
+        uvTransforms[4] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return z / y;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / y;
+                    }
+                };
+        uvTransforms[5] =
+                new UvTransform() {
+
+                    @Override
+                    public double xyzToU(double x, double y, double z) {
+                        return -y / z;
+                    }
+
+                    @Override
+                    public double xyzToV(double x, double y, double z) {
+                        return -x / z;
+                    }
+                };
+        return uvTransforms;
+    }
+
+    @NonNull
+    private static XyzTransform[] createXyzTransforms() {
+        XyzTransform[] xyzTransforms = new XyzTransform[NUM_FACES];
+        xyzTransforms[0] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[1] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return 1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return v;
+                    }
+                };
+        xyzTransforms[2] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -u;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return 1;
+                    }
+                };
+        xyzTransforms[3] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -v;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[4] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return -1;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -u;
+                    }
+                };
+        xyzTransforms[5] =
+                new XyzTransform() {
+
+                    @Override
+                    public double uvToX(double u, double v) {
+                        return v;
+                    }
+
+                    @Override
+                    public double uvToY(double u, double v) {
+                        return u;
+                    }
+
+                    @Override
+                    public double uvToZ(double u, double v) {
+                        return -1;
+                    }
+                };
+        return xyzTransforms;
+    }
+
+    /**
+     * Transform from (x, y, z) coordinates to (u, v) coordinates, indexed by face. For a
+     * (x, y, z) coordinate within a face, each element of the resulting (u, v) coordinate
+     * should lie in the inclusive range [-1, 1], with the face center having a (u, v)
+     * coordinate equal to (0, 0).
+     */
+    private interface UvTransform {
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding u-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToU(double x, double y, double z);
+
+        /**
+         * Returns for the specified (x, y, z) coordinate the corresponding v-coordinate
+         * (which may lie outside the range [-1, 1]).
+         */
+        double xyzToV(double x, double y, double z);
+    }
+
+    /**
+     * Transform from (u, v) coordinates to (x, y, z) coordinates, indexed by face. The
+     * resulting vectors are not necessarily of unit length.
+     */
+    private interface XyzTransform {
+
+        /** Returns for the specified (u, v) coordinate the corresponding x-coordinate. */
+        double uvToX(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding y-coordinate. */
+        double uvToY(double u, double v);
+
+        /** Returns for the specified (u, v) coordinate the corresponding z-coordinate. */
+        double uvToZ(double u, double v);
+    }
+}
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 3b30b1d..5a72b0b 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -89,6 +89,8 @@
     public static final int TYPE_USB_ACCESSORY    = 12;
     /**
      * A device type describing the audio device associated with a dock.
+     * Starting at API 34, this device type only represents digital docks, while docks with an
+     * analog connection are represented with {@link #TYPE_DOCK_ANALOG}.
      * @see #TYPE_DOCK_ANALOG
      */
     public static final int TYPE_DOCK             = 13;
diff --git a/media/java/android/media/AudioHalVersionInfo.aidl b/media/java/android/media/AudioHalVersionInfo.aidl
new file mode 100644
index 0000000..a83f8c8
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.aidl
@@ -0,0 +1,18 @@
+/* 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.media;
+
+parcelable AudioHalVersionInfo;
diff --git a/media/java/android/media/AudioHalVersionInfo.java b/media/java/android/media/AudioHalVersionInfo.java
new file mode 100644
index 0000000..985a758
--- /dev/null
+++ b/media/java/android/media/AudioHalVersionInfo.java
@@ -0,0 +1,174 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Defines the audio HAL version.
+ *
+ * @hide
+ */
+@TestApi
+public final class AudioHalVersionInfo implements Parcelable, Comparable<AudioHalVersionInfo> {
+    /**
+     * Indicate the audio HAL is implemented with HIDL (HAL interface definition language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/hidl/">HIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_HIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#HIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_HIDL = 0;
+
+    /**
+     * Indicate the audio HAL is implemented with AIDL (Android Interface Definition Language).
+     *
+     * @see <a href="https://source.android.com/docs/core/architecture/aidl/">AIDL</a>
+     *     <p>The value of AUDIO_HAL_TYPE_AIDL should match the value of {@link
+     *     android.media.AudioHalVersion.Type#AIDL}.
+     */
+    public static final int AUDIO_HAL_TYPE_AIDL = 1;
+
+    /** @hide */
+    @IntDef(
+            flag = false,
+            prefix = "AUDIO_HAL_TYPE_",
+            value = {AUDIO_HAL_TYPE_HIDL, AUDIO_HAL_TYPE_AIDL})
+    public @interface AudioHalType {}
+
+    /** AudioHalVersionInfo object of all valid Audio HAL versions. */
+    public static final @NonNull AudioHalVersionInfo AIDL_1_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_AIDL, 1 /* major */, 0 /* minor */);
+
+    public static final @NonNull AudioHalVersionInfo HIDL_7_1 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 1 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_7_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 7 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_6_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 6 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_5_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 5 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_4_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 4 /* major */, 0 /* minor */);
+    public static final @NonNull AudioHalVersionInfo HIDL_2_0 =
+            new AudioHalVersionInfo(AUDIO_HAL_TYPE_HIDL, 2 /* major */, 0 /* minor */);
+
+    /**
+     * List of all valid Audio HAL versions. This list need to be in sync with sAudioHALVersions
+     * defined in frameworks/av/media/libaudiohal/FactoryHalHidl.cpp.
+     */
+    // TODO: add AIDL_1_0 with sAudioHALVersions.
+    public static final @NonNull List<AudioHalVersionInfo> VERSIONS =
+            List.of(HIDL_7_1, HIDL_7_0, HIDL_6_0, HIDL_5_0, HIDL_4_0);
+
+    private static final String TAG = "AudioHalVersionInfo";
+    private AudioHalVersion mHalVersion = new AudioHalVersion();
+
+    public @AudioHalType int getHalType() {
+        return mHalVersion.type;
+    }
+
+    public int getMajorVersion() {
+        return mHalVersion.major;
+    }
+
+    public int getMinorVersion() {
+        return mHalVersion.minor;
+    }
+
+    /** String representative of AudioHalVersion.Type */
+    private static @NonNull String typeToString(@AudioHalType int type) {
+        if (type == AudioHalVersion.Type.HIDL) {
+            return "HIDL";
+        } else if (type == AudioHalVersion.Type.AIDL) {
+            return "AIDL";
+        } else {
+            return "INVALID";
+        }
+    }
+
+    /** String representative of type, major and minor */
+    private static @NonNull String toString(@AudioHalType int type, int major, int minor) {
+        return typeToString(type) + ":" + Integer.toString(major) + "." + Integer.toString(minor);
+    }
+
+    private AudioHalVersionInfo(@AudioHalType int type, int major, int minor) {
+        mHalVersion.type = type;
+        mHalVersion.major = major;
+        mHalVersion.minor = minor;
+    }
+
+    private AudioHalVersionInfo(Parcel in) {
+        mHalVersion = in.readTypedObject(AudioHalVersion.CREATOR);
+    }
+
+    /** String representative of this (AudioHalVersionInfo) object */
+    @Override
+    public String toString() {
+        return toString(mHalVersion.type, mHalVersion.major, mHalVersion.minor);
+    }
+
+    /**
+     * Compare two HAL versions by comparing their index in VERSIONS.
+     *
+     * <p>Normally all AudioHalVersionInfo object to compare should exist in the VERSIONS list. If
+     * both candidates exist in the VERSIONS list, smaller index means newer. Any candidate not
+     * exist in the VERSIONS list will be considered to be oldest version.
+     *
+     * @return 0 if the HAL version is the same as the other HAL version. Positive if the HAL
+     *     version is newer than the other HAL version. Negative if the HAL version is older than
+     *     the other version.
+     */
+    @Override
+    public int compareTo(@NonNull AudioHalVersionInfo other) {
+        int indexOther = VERSIONS.indexOf(other);
+        int indexThis = VERSIONS.indexOf(this);
+        if (indexThis < 0 || indexOther < 0) {
+            return indexThis - indexOther;
+        }
+        return indexOther - indexThis;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel out, int flag) {
+        out.writeTypedObject(mHalVersion, flag);
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioHalVersionInfo> CREATOR =
+            new Parcelable.Creator<AudioHalVersionInfo>() {
+                @Override
+                public AudioHalVersionInfo createFromParcel(@NonNull Parcel in) {
+                    return new AudioHalVersionInfo(in);
+                }
+
+                @Override
+                public AudioHalVersionInfo[] newArray(int size) {
+                    return new AudioHalVersionInfo[size];
+                }
+            };
+}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 9c5313a..3ed2c4b 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -8502,13 +8502,14 @@
     }
 
     /**
-     * Returns the audio HAL version in the form MAJOR.MINOR. If there is no audio HAL found, null
-     * will be returned.
+     * Returns an {@link AudioHalVersionInfo} indicating the Audio Hal Version. If there is no audio
+     * HAL found, null will be returned.
      *
+     * @return @see @link #AudioHalVersionInfo The version of Audio HAL.
      * @hide
      */
     @TestApi
-    public static @Nullable String getHalVersion() {
+    public static @Nullable AudioHalVersionInfo getHalVersion() {
         try {
             return getService().getHalVersion();
         } catch (RemoteException e) {
@@ -8517,6 +8518,221 @@
         }
     }
 
+    //====================================================================
+    // Preferred mixer attributes
+
+    /**
+     * Returns the {@link AudioMixerAttributes} that can be used to set as preferred mixe
+     * attributes via {@link #setPreferredMixerAttributes(
+     * AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}.
+     * <p>Note that only USB devices are guaranteed to expose configurable mixer attributes, the
+     * returned list may be empty when devices do not allow dynamic configuration.
+     *
+     * @param device the device to query
+     * @return a list of {@link AudioMixerAttributes} that can be used as preferred mixer attributes
+     *         for the given device.
+     * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
+     */
+    @NonNull
+    public List<AudioMixerAttributes> getSupportedMixerAttributes(@NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(device);
+        List<AudioMixerAttributes> mixerAttrs = new ArrayList<>();
+        return (AudioSystem.getSupportedMixerAttributes(device.getId(), mixerAttrs)
+                == AudioSystem.SUCCESS) ? mixerAttrs : new ArrayList<>();
+    }
+
+    /**
+     * Configures the mixer attributes for a particular {@link AudioAttributes} over a given
+     * {@link AudioDeviceInfo}.
+     * <p>When constructing an {@link AudioMixerAttributes} for setting preferred mixer attributes,
+     * the mixer format must be constructed from an {@link AudioProfile} that can be used to set
+     * preferred mixer attributes.
+     * <p>The ownership of preferred mixer attributes is recognized by uid. When a playback from the
+     * same uid is routed to the given audio device when calling this API, the output mixer/stream
+     * will be configured with the values previously set via this API.
+     * <p>Use {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)}
+     * to cancel setting mixer attributes for this {@link AudioAttributes}.
+     *
+     * @param attributes the {@link AudioAttributes} whose mixer attributes should be set.
+     *                   Currently, only {@link AudioAttributes#USAGE_MEDIA} is supported. When
+     *                   playing audio targeted at the given device, use the same attributes for
+     *                   playback.
+     * @param device the device to be routed. Currently, only USB device will be allowed.
+     * @param mixerAttributes the preferred mixer attributes. When playing audio targeted at the
+     *                        given device, use the same {@link AudioFormat} for both playback
+     *                        and the mixer attributes.
+     * @return true only if the preferred mixer attributes are set successfully.
+     * @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
+    public boolean setPreferredMixerAttributes(@NonNull AudioAttributes attributes,
+            @NonNull AudioDeviceInfo device,
+            @NonNull AudioMixerAttributes mixerAttributes) {
+        Objects.requireNonNull(attributes);
+        Objects.requireNonNull(device);
+        Objects.requireNonNull(mixerAttributes);
+        try {
+            final int status = getService().setPreferredMixerAttributes(
+                    attributes, device.getId(), mixerAttributes);
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns current preferred mixer attributes that is set via
+     * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
+     *
+     * @param attributes the {@link AudioAttributes} whose mixer attributes should be set.
+     * @param device the expected routing device
+     * @return the preferred mixer attributes, which will be null when no preferred mixer attributes
+     *         have been set, or when they have been cleared.
+     * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
+     * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     */
+    @Nullable
+    public AudioMixerAttributes getPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes,
+            @NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(attributes);
+        Objects.requireNonNull(device);
+        List<AudioMixerAttributes> mixerAttrList = new ArrayList<>();
+        int ret = AudioSystem.getPreferredMixerAttributes(
+                attributes, device.getId(), mixerAttrList);
+        if (ret == AudioSystem.SUCCESS) {
+            return mixerAttrList.isEmpty() ? null : mixerAttrList.get(0);
+        } else {
+            Log.e(TAG, "Failed calling getPreferredMixerAttributes, ret=" + ret);
+            return null;
+        }
+    }
+
+    /**
+     * Clears the current preferred mixer attributes that were previously set via
+     * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
+     *
+     * @param attributes the {@link AudioAttributes} whose mixer attributes should be cleared.
+     * @param device the expected routing device
+     * @return true only if the preferred mixer attributes are removed successfully.
+     * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
+     * @see #getPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     */
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)
+    public boolean clearPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes,
+            @NonNull AudioDeviceInfo device) {
+        Objects.requireNonNull(attributes);
+        Objects.requireNonNull(device);
+        try {
+            final int status = getService().clearPreferredMixerAttributes(
+                    attributes, device.getId());
+            return status == AudioSystem.SUCCESS;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Interface to be notified of changes in the preferred mixer attributes.
+     * <p>Note that this listener will only be invoked whenever
+     * {@link #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)}
+     * or {@link #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)} or device
+     * disconnection causes a change in preferred mixer attributes.
+     * @see #setPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
+     * @see #clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     */
+    public interface OnPreferredMixerAttributesChangedListener {
+        /**
+         * Called on the listener to indicate that the preferred mixer attributes for the audio
+         * attributes over the given device has changed.
+         *
+         * @param attributes the audio attributes for playback
+         * @param device the targeted device
+         * @param mixerAttributes the {@link AudioMixerAttributes} that contains information for
+         *                        preferred mixer attributes or null if preferred mixer attributes
+         *                        is cleared
+         */
+        void onPreferredMixerAttributesChanged(
+                @NonNull AudioAttributes attributes,
+                @NonNull AudioDeviceInfo device,
+                @Nullable AudioMixerAttributes mixerAttributes);
+    }
+
+    /**
+     * Manage the {@link OnPreferredMixerAttributesChangedListener} listeners and the
+     * {@link PreferredMixerAttributesDispatcherStub}.
+     */
+    private final CallbackUtil.LazyListenerManager<OnPreferredMixerAttributesChangedListener>
+            mPrefMixerAttributesListenerMgr = new CallbackUtil.LazyListenerManager();
+
+    /**
+     * Adds a listener for being notified of changes to the preferred mixer attributes.
+     * @param executor the executor to execute the callback
+     * @param listener the listener to be notified of changes in the preferred mixer attributes.
+     */
+    public void addOnPreferredMixerAttributesChangedListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnPreferredMixerAttributesChangedListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        mPrefMixerAttributesListenerMgr.addListener(executor, listener,
+                "addOnPreferredMixerAttributesChangedListener",
+                () -> new PreferredMixerAttributesDispatcherStub());
+    }
+
+    /**
+     * Removes a previously added listener of changes to the preferred mixer attributes.
+     * @param listener the listener to be notified of changes in the preferred mixer attributes,
+     *                 which were added via {@link #addOnPreferredMixerAttributesChangedListener(
+     *                 Executor, OnPreferredMixerAttributesChangedListener)}.
+     */
+    public void removeOnPreferredMixerAttributesChangedListener(
+            @NonNull OnPreferredMixerAttributesChangedListener listener) {
+        Objects.requireNonNull(listener);
+        mPrefMixerAttributesListenerMgr.removeListener(listener,
+                "removeOnPreferredMixerAttributesChangedListener");
+    }
+
+    private final class PreferredMixerAttributesDispatcherStub
+            extends IPreferredMixerAttributesDispatcher.Stub
+            implements CallbackUtil.DispatcherStub {
+
+        @Override
+        public void register(boolean register) {
+            try {
+                if (register) {
+                    getService().registerPreferredMixerAttributesDispatcher(this);
+                } else {
+                    getService().unregisterPreferredMixerAttributesDispatcher(this);
+                }
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void dispatchPrefMixerAttributesChanged(@NonNull AudioAttributes attr,
+                                                       int deviceId,
+                                                       @Nullable AudioMixerAttributes mixerAttr) {
+            // TODO: If the device is disconnected, we may not be able to find the device with
+            // given device id. We need a better to carry the device information via binder.
+            AudioDeviceInfo device = getDeviceForPortId(deviceId, GET_DEVICES_OUTPUTS);
+            if (device == null) {
+                Log.d(TAG, "Drop preferred mixer attributes changed as the device("
+                        + deviceId + ") is disconnected");
+                return;
+            }
+            mPrefMixerAttributesListenerMgr.callListeners(
+                    (listener) -> listener.onPreferredMixerAttributesChanged(
+                            attr, device, mixerAttr));
+        }
+    }
+
+    //====================================================================
+    // Mute await connection
+
     private final Object mMuteAwaitConnectionListenerLock = new Object();
 
     @GuardedBy("mMuteAwaitConnectionListenerLock")
diff --git a/media/java/android/media/AudioMixerAttributes.aidl b/media/java/android/media/AudioMixerAttributes.aidl
new file mode 100644
index 0000000..0d9badd
--- /dev/null
+++ b/media/java/android/media/AudioMixerAttributes.aidl
@@ -0,0 +1,18 @@
+/* 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.media;
+
+parcelable AudioMixerAttributes;
diff --git a/media/java/android/media/AudioMixerAttributes.java b/media/java/android/media/AudioMixerAttributes.java
new file mode 100644
index 0000000..320d6bd
--- /dev/null
+++ b/media/java/android/media/AudioMixerAttributes.java
@@ -0,0 +1,183 @@
+/*
+ * 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.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Class to represent the attributes of the audio mixer: its format, which represents by an
+ * {@link AudioFormat} object and mixer behavior.
+ */
+public final class AudioMixerAttributes implements Parcelable {
+
+    /**
+     * Constant indicating the audio mixer behavior will follow the default platform behavior, which
+     * is mixing all audio sources in the mixer.
+     */
+    public static final int MIXER_BEHAVIOR_DEFAULT = 0;
+
+    /** @hide */
+    @IntDef(flag = false, prefix = "MIXER_BEHAVIOR_", value = {
+            MIXER_BEHAVIOR_DEFAULT }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MixerBehavior {}
+
+    private final AudioFormat mFormat;
+    private final @MixerBehavior int mMixerBehavior;
+
+    /**
+     * Constructor from {@link AudioFormat} and mixer behavior
+     */
+    AudioMixerAttributes(AudioFormat format, @MixerBehavior int mixerBehavior) {
+        mFormat = format;
+        mMixerBehavior = mixerBehavior;
+    }
+
+    /**
+     * Return the format of the audio mixer. The format is an {@link AudioFormat} object, which
+     * includes encoding format, sample rate and channel mask or channel index mask.
+     * @return the format of the audio mixer.
+     */
+    @NonNull
+    public AudioFormat getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Returns the mixer behavior for this set of mixer attributes.
+     *
+     * @return the mixer behavior
+     */
+    public @MixerBehavior int getMixerBehavior() {
+        return mMixerBehavior;
+    }
+
+    /**
+     * Builder class for {@link AudioMixerAttributes} objects.
+     */
+    public static final class Builder {
+        private final AudioFormat mFormat;
+        private int mMixerBehavior = MIXER_BEHAVIOR_DEFAULT;
+
+        /**
+         * Constructs a new Builder with the defaults.
+         *
+         * @param format the {@link AudioFormat} for the audio mixer.
+         */
+        public Builder(@NonNull AudioFormat format) {
+            Objects.requireNonNull(format);
+            mFormat = format;
+        }
+
+        /**
+         * Combines all attributes that have been set and returns a new {@link AudioMixerAttributes}
+         * object.
+         * @return a new {@link AudioMixerAttributes} object
+         */
+        public @NonNull AudioMixerAttributes build() {
+            AudioMixerAttributes ama = new AudioMixerAttributes(mFormat, mMixerBehavior);
+            return ama;
+        }
+
+        /**
+         * Sets the mixer behavior for the audio mixer
+         * @param mixerBehavior must be {@link #MIXER_BEHAVIOR_DEFAULT}.
+         * @return the same Builder instance.
+         */
+        public @NonNull Builder setMixerBehavior(@MixerBehavior int mixerBehavior) {
+            switch (mixerBehavior) {
+                case MIXER_BEHAVIOR_DEFAULT:
+                    mMixerBehavior = mixerBehavior;
+                    break;
+                default:
+                    throw new IllegalArgumentException("Invalid mixer behavior " + mixerBehavior);
+            }
+            return this;
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mFormat, mMixerBehavior);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        AudioMixerAttributes that = (AudioMixerAttributes) o;
+        return (mFormat.equals(that.mFormat)
+                && (mMixerBehavior == that.mMixerBehavior));
+    }
+
+    private String mixerBehaviorToString(@MixerBehavior int mixerBehavior) {
+        switch (mixerBehavior) {
+            case MIXER_BEHAVIOR_DEFAULT:
+                return "default";
+            default:
+                return "unknown";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return new String("AudioMixerAttributes:"
+                + " format:" + mFormat.toString()
+                + " mixer behavior:" + mixerBehaviorToString(mMixerBehavior));
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mFormat, flags);
+        dest.writeInt(mMixerBehavior);
+    }
+
+    private AudioMixerAttributes(@NonNull Parcel in) {
+        mFormat = in.readParcelable(AudioFormat.class.getClassLoader(), AudioFormat.class);
+        mMixerBehavior = in.readInt();
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioMixerAttributes> CREATOR =
+            new Parcelable.Creator<AudioMixerAttributes>() {
+                /**
+                 * Rebuilds an AudioMixerAttributes previously stored with writeToParcel().
+                 * @param p Parcel object to read the AudioMixerAttributes from
+                 * @return a new AudioMixerAttributes created from the data in the parcel
+                 */
+                public AudioMixerAttributes createFromParcel(Parcel p) {
+                    return new AudioMixerAttributes(p);
+                }
+
+                public AudioMixerAttributes[] newArray(int size) {
+                    return new AudioMixerAttributes[size];
+                }
+            };
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index a48edac..607225e 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2260,6 +2260,14 @@
 
     /**
      * @hide
+     * Register the sound dose callback with the audio server.
+     *
+     * @return {@link #SUCCESS} if the callback was registered successfully.
+     */
+    public static native int registerSoundDoseCallback(ISoundDoseCallback callback);
+
+    /**
+     * @hide
      * @param attributes audio attributes describing the playback use case
      * @param audioProfilesList the list of AudioProfiles that can be played as direct output
      * @return {@link #SUCCESS} if the list of AudioProfiles was successfully created (can be empty)
@@ -2426,4 +2434,40 @@
      * Keep in sync with core/jni/android_media_DeviceCallback.h.
      */
     final static int NATIVE_EVENT_ROUTING_CHANGE = 1000;
+
+    /**
+     * @hide
+     * Query the mixer attributes that can be set as preferred mixer attributes for the given
+     * device.
+     */
+    public static native int getSupportedMixerAttributes(
+            int deviceId, @NonNull List<AudioMixerAttributes> mixerAttrs);
+
+    /**
+     * @hide
+     * Set preferred mixer attributes for a given device when playing particular
+     * audio attributes.
+     */
+    public static native int setPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes,
+            int portId,
+            int uid,
+            @NonNull AudioMixerAttributes mixerAttributes);
+
+    /**
+     * @hide
+     * Get preferred mixer attributes that is previously set via
+     * {link #setPreferredMixerAttributes}.
+     */
+    public static native int getPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes, int portId,
+            List<AudioMixerAttributes> mixerAttributesList);
+
+    /**
+     * @hide
+     * Clear preferred mixer attributes that is previously set via
+     * {@link #setPreferredMixerAttributes}
+     */
+    public static native int clearPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes, int portId, int uid);
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
old mode 100755
new mode 100644
index 2e766d5..5502db2
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -22,6 +22,8 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioFormat;
 import android.media.AudioFocusInfo;
+import android.media.AudioHalVersionInfo;
+import android.media.AudioMixerAttributes;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
@@ -36,6 +38,7 @@
 import android.media.IDeviceVolumeBehaviorDispatcher;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
+import android.media.IPreferredMixerAttributesDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
 import android.media.IStrategyPreferredDevicesDispatcher;
@@ -571,5 +574,15 @@
             in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
             boolean handlesvolumeAdjustment);
 
-    String getHalVersion();
+    AudioHalVersionInfo getHalVersion();
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)")
+    int setPreferredMixerAttributes(
+            in AudioAttributes aa, int portId, in AudioMixerAttributes mixerAttributes);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS)")
+    int clearPreferredMixerAttributes(in AudioAttributes aa, int portId);
+    void registerPreferredMixerAttributesDispatcher(
+            IPreferredMixerAttributesDispatcher dispatcher);
+    oneway void unregisterPreferredMixerAttributesDispatcher(
+            IPreferredMixerAttributesDispatcher dispatcher);
 }
diff --git a/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl b/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl
new file mode 100644
index 0000000..9138fa7
--- /dev/null
+++ b/media/java/android/media/IPreferredMixerAttributesDispatcher.aidl
@@ -0,0 +1,34 @@
+/*
+ * 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.media;
+
+import android.media.AudioAttributes;
+import android.media.AudioMixerAttributes;
+
+/**
+ * AIDL for AudioService to signal preferred mixer attributes update.
+ *
+ * {@hide}
+ */
+oneway interface IPreferredMixerAttributesDispatcher {
+
+    void dispatchPrefMixerAttributesChanged(
+            in AudioAttributes attributes,
+            int deviceId,
+            in @nullable AudioMixerAttributes mixerAttributes);
+
+}
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index a4a8cc4..5509782 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -49,10 +49,11 @@
         public static final String AUDIO_FOCUS = AUDIO + SEPARATOR + "focus";
         public static final String AUDIO_FORCE_USE = AUDIO + SEPARATOR + "forceUse";
         public static final String AUDIO_MIC = AUDIO + SEPARATOR + "mic";
+        public static final String AUDIO_MIDI = AUDIO + SEPARATOR + "midi";
+        public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
         public static final String AUDIO_SERVICE = AUDIO + SEPARATOR + "service";
         public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
         public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
-        public static final String AUDIO_MODE = AUDIO + SEPARATOR + "mode";
         public static final String METRICS_MANAGER = "metrics" + SEPARATOR + "manager";
     }
 
@@ -90,15 +91,27 @@
         // The client name
         public static final Key<String> CLIENT_NAME = createKey("clientName", String.class);
 
+        public static final Key<Integer> CLOSED_COUNT =
+                createKey("closedCount", Integer.class); // MIDI
+
         // The device type
         public static final Key<Integer> DELAY_MS = createKey("delayMs", Integer.class);
 
         // The device type
         public static final Key<String> DEVICE = createKey("device", String.class);
 
+        // Whether the device is disconnected. This is either "true" or "false"
+        public static final Key<String> DEVICE_DISCONNECTED =
+                createKey("deviceDisconnected", String.class); // MIDI
+
+        // The ID of the device
+        public static final Key<Integer> DEVICE_ID =
+                createKey("deviceId", Integer.class); // MIDI
+
         // For volume changes, up or down
         public static final Key<String> DIRECTION = createKey("direction", String.class);
-
+        public static final Key<Long> DURATION_NS =
+                createKey("durationNs", Long.class); // MIDI
         // A reason for early return or error
         public static final Key<String> EARLY_RETURN =
                 createKey("earlyReturn", String.class);
@@ -128,11 +141,17 @@
         // Generally string "true" or "false"
         public static final Key<String> HAS_HEAD_TRACKER =
                 createKey("hasHeadTracker", String.class);     // spatializer
+        public static final Key<Integer> HARDWARE_TYPE =
+                createKey("hardwareType", Integer.class); // MIDI
         // Generally string "true" or "false"
         public static final Key<String> HEAD_TRACKER_ENABLED =
                 createKey("headTrackerEnabled", String.class); // spatializer
 
         public static final Key<Integer> INDEX = createKey("index", Integer.class); // volume
+        public static final Key<Integer> INPUT_PORT_COUNT =
+                createKey("inputPortCount", Integer.class); // MIDI
+        // Either "true" or "false"
+        public static final Key<String> IS_SHARED = createKey("isShared", String.class); // MIDI
         public static final Key<String> LOG_SESSION_ID = createKey("logSessionId", String.class);
         public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class); // vol
         public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class); // vol
@@ -149,6 +168,11 @@
         public static final Key<Integer> OBSERVERS =
                 createKey("observers", Integer.class);
 
+        public static final Key<Integer> OPENED_COUNT =
+                createKey("openedCount", Integer.class); // MIDI
+        public static final Key<Integer> OUTPUT_PORT_COUNT =
+                createKey("outputPortCount", Integer.class); // MIDI
+
         public static final Key<String> REQUEST =
                 createKey("request", String.class);
 
@@ -163,6 +187,18 @@
         public static final Key<String> STATE = createKey("state", String.class);
         public static final Key<Integer> STATUS = createKey("status", Integer.class);
         public static final Key<String> STREAM_TYPE = createKey("streamType", String.class);
+
+        // The following MIDI string is generally either "true" or "false"
+        public static final Key<String> SUPPORTS_MIDI_UMP =
+                createKey("supportsMidiUmp", String.class); // Universal MIDI Packets
+
+        public static final Key<Integer> TOTAL_INPUT_BYTES =
+                createKey("totalInputBytes", Integer.class); // MIDI
+        public static final Key<Integer> TOTAL_OUTPUT_BYTES =
+                createKey("totalOutputBytes", Integer.class); // MIDI
+
+        // The following MIDI string is generally either "true" or "false"
+        public static final Key<String> USING_ALSA = createKey("usingAlsa", String.class);
     }
 
     /**
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 7786f61..aea6bcb 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -76,13 +76,7 @@
     private static MediaRouter2Manager sInstance;
 
     private final MediaSessionManager mMediaSessionManager;
-
-    final String mPackageName;
-
-    private final Context mContext;
-
     private final Client mClient;
-
     private final IMediaRouterService mMediaRouterService;
     private final AtomicInteger mScanRequestCount = new AtomicInteger(/* initialValue= */ 0);
     final Handler mHandler;
@@ -120,16 +114,14 @@
     }
 
     private MediaRouter2Manager(Context context) {
-        mContext = context.getApplicationContext();
         mMediaRouterService = IMediaRouterService.Stub.asInterface(
                 ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
         mMediaSessionManager = (MediaSessionManager) context
                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
-        mPackageName = mContext.getPackageName();
         mHandler = new Handler(context.getMainLooper());
         mClient = new Client();
         try {
-            mMediaRouterService.registerManager(mClient, mPackageName);
+            mMediaRouterService.registerManager(mClient, context.getPackageName());
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index f15f443..1e270b1 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -826,6 +826,19 @@
         if(!isInternalRingtoneUri(ringtoneUri)) {
             ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
         }
+
+        final String mimeType = resolver.getType(ringtoneUri);
+        if (mimeType == null) {
+            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                    + " ignored: failure to find mimeType (no access from this context?)");
+            return;
+        }
+        if (!(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
+            Log.e(TAG, "setActualDefaultRingtoneUri for URI:" + ringtoneUri
+                    + " ignored: associated mimeType:" + mimeType + " is not an audio type");
+            return;
+        }
+
         Settings.System.putStringForUser(resolver, setting,
                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
 
diff --git a/media/java/android/media/audiopolicy/AudioVolumeGroup.java b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
index d58111d..1a9ebbc 100644
--- a/media/java/android/media/audiopolicy/AudioVolumeGroup.java
+++ b/media/java/android/media/audiopolicy/AudioVolumeGroup.java
@@ -51,7 +51,7 @@
     /**
      * human-readable name of this volume group.
      */
-    private final String mName;
+    private final @NonNull String mName;
 
     private final AudioAttributes[] mAudioAttributes;
     private int[] mLegacyStreamTypes;
@@ -113,7 +113,7 @@
 
         AudioVolumeGroup thatAvg = (AudioVolumeGroup) o;
 
-        return mName == thatAvg.mName && mId == thatAvg.mId
+        return mName.equals(thatAvg.mName) && mId == thatAvg.mId
                 && Arrays.equals(mAudioAttributes, thatAvg.mAudioAttributes);
     }
 
diff --git a/media/java/android/media/midi/IMidiManager.aidl b/media/java/android/media/midi/IMidiManager.aidl
index b03f785..bd678a5 100644
--- a/media/java/android/media/midi/IMidiManager.aidl
+++ b/media/java/android/media/midi/IMidiManager.aidl
@@ -60,4 +60,7 @@
     // used by MIDI devices to report their status
     // the token is used by MidiService for death notification
     void setDeviceStatus(in IMidiDeviceServer server, in MidiDeviceStatus status);
+
+    // Updates the number of bytes sent and received
+    void updateTotalBytes(in IMidiDeviceServer server, int inputBytes, int outputBytes);
 }
diff --git a/media/java/android/media/midi/MidiDeviceServer.java b/media/java/android/media/midi/MidiDeviceServer.java
index d5916b9..fc33cef 100644
--- a/media/java/android/media/midi/MidiDeviceServer.java
+++ b/media/java/android/media/midi/MidiDeviceServer.java
@@ -36,6 +36,7 @@
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Internal class used for providing an implementation for a MIDI device.
@@ -79,6 +80,9 @@
     private final HashMap<MidiInputPort, PortClient> mInputPortClients =
             new HashMap<MidiInputPort, PortClient>();
 
+    private AtomicInteger mTotalInputBytes = new AtomicInteger();
+    private AtomicInteger mTotalOutputBytes = new AtomicInteger();
+
     public interface Callback {
         /**
          * Called to notify when an our device status has changed
@@ -133,6 +137,8 @@
                 int portNumber = mOutputPort.getPortNumber();
                 mInputPortOutputPorts[portNumber] = null;
                 mInputPortOpen[portNumber] = false;
+                mTotalOutputBytes.addAndGet(mOutputPort.pullTotalBytesCount());
+                updateTotalBytes();
                 updateDeviceStatus();
             }
             IoUtils.closeQuietly(mOutputPort);
@@ -156,6 +162,8 @@
                 dispatcher.getSender().disconnect(mInputPort);
                 int openCount = dispatcher.getReceiverCount();
                 mOutputPortOpenCount[portNumber] = openCount;
+                mTotalInputBytes.addAndGet(mInputPort.pullTotalBytesCount());
+                updateTotalBytes();
                 updateDeviceStatus();
            }
 
@@ -405,18 +413,20 @@
         synchronized (mGuard) {
             if (mIsClosed) return;
             mGuard.close();
-
             for (int i = 0; i < mInputPortCount; i++) {
                 MidiOutputPort outputPort = mInputPortOutputPorts[i];
                 if (outputPort != null) {
+                    mTotalOutputBytes.addAndGet(outputPort.pullTotalBytesCount());
                     IoUtils.closeQuietly(outputPort);
                     mInputPortOutputPorts[i] = null;
                 }
             }
             for (MidiInputPort inputPort : mInputPorts) {
+                mTotalInputBytes.addAndGet(inputPort.pullTotalBytesCount());
                 IoUtils.closeQuietly(inputPort);
             }
             mInputPorts.clear();
+            updateTotalBytes();
             try {
                 mMidiManager.unregisterDeviceServer(mServer);
             } catch (RemoteException e) {
@@ -449,4 +459,12 @@
         System.arraycopy(mOutputPortDispatchers, 0, receivers, 0, mOutputPortCount);
         return receivers;
     }
+
+    private void updateTotalBytes() {
+        try {
+            mMidiManager.updateTotalBytes(mServer, mTotalInputBytes.get(), mTotalOutputBytes.get());
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException in updateTotalBytes");
+        }
+    }
 }
diff --git a/media/java/android/media/midi/MidiInputPort.java b/media/java/android/media/midi/MidiInputPort.java
index a300886..fe42b58 100644
--- a/media/java/android/media/midi/MidiInputPort.java
+++ b/media/java/android/media/midi/MidiInputPort.java
@@ -28,6 +28,7 @@
 import java.io.FileDescriptor;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class is used for sending data to a port on a MIDI device
@@ -43,6 +44,7 @@
 
     private final CloseGuard mGuard = CloseGuard.get();
     private boolean mIsClosed;
+    private AtomicInteger mTotalBytes = new AtomicInteger();
 
     // buffer to use for sending data out our output stream
     private final byte[] mBuffer = new byte[MidiPortImpl.MAX_PACKET_SIZE];
@@ -87,6 +89,7 @@
             }
             int length = MidiPortImpl.packData(msg, offset, count, timestamp, mBuffer);
             mOutputStream.write(mBuffer, 0, length);
+            mTotalBytes.addAndGet(length);
         }
     }
 
@@ -170,4 +173,12 @@
             super.finalize();
         }
     }
+
+    /**
+     * Pulls total number of bytes and sets to zero. This allows multiple callers.
+     * @hide
+     */
+    public int pullTotalBytesCount() {
+        return mTotalBytes.getAndSet(0);
+    }
 }
diff --git a/media/java/android/media/midi/MidiOutputPort.java b/media/java/android/media/midi/MidiOutputPort.java
index 5411e66..d948477 100644
--- a/media/java/android/media/midi/MidiOutputPort.java
+++ b/media/java/android/media/midi/MidiOutputPort.java
@@ -31,6 +31,7 @@
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
 import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * This class is used for receiving data from a port on a MIDI device
@@ -46,6 +47,7 @@
 
     private final CloseGuard mGuard = CloseGuard.get();
     private boolean mIsClosed;
+    private AtomicInteger mTotalBytes = new AtomicInteger();
 
     // This thread reads MIDI events from a socket and distributes them to the list of
     // MidiReceivers attached to this device.
@@ -83,6 +85,7 @@
                             Log.e(TAG, "Unknown packet type " + packetType);
                             break;
                     }
+                    mTotalBytes.addAndGet(count);
                 } // while (true)
             } catch (IOException e) {
                 // FIXME report I/O failure?
@@ -163,4 +166,12 @@
             super.finalize();
         }
     }
+
+    /**
+     * Pulls total number of bytes and sets to zero. This allows multiple callers.
+     * @hide
+     */
+    public int pullTotalBytesCount() {
+        return mTotalBytes.getAndSet(0);
+    }
 }
diff --git a/media/java/android/media/projection/OWNERS b/media/java/android/media/projection/OWNERS
index 9ca3910..96532d0 100644
--- a/media/java/android/media/projection/OWNERS
+++ b/media/java/android/media/projection/OWNERS
@@ -1,2 +1,3 @@
 michaelwr@google.com
 santoscordon@google.com
+chaviw@google.com
diff --git a/media/tests/AudioPolicyTest/AndroidManifest.xml b/media/tests/AudioPolicyTest/AndroidManifest.xml
index f696735..5c911b1 100644
--- a/media/tests/AudioPolicyTest/AndroidManifest.xml
+++ b/media/tests/AudioPolicyTest/AndroidManifest.xml
@@ -24,13 +24,22 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:label="@string/app_name" android:name="AudioPolicyTestActivity"
+        <activity android:label="@string/app_name" android:name="AudioVolumeTestActivity"
                   android:screenOrientation="landscape" android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
         </activity>
+        <activity android:label="@string/app_name" android:name="AudioPolicyDeathTestActivity"
+                  android:screenOrientation="landscape"
+                  android:process=":AudioPolicyDeathTestActivityProcess"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
new file mode 100644
index 0000000..841804b
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.audiopolicytest;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioTrack;
+import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AudioPolicyDeathTest {
+    private static final String TAG = "AudioPolicyDeathTest";
+
+    private static final int SAMPLE_RATE = 48000;
+    private static final int PLAYBACK_TIME_MS = 2000;
+
+    private static final IntentFilter AUDIO_NOISY_INTENT_FILTER =
+            new IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+
+    private class MyBroadcastReceiver extends BroadcastReceiver {
+        private boolean mReceived = false;
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY.equals(intent.getAction())) {
+                synchronized (this) {
+                    mReceived = true;
+                    notify();
+                }
+            }
+        }
+
+        public synchronized boolean received() {
+            return mReceived;
+        }
+    }
+    private final MyBroadcastReceiver mReceiver = new MyBroadcastReceiver();
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = getApplicationContext();
+        assertEquals(PackageManager.PERMISSION_GRANTED,
+                mContext.checkSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
+    }
+
+    //-----------------------------------------------------------------
+    // Tests that an AUDIO_BECOMING_NOISY intent is broadcast when an app having registered
+    // a dynamic audio policy that intercepts an active media playback dies
+    //-----------------------------------------------------------------
+    @Test
+    public void testPolicyClientDeathSendBecomingNoisyIntent() {
+        mContext.registerReceiver(mReceiver, AUDIO_NOISY_INTENT_FILTER);
+
+        // Launch process registering a dynamic auido policy and dying after PLAYBACK_TIME_MS/2 ms
+        Intent intent = new Intent(mContext, AudioPolicyDeathTestActivity.class);
+        intent.putExtra("captureDurationMs", PLAYBACK_TIME_MS / 2);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        AudioTrack track = createAudioTrack();
+        track.play();
+        synchronized (mReceiver) {
+            long startTimeMs = System.currentTimeMillis();
+            long elapsedTimeMs = 0;
+            while (elapsedTimeMs < PLAYBACK_TIME_MS && !mReceiver.received()) {
+                try {
+                    mReceiver.wait(PLAYBACK_TIME_MS - elapsedTimeMs);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "wait interrupted");
+                }
+                elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+            }
+        }
+
+        track.stop();
+        track.release();
+
+        assertTrue(mReceiver.received());
+    }
+
+    private AudioTrack createAudioTrack() {
+        AudioFormat format = new AudioFormat.Builder()
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setSampleRate(SAMPLE_RATE)
+                .build();
+
+        short[] data = new short[PLAYBACK_TIME_MS * SAMPLE_RATE * format.getChannelCount() / 1000];
+        AudioAttributes attributes =
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build();
+
+        AudioTrack track = new AudioTrack(attributes, format, data.length,
+                AudioTrack.MODE_STATIC, AudioManager.AUDIO_SESSION_ID_GENERATE);
+        track.write(data, 0, data.length);
+
+        return track;
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
new file mode 100644
index 0000000..957e719
--- /dev/null
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyDeathTestActivity.java
@@ -0,0 +1,132 @@
+/*
+ * 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.audiopolicytest;
+
+import android.app.Activity;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.audiopolicy.AudioMix;
+import android.media.audiopolicy.AudioMixingRule;
+import android.media.audiopolicy.AudioPolicy;
+import android.os.Bundle;
+import android.os.Looper;
+import android.util.Log;
+
+// This activity will register a dynamic audio policy to intercept media playback and launch
+// a thread that will capture audio from the policy mix and crash after the time indicated by
+// intent extra "captureDurationMs" has elapsed
+public class AudioPolicyDeathTestActivity extends Activity  {
+    private static final String TAG = "AudioPolicyDeathTestActivity";
+
+    private static final int SAMPLE_RATE = 48000;
+    private static final int RECORD_TIME_MS = 1000;
+
+    private AudioManager mAudioManager = null;
+    private AudioPolicy mAudioPolicy = null;
+
+    public AudioPolicyDeathTestActivity() {
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mAudioManager = getApplicationContext().getSystemService(AudioManager.class);
+
+        AudioAttributes attributes = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA).build();
+        AudioMixingRule.Builder audioMixingRuleBuilder = new AudioMixingRule.Builder()
+                .addRule(attributes, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
+
+        AudioFormat audioFormat = new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                .setSampleRate(SAMPLE_RATE)
+                .build();
+
+        AudioMix audioMix = new AudioMix.Builder(audioMixingRuleBuilder.build())
+                .setFormat(audioFormat)
+                .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
+                .build();
+
+        AudioPolicy.Builder audioPolicyBuilder = new AudioPolicy.Builder(getApplicationContext());
+        audioPolicyBuilder.addMix(audioMix)
+                .setLooper(Looper.getMainLooper());
+        mAudioPolicy = audioPolicyBuilder.build();
+
+        int result = mAudioManager.registerAudioPolicy(mAudioPolicy);
+        if (result != AudioManager.SUCCESS) {
+            Log.w(TAG, "registerAudioPolicy failed, status: " + result);
+            return;
+        }
+        AudioRecord audioRecord = mAudioPolicy.createAudioRecordSink(audioMix);
+        if (audioRecord == null) {
+            Log.w(TAG, "AudioRecord creation failed");
+            return;
+        }
+
+        int captureDurationMs = getIntent().getIntExtra("captureDurationMs", RECORD_TIME_MS);
+        AudioCapturingThread thread = new AudioCapturingThread(audioRecord, captureDurationMs);
+        thread.start();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (mAudioManager != null && mAudioPolicy != null) {
+            mAudioManager.unregisterAudioPolicy(mAudioPolicy);
+        }
+    }
+
+    // A thread that captures audio from the supplied AudioRecord and crashes after the supplied
+    // duration has elapsed
+    private static class AudioCapturingThread extends Thread {
+        private final AudioRecord mAudioRecord;
+        private final int mDurationMs;
+
+        AudioCapturingThread(AudioRecord record, int durationMs) {
+            super();
+            mAudioRecord = record;
+            mDurationMs = durationMs;
+        }
+
+        @Override
+        @SuppressWarnings("ConstantOverflow")
+        public void run() {
+            int samplesLeft = mDurationMs * SAMPLE_RATE * mAudioRecord.getChannelCount() / 1000;
+            short[] readBuffer = new short[samplesLeft / 10];
+            mAudioRecord.startRecording();
+            long startTimeMs = System.currentTimeMillis();
+            long elapsedTimeMs = 0;
+            do {
+                int read = readBuffer.length < samplesLeft ? readBuffer.length : samplesLeft;
+                read = mAudioRecord.read(readBuffer, 0, read);
+                elapsedTimeMs = System.currentTimeMillis() - startTimeMs;
+                if (read < 0) {
+                    Log.w(TAG, "read error: " + read);
+                    break;
+                }
+                samplesLeft -= read;
+            } while (elapsedTimeMs < mDurationMs && samplesLeft > 0);
+
+            // force process to crash
+            int i = 1 / 0;
+        }
+    }
+}
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
similarity index 91%
rename from media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
rename to media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
index e31c01a..8f61815 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioPolicyTestActivity.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumeTestActivity.java
@@ -19,9 +19,9 @@
 import android.app.Activity;
 import android.os.Bundle;
 
-public class AudioPolicyTestActivity extends Activity  {
+public class AudioVolumeTestActivity extends Activity  {
 
-    public AudioPolicyTestActivity() {
+    public AudioVolumeTestActivity() {
     }
 
     /** Called when the activity is first created. */
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
index fc3b198..c6ec7a6 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioVolumesTestRule.java
@@ -100,7 +100,7 @@
 
     @Before
     public void setUp() throws Exception {
-        ActivityScenario.launch(AudioPolicyTestActivity.class);
+        ActivityScenario.launch(AudioVolumeTestActivity.class);
 
         mContext = getApplicationContext();
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
diff --git a/media/tests/projection/OWNERS b/media/tests/projection/OWNERS
new file mode 100644
index 0000000..832bcd9
--- /dev/null
+++ b/media/tests/projection/OWNERS
@@ -0,0 +1 @@
+include /media/java/android/media/projection/OWNERS
diff --git a/packages/CarrierDefaultApp/res/values-af/strings.xml b/packages/CarrierDefaultApp/res/values-af/strings.xml
index 3bc18ce..f3ea488 100644
--- a/packages/CarrierDefaultApp/res/values-af/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-af/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Die netwerk waarby jy probeer aansluit, het sekuriteitkwessies."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Byvoorbeeld, die aanmeldbladsy behoort dalk nie aan die organisasie wat gewys word nie."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Gaan in elk geval deur blaaier voort"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Netwerkhupstoot"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s beveel ’n datahupstoot aan"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Koop ’n netwerkhupstoot vir beter werkverrigting"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Nie nou nie"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Bestuur"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Koop ’n netwerkhupstoot."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ar/strings.xml b/packages/CarrierDefaultApp/res/values-ar/strings.xml
index cd979b2..f5ae9f2 100644
--- a/packages/CarrierDefaultApp/res/values-ar/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ar/strings.xml
@@ -16,16 +16,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"الشبكة التي تحاول الانضمام إليها بها مشاكل أمنية."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"على سبيل المثال، قد لا تنتمي صفحة تسجيل الدخول إلى المؤسسة المعروضة."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"المتابعة على أي حال عبر المتصفح"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"خطة معزَّزة للشبكة"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"‏هناك اقتراح من %s بخطة معزَّزة للبيانات"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"من خلال شراء خطة معزَّزة للشبكة، يمكنك الاستفادة من أداء أفضل."</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"لاحقًا"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"إدارة"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"شراء خطة معزَّزة للشبكة"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-as/strings.xml b/packages/CarrierDefaultApp/res/values-as/strings.xml
index fdafe2b..b983fd1 100644
--- a/packages/CarrierDefaultApp/res/values-as/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-as/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"আপুনি সংযোগ কৰিবলৈ বিচৰা নেটৱৰ্কটোত সুৰক্ষাজনিত সমস্যা আছে।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"উদাহৰণস্বৰূপে, আপোনাক দেখুওৱা লগ ইনৰ পৃষ্ঠাটো প্ৰতিষ্ঠানটোৰ নিজা নহ\'বও পাৰে।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"তথাপিও ব্ৰাউজাৰৰ জৰিয়তে অব্যাহত ৰাখক"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"নেটৱৰ্ক পৰিৱৰ্ধন"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%sএ এটা ডেটা পৰিৱৰ্ধনৰ চুপাৰিছ কৰিছে"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"উন্নত পাৰদৰ্শিতা পাবলৈ এটা নেটৱৰ্ক পৰিৱৰ্ধন ক্ৰয় কৰক"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"এতিয়া নহয়"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"পৰিচালনা কৰক"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"এটা নেটৱৰ্ক পৰিৱৰ্ধন ক্ৰয় কৰক।"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-az/strings.xml b/packages/CarrierDefaultApp/res/values-az/strings.xml
index 32c3c1c..3e6e1a8 100644
--- a/packages/CarrierDefaultApp/res/values-az/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-az/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Qoşulmaq istədiyiniz şəbəkənin təhlükəsizlik problemləri var."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Məsələn, giriş səhifəsi göstərilən təşkilata aid olmaya bilər."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Hər bir halda brazuer ilə davam edin"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Əlavə trafik"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s əlavə data trafiki almağı tövsiyə edir"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Daha yaxşı performans üçün əlavə şəbəkə trafiki alın"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"İndi yox"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"İdarə edin"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Əlavə trafik alın."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
index 932fc03..a1974f0 100644
--- a/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-b+sr+Latn/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate da se pridružite ima bezbednosnih problema."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na primer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko pregledača"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Pojačanje mreže"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s preporučuje povećanje podataka"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kupite pojačanje mreže za bolji učinak"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne sada"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Upravljajte"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kupite pojačanje mreže."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-be/strings.xml b/packages/CarrierDefaultApp/res/values-be/strings.xml
index 20606f6..1cb013b 100644
--- a/packages/CarrierDefaultApp/res/values-be/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-be/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"У сеткі, да якой вы спрабуеце далучыцца, ёсць праблемы з бяспекай."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Напрыклад, старонка ўваходу можа не належаць указанай арганізацыі."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Усё роўна працягнуць праз браўзер"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Паскарэнне сеткі"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s рэкамендуе паскарэнне перадачы даных"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Купіце паскарэнне сеткі, каб павысіць прадукцыйнасць"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не цяпер"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Кіраваць"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Купіць паскарэнне сеткі."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bg/strings.xml b/packages/CarrierDefaultApp/res/values-bg/strings.xml
index 46a9db5..aee67d7 100644
--- a/packages/CarrierDefaultApp/res/values-bg/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bg/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата, към която опитвате да се присъедините, има проблеми със сигурността."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Например страницата за вход може да не принадлежи на показаната организация."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Продължаване през браузър въпреки това"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Подсилване на мрежата"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s препоръчва увеличаване на данните"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Купете подсилване на мрежата за по-висока ефективност"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не сега"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Управление"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Купете подсилване на мрежата."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bn/strings.xml b/packages/CarrierDefaultApp/res/values-bn/strings.xml
index 0826ae1..7f93175 100644
--- a/packages/CarrierDefaultApp/res/values-bn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bn/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"আপনি যে নেটওয়ার্কে যোগ দেওয়ার চেষ্টা করছেন সেটিতে নিরাপত্তাজনিত সমস্যা আছে।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"যেমন, লগ-ইন পৃষ্ঠাটি যে প্রতিষ্ঠানের পৃষ্ঠা বলে দেখানো আছে, আসলে তা নাও হতে পারে৷"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"যাই হোক, ব্রাউজারের মাধ্যমে চালিয়ে যান"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"নেটওয়ার্ক বুস্ট"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ডেটা বুস্ট করার জন্য সাজেস্ট করে"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"আরও ভাল পারফর্ম্যান্সের জন্য নেটওয়ার্ক বুস্ট কিনুন"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"এখন নয়"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ম্যানেজ করুন"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"নেটওয়ার্ক বুস্ট কিনুন।"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-bs/strings.xml b/packages/CarrierDefaultApp/res/values-bs/strings.xml
index e2bc342..093f03f 100644
--- a/packages/CarrierDefaultApp/res/values-bs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-bs/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj pokušavate pristupiti ima sigurnosnih problema."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Naprimjer, stranica za prijavljivanje možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi preko preglednika"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Pojačanje mreže"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s preporučuje povećanje podataka"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kupite pojačanje mreže za bolju izvedbu"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne sad"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Upravljajte"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kupite pojačanje mreže."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ca/strings.xml b/packages/CarrierDefaultApp/res/values-ca/strings.xml
index bdde567..63b243a 100644
--- a/packages/CarrierDefaultApp/res/values-ca/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ca/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La xarxa a què et vols connectar té problemes de seguretat."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Per exemple, la pàgina d\'inici de sessió podria no pertànyer a l\'organització que es mostra."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continua igualment mitjançant el navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Optimització de xarxa"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomana optimitzar les dades"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compra una optimització de xarxa per millorar-ne el rendiment"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ara no"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gestiona"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Compra una optimització de xarxa."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-cs/strings.xml b/packages/CarrierDefaultApp/res/values-cs/strings.xml
index d5fdac9..3cff883 100644
--- a/packages/CarrierDefaultApp/res/values-cs/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-cs/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Síť, ke které se pokoušíte připojit, má bezpečnostní problémy."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Přihlašovací stránka například nemusí patřit zobrazované organizaci."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Přesto pokračovat prostřednictvím prohlížeče"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Zesilovač sítě"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s doporučuje zesílení datového připojení"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Pořiďte si zesilovač, který zvýší výkon sítě"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Teď ne"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Spravovat"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kupte si zesilovač sítě."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-da/strings.xml b/packages/CarrierDefaultApp/res/values-da/strings.xml
index 8b2bb7c..487f62c 100644
--- a/packages/CarrierDefaultApp/res/values-da/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-da/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Der er sikkerhedsproblemer på det netværk, du forsøger at logge ind på."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det er f.eks. ikke sikkert, at loginsiden tilhører den anførte organisation."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsæt alligevel via browseren"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Netværksboost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s anbefaler et databoost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Køb et netværksboost for at få en bedre ydeevne"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ikke nu"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Administrer"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Køb et netværksboost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-de/strings.xml b/packages/CarrierDefaultApp/res/values-de/strings.xml
index 21af41c..1394d10 100644
--- a/packages/CarrierDefaultApp/res/values-de/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-de/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Im Netzwerk, zu dem du eine Verbindung herstellen möchtest, liegen Sicherheitsprobleme vor."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Beispiel: Die Log-in-Seite gehört eventuell nicht zur angezeigten Organisation."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Trotzdem in einem Browser fortfahren"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Netzwerk-Boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s empfiehlt einen Daten-Boost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Netzwerk-Boost für bessere Leistung kaufen"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Nicht jetzt"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Verwalten"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Jetzt Netzwerk-Boost kaufen."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-el/strings.xml b/packages/CarrierDefaultApp/res/values-el/strings.xml
index 7514314..3551401 100644
--- a/packages/CarrierDefaultApp/res/values-el/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-el/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Παρουσιάζονται προβλήματα ασφάλειας στο δίκτυο στο οποίο προσπαθείτε να συνδεθείτε."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Για παράδειγμα, η σελίδα σύνδεσης ενδέχεται να μην ανήκει στον οργανισμό που εμφανίζεται."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Συνέχεια ούτως ή άλλως μέσω του προγράμματος περιήγησης"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Ενίσχυση δικτύου"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"Η %s συνιστά ενίσχυση δεδομένων"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Αγοράστε ενίσχυση δικτύου για καλύτερη απόδοση"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Όχι τώρα"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Διαχείριση"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Αγορά ενίσχυσης δικτύου."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
index bddae48..5fa8347 100644
--- a/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es-rUS/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas conectarte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de acceso no pertenezca a la organización que aparece."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos desde el navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Potenciación de red"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomienda un potenciador de datos"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compra un potenciador de red para obtener un mejor rendimiento"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ahora no"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Administrar"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Compra un potenciador de red."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-es/strings.xml b/packages/CarrierDefaultApp/res/values-es/strings.xml
index dd56770..e65bc9b 100644
--- a/packages/CarrierDefaultApp/res/values-es/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-es/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La red a la que intentas unirte tiene problemas de seguridad."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por ejemplo, es posible que la página de inicio de sesión no pertenezca a la organización mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar de todos modos a través del navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Mejora de red"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomienda una mejora de datos"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compra una mejora de red para impulsar el rendimiento"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ahora no"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gestionar"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Compra una mejora de red."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-et/strings.xml b/packages/CarrierDefaultApp/res/values-et/strings.xml
index 8cdc291..a951e7f 100644
--- a/packages/CarrierDefaultApp/res/values-et/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-et/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Võrgul, millega üritate ühenduse luua, on turvaprobleeme."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Näiteks ei pruugi sisselogimisleht kuuluda kuvatavale organisatsioonile."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jätka siiski brauseris"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Võrgu võimendus"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s soovitab andmeside võimendust"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Ostke võrgu võimendus, et toimivust parandada"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Mitte praegu"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Haldamine"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Ostke võrgu võimendus."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-eu/strings.xml b/packages/CarrierDefaultApp/res/values-eu/strings.xml
index 22565dc..ad80cc9 100644
--- a/packages/CarrierDefaultApp/res/values-eu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-eu/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Erabili nahi duzun sareak segurtasun-arazoak ditu."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Adibidez, baliteke saioa hasteko orria adierazitako erakundearena ez izatea."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jarraitu arakatzailearen bidez, halere"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Sarearen hobekuntza"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s zerbitzuak datuak hobetzeko gomendatzen du"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Errendimendua areagotzeko, erosi sarearen hobekuntza bat"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Orain ez"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Kudeatu"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Erosi sarearen hobekuntza bat."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fa/strings.xml b/packages/CarrierDefaultApp/res/values-fa/strings.xml
index ecb9930..4f55d76 100644
--- a/packages/CarrierDefaultApp/res/values-fa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fa/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"شبکه‌ای که می‌خواهید به آن بپیوندید مشکلات امنیتی دارد."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"به عنوان مثال، صفحه ورود به سیستم ممکن است متعلق به سازمان نشان داده شده نباشد."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"درهر صورت ازطریق مرورگر ادامه یابد"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"تقویت‌کننده شبکه"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"‏%s توصیه می‌کند از تقویت‌کننده داده استفاده کنید"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"برای بهبود عملکرد، تقویت‌کننده شبکه خریداری کنید"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"اکنون نه"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"مدیریت"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"تقویت‌کننده شبکه بخرید."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fi/strings.xml b/packages/CarrierDefaultApp/res/values-fi/strings.xml
index 850f9db..5ceddfb 100644
--- a/packages/CarrierDefaultApp/res/values-fi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fi/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Verkossa, johon yrität muodostaa yhteyttä, havaittiin turvallisuusongelmia."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Kirjautumissivu ei välttämättä kuulu näytetylle organisaatiolle."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Jatka selaimen kautta"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Tehokkaampi verkko"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s suosittelee tehokkaampaa verkkoa"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Osta tehokkaampi verkko, jotta saat parempia tuloksia"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ei nyt"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Muokkaa"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Osta tehokkaampi verkko."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
index 61fc2e4..d7dc3ac 100644
--- a/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr-rCA/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau que vous essayez de joindre présente des problèmes de sécurité."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion pourrait ne pas appartenir à l\'organisation représentée."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans un navigateur"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Amplification de réseau"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommande une augmentation des données"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Acheter une amplification de réseau pour de meilleures performances"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Plus tard"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gérer"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Acheter une amplification de réseau."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-fr/strings.xml b/packages/CarrierDefaultApp/res/values-fr/strings.xml
index ef1857d..2067e7b 100644
--- a/packages/CarrierDefaultApp/res/values-fr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-fr/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Le réseau auquel vous essayez de vous connecter présente des problèmes de sécurité."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Par exemple, la page de connexion peut ne pas appartenir à l\'organisation représentée."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuer quand même dans le navigateur"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Boost réseau"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recommande de booster les données"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Achetez un boost réseau pour améliorer les performances"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Pas maintenant"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gérer"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Acheter un boost réseau."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-gl/strings.xml b/packages/CarrierDefaultApp/res/values-gl/strings.xml
index 6dde8a8..a01941f 100644
--- a/packages/CarrierDefaultApp/res/values-gl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gl/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede á que tentas unirte ten problemas de seguranza."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, é posible que a páxina de inicio de sesión non pertenza á organización que se mostra."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar igualmente co navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Optimizador de rede"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda un optimizador de datos"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Compra un optimizador de rede para obter un mellor rendemento"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora non"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Xestionar"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Compra un optimizador de rede."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-gu/strings.xml b/packages/CarrierDefaultApp/res/values-gu/strings.xml
index 473a035..28f9ecb 100644
--- a/packages/CarrierDefaultApp/res/values-gu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-gu/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"તમે જોડાવાનો પ્રયાસ કરી રહ્યા છો તે નેટવર્કમાં સુરક્ષા સંબંધી સમસ્યાઓ છે."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ઉદાહરણ તરીકે, લોગિન પૃષ્ઠ બતાવવામાં આવેલી સંસ્થાનું ન પણ હોય."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"તો પણ બ્રાઉઝર મારફતે ચાલુ રાખો"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"નેટવર્ક બૂસ્ટ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"ડેટા બૂસ્ટનો સુઝાવ %s દ્વારા આપવામાં આવે છે"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"બહેતર પર્ફોર્મન્સ માટે, કોઈ નેટવર્ક બૂસ્ટ ખરીદો"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"હમણાં નહીં"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"મેનેજ કરો"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"કોઈ નેટવર્ક બૂસ્ટ ખરીદો."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hi/strings.xml b/packages/CarrierDefaultApp/res/values-hi/strings.xml
index d878c1c..75206b7 100644
--- a/packages/CarrierDefaultApp/res/values-hi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hi/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"आप जिस नेटवर्क में शामिल होने की कोशिश कर रहे हैं उसमें सुरक्षा से जुड़ी समस्‍याएं हैं."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरण के लिए, हो सकता है कि लॉगिन पेज दिखाए गए संगठन का ना हो."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ब्राउज़र के ज़रिए किसी भी तरह जारी रखें"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"नेटवर्क बूस्ट"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s, डेटा बूस्ट इस्तेमाल करने का सुझाव देता है"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"बेहतर परफ़ॉर्मेंस के लिए, नेटवर्क बूस्ट खरीदें"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"अभी नहीं"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"मैनेज करें"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"नेटवर्क बूस्ट खरीदें."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hr/strings.xml b/packages/CarrierDefaultApp/res/values-hr/strings.xml
index 0da2280..d31f4f8 100644
--- a/packages/CarrierDefaultApp/res/values-hr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hr/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mreža kojoj se pokušavate pridružiti ima sigurnosne poteškoće."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na primjer, stranica za prijavu možda ne pripada prikazanoj organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ipak nastavi putem preglednika"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Pojačanje mreže"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s preporučuje povećanje podataka"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kupite pojačanje mreže za bolju izvedbu"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne sad"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Upravljajte"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kupite pojačanje mreže."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hu/strings.xml b/packages/CarrierDefaultApp/res/values-hu/strings.xml
index 95c1db8..afd6742 100644
--- a/packages/CarrierDefaultApp/res/values-hu/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hu/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Biztonsági problémák vannak azzal a hálózattal, amelyhez csatlakozni szeretne."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Például lehetséges, hogy a bejelentkezési oldal nem a megjelenített szervezethez tartozik."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Folytatás ennek ellenére böngészőn keresztül"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Teljesítménynövelés"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s a teljesítménynövelést javasol"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Vásároljon teljesítménynövelési lehetőséget a jobb teljesítmény érdekében"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Most nem"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Kezelés"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Vásároljon teljesítménynövelést."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-hy/strings.xml b/packages/CarrierDefaultApp/res/values-hy/strings.xml
index a846e04..1effc3d 100644
--- a/packages/CarrierDefaultApp/res/values-hy/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-hy/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Ցանցը, որին փորձում եք միանալ, անվտանգության խնդիրներ ունի:"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Օրինակ՝ մուտքի էջը կարող է ցուցադրված կազմակերպության էջը չլինել:"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Շարունակել դիտարկիչի միջոցով"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Բջջային ինտերնետի փաթեթ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s-ը խորհուրդ է տալիս գնել բջջային ինտերնետի փաթեթ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Գնեք բջջային ինտերնետի փաթեթ՝ աշխատանքի արդյունավետությունը լավացնելու համար"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ոչ հիմա"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Կառավարել"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Գնել ցանցի բջջային ինտերնետի փաթեթ։"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-in/strings.xml b/packages/CarrierDefaultApp/res/values-in/strings.xml
index 488ad09..ed7385a 100644
--- a/packages/CarrierDefaultApp/res/values-in/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-in/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Jaringan yang ingin Anda masuki memiliki masalah keamanan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Misalnya, halaman login mungkin bukan milik organisasi yang ditampilkan."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Tetap lanjutkan melalui browser"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Penguat sinyal"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s merekomendasikan penguat sinyal data seluler"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Beli penguat sinyal untuk performa yang lebih baik"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Lain kali"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Kelola"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Beli penguat sinyal."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-is/strings.xml b/packages/CarrierDefaultApp/res/values-is/strings.xml
index ab4981d..df8c01b 100644
--- a/packages/CarrierDefaultApp/res/values-is/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-is/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Öryggisvandamál eru á netinu sem þú ert að reyna að tengjast."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Til dæmis getur verið að innskráningarsíðan tilheyri ekki fyrirtækinu sem birtist."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Halda samt áfram í vafra"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Nethröðun"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s mælir með auknu gagnamagni"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kauptu nethröðun til að bæta afköstin"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ekki núna"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Stjórna"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kaupa nethröðun."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-it/strings.xml b/packages/CarrierDefaultApp/res/values-it/strings.xml
index 95ea060..3c76f21 100644
--- a/packages/CarrierDefaultApp/res/values-it/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-it/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"La rete a cui stai tentando di accedere presenta problemi di sicurezza."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Ad esempio, la pagina di accesso potrebbe non appartenere all\'organizzazione indicata."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continua comunque dal browser"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Potenziamento di rete"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s consiglia un aumento dei dati"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Acquista un potenziamento di rete per migliorare le prestazioni"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Non ora"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gestisci"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Acquista un potenziamento di rete."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-iw/strings.xml b/packages/CarrierDefaultApp/res/values-iw/strings.xml
index 263ed1a..9f7cc8e 100644
--- a/packages/CarrierDefaultApp/res/values-iw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-iw/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"יש בעיות אבטחה ברשת שאליה אתה מנסה להתחבר."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"לדוגמה, ייתכן שדף ההתחברות אינו שייך לארגון המוצג."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"המשך בכל זאת באמצעות דפדפן"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"מגבר לרשת"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"‏יש המלצה של %s להגברת הרשת"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"קניית מגבר לרשת לשיפור הביצועים"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"לא עכשיו"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ניהול"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"קניית מגבר לרשת."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ja/strings.xml b/packages/CarrierDefaultApp/res/values-ja/strings.xml
index 3b22ae1..4b709afc 100644
--- a/packages/CarrierDefaultApp/res/values-ja/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ja/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"接続しようとしているネットワークにセキュリティの問題があります。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"たとえば、ログインページが表示されている組織に属していない可能性があります。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ブラウザから続行"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ネットワーク ブースト"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ではデータブーストが推奨されます"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ネットワーク ブーストを購入してパフォーマンスを改善しましょう"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"後で"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"管理"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ネットワーク ブーストを購入できます。"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ka/strings.xml b/packages/CarrierDefaultApp/res/values-ka/strings.xml
index 4b9cd38..713c488 100644
--- a/packages/CarrierDefaultApp/res/values-ka/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ka/strings.xml
@@ -14,16 +14,12 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ქსელს, რომელთან დაკავშრებასაც ცდილობთ, უსაფრთხოების პრობლემები აქვს."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"მაგალითად, სისტემაში შესვლის გვერდი შეიძლება არ ეკუთვნოდეს ნაჩვენებ ორგანიზაციას."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"მაინც ბრაუზერში გაგრძელება"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ქსელის გაძლიერება"</string>
+    <!-- String.format failed for translation -->
     <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
     <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"შეიძინეთ ქსელის გაძლიერება უკეთესი მუშაობისთვის"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ახლა არა"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"მართვა"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"შეიძინეთ ქსელის გაძლიერება."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-kk/strings.xml b/packages/CarrierDefaultApp/res/values-kk/strings.xml
index fce8dd2..13fdc0a 100644
--- a/packages/CarrierDefaultApp/res/values-kk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kk/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Қосылайын деп жатқан желіңізде қауіпсіздік мәселелері бар."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Мысалы, кіру беті көрсетілген ұйымға тиесілі болмауы мүмкін."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Бәрібір браузер арқылы жалғастыру"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Күшейтілген желі"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s күшейтілген желі пакетін ұсынады"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Өнімділікті арттыру үшін күшейтілген желі пакетін сатып алыңыз."</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Қазір емес"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Басқару"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Күшейтілген желіні сатып алыңыз."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-km/strings.xml b/packages/CarrierDefaultApp/res/values-km/strings.xml
index 983f09e..7993f6f 100644
--- a/packages/CarrierDefaultApp/res/values-km/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-km/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"បណ្តាញដែលអ្នកកំពុងព្យាយាមចូលមានបញ្ហាសុវត្ថិភាព។"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ឧទាហរណ៍៖ ទំព័រចូលនេះអាចនឹងមិនមែនជាកម្មសិទ្ធិរបស់ស្ថាប័នដែលបានបង្ហាញនេះទេ។"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"យ៉ាងណាក៏ដោយនៅតែបន្តតាមរយៈកម្មវិធីរុករកតាមអ៊ីនធឺណិត"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ការបង្កើនល្បឿនបណ្ដាញ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ណែនាំ​ឱ្យបង្កើន​ទិន្នន័យ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ទិញ​ការបង្កើនល្បឿនបណ្ដាញ ដើម្បីឱ្យប្រតិបត្តិការ​ប្រសើរជាងមុន"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"កុំទាន់"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"គ្រប់គ្រង"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ទិញការបង្កើន​ល្បឿនបណ្ដាញ។"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-kn/strings.xml b/packages/CarrierDefaultApp/res/values-kn/strings.xml
index 86b29ce..323885e 100644
--- a/packages/CarrierDefaultApp/res/values-kn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-kn/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ನೀವು ಸೇರಬೇಕೆಂದಿರುವ ನೆಟ್‌ವರ್ಕ್, ಭದ್ರತೆ ಸಮಸ್ಯೆಗಳನ್ನು ಹೊಂದಿದೆ."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ಉದಾಹರಣೆಗೆ, ಲಾಗಿನ್ ಪುಟವು ತೋರಿಸಲಾಗಿರುವ ಸಂಸ್ಥೆಗೆ ಸಂಬಂಧಿಸಿಲ್ಲದಿರಬಹುದು."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ಪರವಾಗಿಲ್ಲ, ಬ್ರೌಸರ್ ಮೂಲಕ ಮುಂದುವರಿಸಿ"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ನೆಟ್‌ವರ್ಕ್ ಬೂಸ್ಟ್"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ಡೇಟಾ ಬೂಸ್ಟ್ ಅನ್ನು ಶಿಫಾರಸು ಮಾಡುತ್ತದೆ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ಉತ್ತಮ ಕಾರ್ಯಕ್ಷಮತೆಗಾಗಿ ನೆಟ್‌ವರ್ಕ್ ಬೂಸ್ಟ್ ಅನ್ನು ಖರೀದಿಸಿ"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ಸದ್ಯಕ್ಕೆ ಬೇಡ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ನಿರ್ವಹಿಸಿ"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ನೆಟ್‌ವರ್ಕ್ ಬೂಸ್ಟ್ ಖರೀದಿಸಿ."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ko/strings.xml b/packages/CarrierDefaultApp/res/values-ko/strings.xml
index 3c6f4a6..8ce69a3 100644
--- a/packages/CarrierDefaultApp/res/values-ko/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ko/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"가입하려는 네트워크에 보안 문제가 있습니다."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"예를 들어 로그인 페이지가 표시된 조직에 속하지 않을 수 있습니다."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"브라우저를 통해 계속하기"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"성능 향상"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s에서 성능 향상을 권장합니다"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"성능 향상을 구매하여 성능을 개선하세요."</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"나중에"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"관리"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"성능 향상을 구매하세요."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ky/strings.xml b/packages/CarrierDefaultApp/res/values-ky/strings.xml
index 3cece7d..637620c 100644
--- a/packages/CarrierDefaultApp/res/values-ky/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ky/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Кошулайын деген тармагыңызда коопсуздук көйгөйлөрү бар."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Мисалы, аккаунтка кирүү баракчасы көрсөтүлгөн уюмга таандык эмес болушу мүмкүн."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Баары бир серепчи аркылуу улантуу"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Тармакты оптималдаштыруу"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s тармак оптималдаштыруусун сунуштайт"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Иштин майнаптуулугун жогорулатуу үчүн тармакты оптималдаштыруу мүмкүнчүлүгүн сатып алыңыз"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Азыр эмес"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Тескөө"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Тармакты оптималдаштырууну сатып алыңыз."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lo/strings.xml b/packages/CarrierDefaultApp/res/values-lo/strings.xml
index c6c0533..124e5cc 100644
--- a/packages/CarrierDefaultApp/res/values-lo/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lo/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ເຄືອຂ່າຍທີ່ທ່ານກຳລັງເຂົ້າຮ່ວມມີບັນຫາຄວາມປອດໄພ."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ຕົວຢ່າງ, ໜ້າເຂົ້າສູ່ລະບົບອາດຈະບໍ່ແມ່ນຂອງອົງກອນທີ່ປາກົດ."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ດຳເນີນການຕໍ່ຜ່ານໂປຣແກຣມທ່ອງເວັບ"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ການເພີ່ມປະສິດທິພາບເຄືອຂ່າຍ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ແນະນຳໃຫ້ເພີ່ມປະສິດທິພາບຂໍ້ມູນ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ຊື້ການເພີ່ມປະສິດທິພາບເຄືອຂ່າຍເພື່ອປະສິດທິພາບການເຮັດວຽກທີ່ດີຂຶ້ນ"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ບໍ່ຟ້າວເທື່ອ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ຈັດການ"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ຊື້ການເພີ່ມປະສິດທິພາບເຄືອຂ່າຍ."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lt/strings.xml b/packages/CarrierDefaultApp/res/values-lt/strings.xml
index fe33b1d..61e1593 100644
--- a/packages/CarrierDefaultApp/res/values-lt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lt/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Kilo tinklo, prie kurio bandote prisijungti, saugos problemų."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Pavyzdžiui, prisijungimo puslapis gali nepriklausyti rodomai organizacijai."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vis tiek tęsti naudojant naršyklę"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Papildomi duomenys"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s rekomenduoja įsigyti papildomų duomenų"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Įsigykite papildomų duomenų, kad pagerintumėte našumą"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne dabar"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Tvarkyti"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Įsigykite papildomų duomenų."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-lv/strings.xml b/packages/CarrierDefaultApp/res/values-lv/strings.xml
index f8864e2..3472b4c 100644
--- a/packages/CarrierDefaultApp/res/values-lv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-lv/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Tīklā, kuram mēģināt pievienoties, ir drošības problēmas."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Piemēram, pieteikšanās lapa, iespējams, nepieder norādītajai organizācijai."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Tomēr turpināt, izmantojot pārlūkprogrammu"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Tīkla veiktspējas uzlabojums"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s iesaka veiktspējas uzlabojumu"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Iegādājieties tīkla veiktspējas uzlabojumu."</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Vēlāk"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Pārvaldīt"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Iegādājieties tīkla veiktspējas uzlabojumu."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mk/strings.xml b/packages/CarrierDefaultApp/res/values-mk/strings.xml
index 0b8daaf..759c58a 100644
--- a/packages/CarrierDefaultApp/res/values-mk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mk/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежата на која се обидувате да се придружите има проблеми со безбедноста."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"На пример, страницата за најавување може да не припаѓа на прикажаната организација."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Сепак продолжи преку прелистувач"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Засилување за мрежата"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s препорачува засилување за мрежата"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Купете засилување за мрежата за подобра изведба"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не сега"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Управувајте"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Купете засилување за мрежата."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ml/strings.xml b/packages/CarrierDefaultApp/res/values-ml/strings.xml
index f27d4d8..08a04e3 100644
--- a/packages/CarrierDefaultApp/res/values-ml/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ml/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"നിങ്ങൾ ചേരാൻ ശ്രമിക്കുന്ന നെറ്റ്‌വർക്കിൽ സുരക്ഷാ പ്രശ്‌നങ്ങളുണ്ടായിരിക്കാം."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ഉദാഹരണത്തിന്, കാണിച്ചിരിക്കുന്ന ഓർഗനൈസേഷന്റേതായിരിക്കില്ല ലോഗിൻ പേജ്."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"എന്തായാലും ബ്രൗസർ വഴി തുടരുക"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"നെറ്റ്‌വർക്ക് ബൂസ്റ്റ്"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ഡാറ്റാ ബൂസ്റ്റ് നിർദ്ദേശിക്കുന്നു"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"മികച്ച പ്രകടനത്തിന് നെറ്റ്‌വർക്ക് ബൂസ്റ്റ് വാങ്ങുക"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ഇപ്പോൾ വേണ്ട"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"മാനേജ് ചെയ്യുക"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ഒരു നെറ്റ്‌വർക്ക് ബൂസ്‌റ്റ് വാങ്ങുക."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mn/strings.xml b/packages/CarrierDefaultApp/res/values-mn/strings.xml
index 354bd34..5ef4243 100644
--- a/packages/CarrierDefaultApp/res/values-mn/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mn/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Таны холбогдох гэж буй сүлжээ аюулгүй байдлын асуудалтай байна."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Жишээлбэл нэвтрэх хуудас нь харагдаж буй байгууллагынх биш байж болно."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ямар ч тохиолдолд хөтчөөр үргэлжлүүлэх"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Сүлжээний идэвхжүүлэлт"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s дата идэвхжүүлэлтийг санал болгодог"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Илүү сайн гүйцэтгэл авах бол сүлжээний идэвхжүүлэлтийг худалдаж авна уу"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Одоо биш"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Удирдах"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Сүлжээний идэвхжүүлэлтийг худалдан авна уу."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-mr/strings.xml b/packages/CarrierDefaultApp/res/values-mr/strings.xml
index c61d3c8..930ed8c 100644
--- a/packages/CarrierDefaultApp/res/values-mr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-mr/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"तुम्ही ज्या नेटवर्कमध्‍ये सामील होण्याचा प्रयत्न करत आहात त्यात सुरक्षितता समस्या आहेत."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणार्थ, लॉग इन पृष्‍ठ दर्शवलेल्या संस्थेच्या मालकीचे नसू शकते."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"तरीही ब्राउझरद्वारे सुरू ठेवा"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"नेटवर्क बूस्ट"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s डेटा बूस्टची शिफारस करते"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"आणखी चांगल्या परफॉर्मन्ससाठी नेटवर्क बूस्ट खरेदी करा"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"आता नको"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"व्यवस्थापित करा"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"नेटवर्क बूस्ट खरेदी करा."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ms/strings.xml b/packages/CarrierDefaultApp/res/values-ms/strings.xml
index 366463f..2f95915 100644
--- a/packages/CarrierDefaultApp/res/values-ms/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ms/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rangkaian yang cuba anda sertai mempunyai isu keselamatan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Contohnya, halaman log masuk mungkin bukan milik organisasi yang ditunjukkan."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Teruskan juga melalui penyemak imbas"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Perangsang rangkaian"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s mengesyorkan peningkatan data"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Beli perangsang rangkaian untuk mendapatkan prestasi yang lebih baik"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Bukan sekarang"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Urus"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Beli perangsang rangkaian."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-my/strings.xml b/packages/CarrierDefaultApp/res/values-my/strings.xml
index 2fa6188..1ca4317 100644
--- a/packages/CarrierDefaultApp/res/values-my/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-my/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"သင်ချိတ်ဆက်ရန် ကြိုးစားနေသည့် ကွန်ရက်တွင် လုံခြုံရေးပြဿနာများ ရှိနေသည်။"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ဥပမာ− ဝင်ရောက်ရန် စာမျက်နှာသည် ပြသထားသည့် အဖွဲ့အစည်းနှင့် သက်ဆိုင်မှုမရှိခြင်း ဖြစ်နိုင်ပါသည်။"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"မည်သို့ပင်ဖြစ်စေ ဘရောက်ဇာမှတစ်ဆင့် ရှေ့ဆက်ရန်"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ကွန်ရက်မြှင့်တင်အက်ပ်"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s က ဒေတာမြှင့်တင်ရန် အကြံပြုသည်"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"စွမ်းဆောင်ရည် ပိုကောင်းစေရန် ကွန်ရက်မြှင့်တင်အက်ပ် ဝယ်ပါ"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ယခုမလုပ်ပါ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"စီမံရန်"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ကွန်ရက်မြှင့်တင်အက်ပ် ဝယ်ယူရန်။"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-nb/strings.xml b/packages/CarrierDefaultApp/res/values-nb/strings.xml
index 16f8eaa..8660207 100644
--- a/packages/CarrierDefaultApp/res/values-nb/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nb/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Nettverket du prøver å logge på, har sikkerhetsproblemer."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det er for eksempel mulig at påloggingssiden ikke tilhører organisasjonen som vises."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsett likevel via nettleseren"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Nettverksboost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s anbefaler en databoost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kjøp en nettverksboost for å få bedre ytelse"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ikke nå"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Administrer"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kjøp en nettverksboost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ne/strings.xml b/packages/CarrierDefaultApp/res/values-ne/strings.xml
index cb175df..c92237c 100644
--- a/packages/CarrierDefaultApp/res/values-ne/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ne/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"तपाईंले सामेल हुने प्रयास गरिरहनु भएको नेटवर्कमा सुरक्षा सम्बन्धी समस्याहरू छन्।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"उदाहरणका लागि, लग इन पृष्ठ देखाइएको संस्थाको नहुन सक्छ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"जे भए पनि ब्राउजर मार्फत जारी राख्नुहोस्"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"नेटवर्क बुस्ट"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ले डेटा बुस्ट किन्न सिफारिस गर्छ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"अझ राम्रो पर्फर्मेन्सका लागि नेटवर्क बुस्ट किन्नुहोस्"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"अहिले होइन"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"व्यवस्थापन गर्नुहोस्"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"नेटवर्क बुस्ट किन्नुहोस्।"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-nl/strings.xml b/packages/CarrierDefaultApp/res/values-nl/strings.xml
index 8511ff5..1cd929e 100644
--- a/packages/CarrierDefaultApp/res/values-nl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-nl/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Het netwerk waarmee je verbinding probeert te maken, heeft beveiligingsproblemen."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Zo hoort de weergegeven inlogpagina misschien niet bij de weergegeven organisatie."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Toch doorgaan via browser"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Netwerkboost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s raadt een databoost aan"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Koop een netwerkboost voor betere prestaties"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Niet nu"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Beheren"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Koop een netwerkboost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-or/strings.xml b/packages/CarrierDefaultApp/res/values-or/strings.xml
index 65fd7bb..4a01c12 100644
--- a/packages/CarrierDefaultApp/res/values-or/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-or/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ଆପଣ ଯୋଗ ଦେବାକୁ ଚେଷ୍ଟା କରୁଥିବା ନେଟୱର୍କର ସୁରକ୍ଷା ସମସ୍ୟା ଅଛି।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ଉଦାହରଣସ୍ୱରୂପ, ଲଗଇନ୍‍ ପୃଷ୍ଠା ଦେଖାଯାଇଥିବା ସଂସ୍ଥାର ହୋଇନଥାଇପାରେ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ବ୍ରାଉଜର୍‍ ଜରିଆରେ ଯେମିତିବି ହେଉ ଜାରି ରଖନ୍ତୁ"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ନେଟୱାର୍କ ବୁଷ୍ଟ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ଏକ ଡାଟା ବୁଷ୍ଟ ପାଇଁ ସୁପାରିଶ କରେ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ଆହୁରି ଭଲ ପରଫରମାନ୍ସ ପାଇଁ ଏକ ନେଟୱାର୍କ ବୁଷ୍ଟ କିଣନ୍ତୁ"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ବର୍ତ୍ତମାନ ନୁହେଁ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ପରିଚାଳନା କରନ୍ତୁ"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ଏକ ନେଟୱାର୍କ ବୁଷ୍ଟ କିଣନ୍ତୁ।"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pa/strings.xml b/packages/CarrierDefaultApp/res/values-pa/strings.xml
index 0f096ab..6b15c1e 100644
--- a/packages/CarrierDefaultApp/res/values-pa/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pa/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ਤੁਸੀਂ ਜਿਸ ਨੈੱਟਵਰਕ ਵਿੱਚ ਸ਼ਾਮਲ ਹੋਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰ ਰਹੇ ਹੋ ਉਸ ਵਿੱਚ ਸੁਰੱਖਿਆ ਸਬੰਧੀ ਸਮੱਸਿਆਵਾਂ ਹਨ।"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ਉਦਾਹਰਣ ਵੱਜੋਂ, ਲੌਗ-ਇਨ ਪੰਨਾ ਦਿਖਾਈ ਗਈ ਸੰਸਥਾ ਨਾਲ ਸੰਬੰਧਿਤ ਨਹੀਂ ਹੋ ਸਕਦਾ ਹੈ।"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ਬ੍ਰਾਊਜ਼ਰ ਰਾਹੀਂ ਫਿਰ ਵੀ ਜਾਰੀ ਰੱਖੋ"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ਨੈੱਟਵਰਕ ਬੂਸਟ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s ਵੱਲੋਂ ਡਾਟਾ ਬੂਸਟ ਦੀ ਸਿਫ਼ਾਰਸ਼ ਕੀਤੀ ਜਾਂਦੀ ਹੈ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ਬਿਹਤਰ ਕਾਰਗੁਜ਼ਾਰੀ ਲਈ ਨੈੱਟਵਰਕ ਬੂਸਟ ਖਰੀਦੋ"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ਹੁਣੇ ਨਹੀਂ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"ਪ੍ਰਬੰਧਨ ਕਰੋ"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ਨੈੱਟਵਰਕ ਬੂਸਟ ਖਰੀਦੋ।"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pl/strings.xml b/packages/CarrierDefaultApp/res/values-pl/strings.xml
index 08bc767..86306a2 100644
--- a/packages/CarrierDefaultApp/res/values-pl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pl/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"W sieci, z którą próbujesz się połączyć, występują problemy z zabezpieczeniami."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Na przykład strona logowania może nie należeć do wyświetlanej organizacji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Kontynuuj mimo to w przeglądarce"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Wzmocnienie sygnału"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s zaleca wzmocnienie transmisji danych"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Kup wzmocnienie sygnału, aby zwiększyć skuteczność sieci"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Nie teraz"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Zarządzaj"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Kup wzmocnienie sygnału"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
index 23b4152..1138a8b 100644
--- a/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt-rBR/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Aumento de rede"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda um aumento de dados"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Aumente a rede para ter um desempenho melhor"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora não"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gerenciar"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Comprar um aumento de rede."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-pt/strings.xml b/packages/CarrierDefaultApp/res/values-pt/strings.xml
index 23b4152..1138a8b 100644
--- a/packages/CarrierDefaultApp/res/values-pt/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-pt/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"A rede à qual você está tentando se conectar tem problemas de segurança."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Por exemplo, a página de login pode não pertencer à organização mostrada."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuar mesmo assim pelo navegador"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Aumento de rede"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomenda um aumento de dados"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Aumente a rede para ter um desempenho melhor"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Agora não"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gerenciar"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Comprar um aumento de rede."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ro/strings.xml b/packages/CarrierDefaultApp/res/values-ro/strings.xml
index 165952c..4a9d57c2 100644
--- a/packages/CarrierDefaultApp/res/values-ro/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ro/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rețeaua la care încercați să vă conectați are probleme de securitate."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"De exemplu, este posibil ca pagina de conectare să nu aparțină organizației afișate."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Continuă oricum prin browser"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Pachet de date"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s recomandă un pachet de date"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Cumpără un pachet de date pentru o performanță mai bună"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Nu acum"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Gestionează"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Cumpără un pachet de date."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ru/strings.xml b/packages/CarrierDefaultApp/res/values-ru/strings.xml
index 77ce91f..4fd6ffb 100644
--- a/packages/CarrierDefaultApp/res/values-ru/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ru/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Сеть, к которой вы хотите подключиться, небезопасна."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Например, страница входа в аккаунт может быть фиктивной."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Продолжить в браузере"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Ускорение сети"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s рекомендует использовать дополнительные данные"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Приобретите ускорение сети, чтобы повысить ее производительность"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не сейчас"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Настроить"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Покупка ускорения сети."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-si/strings.xml b/packages/CarrierDefaultApp/res/values-si/strings.xml
index fe981ca..7dad968 100644
--- a/packages/CarrierDefaultApp/res/values-si/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-si/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"ඔබ සම්බන්ධ වීමට උත්සහ කරන ජාලයේ ආරක්ෂක ගැටළු ඇත."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"උදාහරණයක් ලෙස, පුරනය වන පිටුව පෙන්වා ඇති සංවිධානයට අයිති නැති විය හැක."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"කෙසේ වුවත් බ්‍රවුසරය හරහා ඉදිරියට යන්න"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"ජාල වැඩි කිරීම"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s දත්ත වැඩි කිරීමක් නිර්දේශ කරයි"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"වඩා හොඳ කාර්ය සාධනයක් සඳහා ජාල වැඩි වීමක් මිල දී ගන්න"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"දැන් නොවේ"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"කළමනාකරණය කරන්න"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ජාල වැඩි වීමක් මිල දී ගන්න."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sl/strings.xml b/packages/CarrierDefaultApp/res/values-sl/strings.xml
index 1a0f74b9..66dec186 100644
--- a/packages/CarrierDefaultApp/res/values-sl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sl/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Omrežje, ki se mu poskušate pridružiti, ima varnostne težave."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Stran za prijavo na primer morda ne pripada prikazani organizaciji."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vseeno nadaljuj v brskalniku"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Izboljšanje omrežja"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s priporoča izboljšanje prenosa podatkov"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Za boljše delovanje si zagotovite izboljšanje omrežja."</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Ne zdaj"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Upravljanje"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Nakup izboljšanja omrežja"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sq/strings.xml b/packages/CarrierDefaultApp/res/values-sq/strings.xml
index f6e1935..c0d430e 100644
--- a/packages/CarrierDefaultApp/res/values-sq/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sq/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Rrjeti në të cilin po përpiqesh të bashkohesh ka probleme sigurie."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"për shembull, faqja e identifikimit mund të mos i përkasë organizatës së shfaqur."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vazhdo gjithsesi nëpërmjet shfletuesit"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Paketa e përforcimit të rrjetit"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s rekomandon një paketë përforcimi të të dhënave"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Bli një paketë përforcimi të rrjetit për një performancë më të mirë"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Jo tani"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Menaxho"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Bli një paketë përforcimi të rrjetit."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sr/strings.xml b/packages/CarrierDefaultApp/res/values-sr/strings.xml
index e615ead..84db181b 100644
--- a/packages/CarrierDefaultApp/res/values-sr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sr/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Мрежа којој покушавате да се придружите има безбедносних проблема."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"На пример, страница за пријављивање можда не припада приказаној организацији."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Ипак настави преко прегледача"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Појачање мреже"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s препоручује повећање података"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Купите појачање мреже за бољи учинак"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не сада"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Управљајте"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Купите појачање мреже."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sv/strings.xml b/packages/CarrierDefaultApp/res/values-sv/strings.xml
index 778663b..5e8853a 100644
--- a/packages/CarrierDefaultApp/res/values-sv/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sv/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Nätverket du försöker ansluta till har säkerhetsproblem."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Det kan t.ex. hända att inloggningssidan inte tillhör den organisation som visas."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Fortsätt ändå via webbläsaren"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Nätverksboost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s rekommenderar en databoost"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Köp en nätverksboost för bättre resultat"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Inte nu"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Hantera"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Köp en nätverksboost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-sw/strings.xml b/packages/CarrierDefaultApp/res/values-sw/strings.xml
index 4f0745c..c99eb13 100644
--- a/packages/CarrierDefaultApp/res/values-sw/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-sw/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mtandao unaojaribu kujiunga nao una matatizo ya usalama."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Kwa mfano, ukurasa wa kuingia katika akaunti unaweza usiwe unamilikiwa na shirika lililoonyeshwa."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Endelea hata hivyo kupitia kivinjari"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Kuimarisha mtandao"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s inapendekeza kuimarisha data"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Nunua kifaa cha kuimarisha mtandao kwa utendaji bora zaidi"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Si sasa"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Dhibiti"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Nunua kifaa cha kuimarisha mtandao."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ta/strings.xml b/packages/CarrierDefaultApp/res/values-ta/strings.xml
index a1d2928..6f7f480 100644
--- a/packages/CarrierDefaultApp/res/values-ta/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ta/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"நீங்கள் சேர முயலும் நெட்வொர்க்கில் பாதுகாப்புச் சிக்கல்கள் உள்ளன."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"எடுத்துக்காட்டாக, உள்நுழைவுப் பக்கமானது காட்டப்படும் அமைப்பிற்குச் சொந்தமானதாக இல்லாமல் இருக்கலாம்."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"பரவாயில்லை, உலாவி வழியாகத் தொடர்க"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"நெட்வொர்க் பூஸ்ட்"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"டேட்டா பூஸ்ட்டை %s பரிந்துரைக்கிறது"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"சிறப்பான செயல்திறனுக்கு நெட்வொர்க் பூஸ்ட்டை வாங்கவும்"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"இப்போது வேண்டாம்"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"நிர்வகியுங்கள்"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"நெட்வொர்க் பூஸ்ட்டை வாங்குங்கள்."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-te/strings.xml b/packages/CarrierDefaultApp/res/values-te/strings.xml
index 7139903..d1e49ca 100644
--- a/packages/CarrierDefaultApp/res/values-te/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-te/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"మీరు చేరడానికి ప్రయత్నిస్తున్న నెట్‌వర్క్ భద్రతా సమస్యలను కలిగి ఉంది."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ఉదాహరణకు, లాగిన్ పేజీ చూపిన సంస్థకు చెందినది కాకపోవచ్చు."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ఏదేమైనా బ్రౌజర్ ద్వారా కొనసాగించు"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"నెట్‌వర్క్ బూస్ట్"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"డేటా బూస్ట్‌ను %s సిఫార్సు చేస్తోంది"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"మెరుగైన పనితీరు కోసం నెట్‌వర్క్ బూస్ట్‌ను కొనుగోలు చేయండి"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ఇప్పుడు కాదు"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"మేనేజ్ చేయండి"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"నెట్‌వర్క్ బూస్ట్‌ను కొనుగోలు చేయండి."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-th/strings.xml b/packages/CarrierDefaultApp/res/values-th/strings.xml
index 5c63bb1..3988995 100644
--- a/packages/CarrierDefaultApp/res/values-th/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-th/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"เครือข่ายที่คุณพยายามเข้าร่วมมีปัญหาด้านความปลอดภัย"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"ตัวอย่างเช่น หน้าเข้าสู่ระบบอาจไม่ใช่ขององค์กรที่แสดงไว้"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"ดำเนินการต่อผ่านเบราว์เซอร์"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"เพิ่มประสิทธิภาพเครือข่าย"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s แนะนำให้เพิ่มอินเทอร์เน็ตมือถือ"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"ซื้อการเพิ่มประสิทธิภาพเครือข่ายเพื่อการทำงานที่ดียิ่งขึ้น"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ไว้ทีหลัง"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"จัดการ"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"ซื้อการเพิ่มประสิทธิภาพเครือข่าย"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-tl/strings.xml b/packages/CarrierDefaultApp/res/values-tl/strings.xml
index 9e320c8..6375a93 100644
--- a/packages/CarrierDefaultApp/res/values-tl/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tl/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"May mga isyu sa seguridad ang network na sinusubukan mong salihan."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Halimbawa, maaaring hindi pag-aari ng ipinapakitang organisasyon ang page ng login."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Magpatuloy pa rin sa pamamagitan ng browser"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Network boost"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"Nagrerekomenda ng data boost ang %s"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Bumili ng network boost para sa mas mahusay na performance"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Huwag muna"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Pamahalaan"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Bumili ng network boost."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-tr/strings.xml b/packages/CarrierDefaultApp/res/values-tr/strings.xml
index 63616cc..72808e68 100644
--- a/packages/CarrierDefaultApp/res/values-tr/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-tr/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Katılmaya çalıştığınız ağda güvenlik sorunları var."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Örneğin, giriş sayfası, gösterilen kuruluşa ait olmayabilir."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Yine de tarayıcıyla devam et"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Ağ güçlendirme"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s veri güçlendirme öneriyor"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Daha iyi performans için ağ güçlendirme satın alın"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Şimdi değil"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Yönet"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Ağ güçlendirme satın alın."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-uk/strings.xml b/packages/CarrierDefaultApp/res/values-uk/strings.xml
index bd44327..2b30164 100644
--- a/packages/CarrierDefaultApp/res/values-uk/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uk/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"У мережі, до якої ви намагаєтеся під’єднатись, є проблеми з безпекою."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Наприклад, сторінка входу може не належати вказаній організації."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Усе одно продовжити у веб-переглядачі"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Покращення характеристик мережі"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s рекомендує придбати покращення характеристик мережі"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Придбайте покращення характеристик мережі, щоб підвищити продуктивність"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Не зараз"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Керувати"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Придбайте покращення характеристик мережі."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-ur/strings.xml b/packages/CarrierDefaultApp/res/values-ur/strings.xml
index 3294cf5..104f806 100644
--- a/packages/CarrierDefaultApp/res/values-ur/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-ur/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"آپ جس نیٹ ورک میں شامل ہونے کی کوشش کر رہے ہیں، اس میں سیکیورٹی کے مسائل ہیں۔"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"مثال کے طور پر ہو سکتا ہے کہ لاگ ان صفحہ دکھائی گئی تنظیم سے تعلق نہ رکھتا ہو۔"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"براؤزر کے ذریعے بہرحال جاری رکھیں"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"نیٹ ورک بوسٹ"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"‏%s ڈیٹا بوسٹ کی تجویز کرتا ہے"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"بہتر کارکردگی کے لیے نیٹ ورک بوسٹ خریدیں"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"ابھی نہیں"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"نظم کریں"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"نیٹ ورک بوسٹ خریدیں۔"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-uz/strings.xml b/packages/CarrierDefaultApp/res/values-uz/strings.xml
index 4eca545..a4eb377 100644
--- a/packages/CarrierDefaultApp/res/values-uz/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-uz/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Siz ulanmoqchi bo‘lgan tarmoqda xavfsizlik bilan bog‘liq muammolar mavjud."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Masalan, tizimga kirish sahifasi ko‘rsatilgan tashkilotga tegishli bo‘lmasligi mumkin."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Brauzerda davom ettirish"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Tarmoq kuchaytirgichi"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s maʼlumotlarni kuchaytirishni tavsiya qiladi"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Yaxshiroq ishlash uchun tarmoq kuchaytirgichini sotib oling"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Hozir emas"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Boshqarish"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Tarmoq kuchaytirgichini sotib oling."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-vi/strings.xml b/packages/CarrierDefaultApp/res/values-vi/strings.xml
index d8f15e8..6f6a314 100644
--- a/packages/CarrierDefaultApp/res/values-vi/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-vi/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"Mạng mà bạn đang cố gắng tham gia có vấn đề về bảo mật."</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"Ví dụ: trang đăng nhập có thể không thuộc về tổ chức được hiển thị."</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"Vẫn tiếp tục qua trình duyệt"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"Tăng tốc độ mạng"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s đề xuất tăng tốc độ dữ liệu"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"Mua gói tăng tốc độ mạng để cải thiện hiệu suất"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"Để sau"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"Quản lý"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"Mua gói tăng tốc độ mạng."</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
index 4ce19f5..a92278d 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rCN/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"您尝试加入的网络存在安全问题。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登录页面可能并不属于页面上显示的单位。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍然通过浏览器继续操作"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"网络加速"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"%s建议提升移动网络速度"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"购买网络加速服务,以获得更好的效果"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"以后再说"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"管理"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"购买网络加速服务。"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
index f019beb..959b497 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rHK/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"您正在嘗試加入的網絡有安全性問題。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登入頁面可能並不屬於所顯示的機構。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"網絡強化"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"「%s」建議購買流動數據強化"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"購買網絡強化以提升效能"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"暫時不要"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"管理"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"購買網絡強化。"</string>
 </resources>
diff --git a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
index 32724b5..29acabf 100644
--- a/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
+++ b/packages/CarrierDefaultApp/res/values-zh-rTW/strings.xml
@@ -14,16 +14,10 @@
     <string name="ssl_error_warning" msgid="3127935140338254180">"你嘗試加入的網路有安全性問題。"</string>
     <string name="ssl_error_example" msgid="6188711843183058764">"例如,登入網頁中顯示的機構可能並非該網頁實際隸屬的機構。"</string>
     <string name="ssl_error_continue" msgid="1138548463994095584">"仍要透過瀏覽器繼續操作"</string>
-    <!-- no translation found for network_boost_notification_channel (5430986172506159199) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_title (8226368121348880044) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_detail (3812434025544196192) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_not_now (4129218252146702688) -->
-    <skip />
-    <!-- no translation found for network_boost_notification_button_manage (1511552684142641182) -->
-    <skip />
-    <!-- no translation found for slice_purchase_app_label (915654761797446390) -->
-    <skip />
+    <string name="network_boost_notification_channel" msgid="5430986172506159199">"網路增強"</string>
+    <string name="network_boost_notification_title" msgid="8226368121348880044">"「%s」建議購買行動數據增強"</string>
+    <string name="network_boost_notification_detail" msgid="3812434025544196192">"購買網路增強以提升效能"</string>
+    <string name="network_boost_notification_button_not_now" msgid="4129218252146702688">"暫時不要"</string>
+    <string name="network_boost_notification_button_manage" msgid="1511552684142641182">"管理"</string>
+    <string name="slice_purchase_app_label" msgid="915654761797446390">"購買網路增強。"</string>
 </resources>
diff --git a/packages/CredentialManager/res/drawable/ic_face.xml b/packages/CredentialManager/res/drawable/ic_face.xml
deleted file mode 100644
index 16fe144..0000000
--- a/packages/CredentialManager/res/drawable/ic_face.xml
+++ /dev/null
@@ -1,30 +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.
-  -->
-
-<!--TODO: Testing only icon. Remove later. -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        tools:ignore="VectorPath"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="#808080"
-        android:pathData="M9.025,14.275Q8.5,14.275 8.125,13.9Q7.75,13.525 7.75,13Q7.75,12.475 8.125,12.1Q8.5,11.725 9.025,11.725Q9.575,11.725 9.938,12.1Q10.3,12.475 10.3,13Q10.3,13.525 9.938,13.9Q9.575,14.275 9.025,14.275ZM14.975,14.275Q14.425,14.275 14.062,13.9Q13.7,13.525 13.7,13Q13.7,12.475 14.062,12.1Q14.425,11.725 14.975,11.725Q15.5,11.725 15.875,12.1Q16.25,12.475 16.25,13Q16.25,13.525 15.875,13.9Q15.5,14.275 14.975,14.275ZM12,19.925Q15.325,19.925 17.625,17.625Q19.925,15.325 19.925,12Q19.925,11.4 19.85,10.85Q19.775,10.3 19.575,9.775Q19.05,9.9 18.538,9.962Q18.025,10.025 17.45,10.025Q15.2,10.025 13.188,9.062Q11.175,8.1 9.775,6.375Q8.975,8.3 7.5,9.712Q6.025,11.125 4.075,11.85Q4.075,11.9 4.075,11.925Q4.075,11.95 4.075,12Q4.075,15.325 6.375,17.625Q8.675,19.925 12,19.925ZM12,22.2Q9.9,22.2 8.038,21.4Q6.175,20.6 4.788,19.225Q3.4,17.85 2.6,15.988Q1.8,14.125 1.8,12Q1.8,9.875 2.6,8.012Q3.4,6.15 4.788,4.775Q6.175,3.4 8.038,2.6Q9.9,1.8 12,1.8Q14.125,1.8 15.988,2.6Q17.85,3.4 19.225,4.775Q20.6,6.15 21.4,8.012Q22.2,9.875 22.2,12Q22.2,14.125 21.4,15.988Q20.6,17.85 19.225,19.225Q17.85,20.6 15.988,21.4Q14.125,22.2 12,22.2Z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_manage_accounts.xml b/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
deleted file mode 100644
index adad2f1..0000000
--- a/packages/CredentialManager/res/drawable/ic_manage_accounts.xml
+++ /dev/null
@@ -1,30 +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.
-  -->
-
-<!--TODO: Testing only icon. Remove later. -->
-
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        tools:ignore="VectorPath"
-        android:width="24dp"
-        android:height="24dp"
-        android:viewportWidth="24"
-        android:viewportHeight="24"
-        android:tint="?attr/colorControlNormal">
-    <path
-        android:fillColor="#808080"
-        android:pathData="M16.1,21.2 L15.775,19.675Q15.5,19.55 15.25,19.425Q15,19.3 14.75,19.1L13.275,19.575L12.2,17.75L13.375,16.725Q13.325,16.4 13.325,16.112Q13.325,15.825 13.375,15.5L12.2,14.475L13.275,12.65L14.75,13.1Q15,12.925 15.25,12.787Q15.5,12.65 15.775,12.55L16.1,11.025H18.25L18.55,12.55Q18.825,12.65 19.075,12.8Q19.325,12.95 19.575,13.15L21.05,12.65L22.125,14.525L20.95,15.55Q21.025,15.825 21.013,16.137Q21,16.45 20.95,16.725L22.125,17.75L21.05,19.575L19.575,19.1Q19.325,19.3 19.075,19.425Q18.825,19.55 18.55,19.675L18.25,21.2ZM1.8,20.3V17.3Q1.8,16.375 2.275,15.613Q2.75,14.85 3.5,14.475Q4.775,13.825 6.425,13.362Q8.075,12.9 10,12.9Q10.2,12.9 10.4,12.9Q10.6,12.9 10.775,12.95Q9.925,14.85 10.062,16.738Q10.2,18.625 11.4,20.3ZM17.175,18.075Q17.975,18.075 18.55,17.487Q19.125,16.9 19.125,16.1Q19.125,15.3 18.55,14.725Q17.975,14.15 17.175,14.15Q16.375,14.15 15.788,14.725Q15.2,15.3 15.2,16.1Q15.2,16.9 15.788,17.487Q16.375,18.075 17.175,18.075ZM10,11.9Q8.25,11.9 7.025,10.662Q5.8,9.425 5.8,7.7Q5.8,5.95 7.025,4.725Q8.25,3.5 10,3.5Q11.75,3.5 12.975,4.725Q14.2,5.95 14.2,7.7Q14.2,9.425 12.975,10.662Q11.75,11.9 10,11.9Z"/>
-</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
new file mode 100644
index 0000000..388d098
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding.png
Binary files differ
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
new file mode 100644
index 0000000..9e4f424
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_device.xml
@@ -0,0 +1,32 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="21dp"
+    android:height="17dp"
+    android:viewportWidth="21"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 4 2.941 L 20 2.941 L 20 0.941 L 4 0.941 C 2.9 0.941 2 1.841 2 2.941 L 2 13.941 L 0 13.941 L 0 16.941 L 11 16.941 L 11 13.941 L 4 13.941 L 4 2.941 Z M 20 4.941 L 14 4.941 C 13.45 4.941 13 5.391 13 5.941 L 13 15.941 C 13 16.491 13.45 16.941 14 16.941 L 20 16.941 C 20.55 16.941 21 16.491 21 15.941 L 21 5.941 C 21 5.391 20.55 4.941 20 4.941 Z M 15 13.941 L 19 13.941 L 19 6.941 L 15 6.941 L 15 13.941 Z"
+        android:fillColor="#5f6368"
+        android:strokeWidth="1"
+        android:fillType="evenOdd"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
new file mode 100644
index 0000000..b6ee4f9
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_fingerprint.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="19dp"
+    android:height="21dp"
+    android:viewportWidth="19"
+    android:viewportHeight="21">
+    <path
+        android:name="path"
+        android:pathData="M 0.25 8.591 C 0.133 8.508 0.058 8.408 0.025 8.291 C 0.008 8.158 0.05 8.025 0.15 7.891 C 1.183 6.475 2.475 5.375 4.025 4.591 C 5.592 3.808 7.258 3.416 9.025 3.416 C 10.792 3.416 12.458 3.8 14.025 4.566 C 15.592 5.316 16.9 6.408 17.95 7.841 C 18.067 7.991 18.1 8.125 18.05 8.241 C 18.017 8.358 17.95 8.458 17.85 8.541 C 17.75 8.625 17.633 8.666 17.5 8.666 C 17.367 8.65 17.25 8.575 17.15 8.441 C 16.233 7.141 15.05 6.15 13.6 5.466 C 12.167 4.766 10.642 4.416 9.025 4.416 C 7.408 4.416 5.892 4.766 4.475 5.466 C 3.058 6.15 1.883 7.141 0.95 8.441 C 0.85 8.591 0.733 8.675 0.6 8.691 C 0.467 8.708 0.35 8.675 0.25 8.591 Z M 11.85 20.916 C 10.117 20.483 8.7 19.625 7.6 18.341 C 6.5 17.041 5.95 15.458 5.95 13.591 C 5.95 12.758 6.25 12.058 6.85 11.491 C 7.45 10.925 8.175 10.641 9.025 10.641 C 9.875 10.641 10.6 10.925 11.2 11.491 C 11.8 12.058 12.1 12.758 12.1 13.591 C 12.1 14.141 12.308 14.608 12.725 14.991 C 13.142 15.358 13.633 15.541 14.2 15.541 C 14.767 15.541 15.25 15.358 15.65 14.991 C 16.05 14.608 16.25 14.141 16.25 13.591 C 16.25 11.658 15.542 10.033 14.125 8.716 C 12.708 7.4 11.017 6.741 9.05 6.741 C 7.083 6.741 5.392 7.4 3.975 8.716 C 2.558 10.033 1.85 11.65 1.85 13.566 C 1.85 13.966 1.883 14.466 1.95 15.066 C 2.033 15.666 2.217 16.366 2.5 17.166 C 2.55 17.316 2.542 17.45 2.475 17.566 C 2.425 17.683 2.333 17.766 2.2 17.816 C 2.067 17.866 1.933 17.866 1.8 17.816 C 1.683 17.75 1.6 17.65 1.55 17.516 C 1.3 16.866 1.117 16.225 1 15.591 C 0.9 14.941 0.85 14.275 0.85 13.591 C 0.85 11.375 1.65 9.516 3.25 8.016 C 4.867 6.516 6.792 5.766 9.025 5.766 C 11.275 5.766 13.208 6.516 14.825 8.016 C 16.442 9.516 17.25 11.375 17.25 13.591 C 17.25 14.425 16.95 15.125 16.35 15.691 C 15.767 16.241 15.05 16.516 14.2 16.516 C 13.35 16.516 12.617 16.241 12 15.691 C 11.4 15.125 11.1 14.425 11.1 13.591 C 11.1 13.041 10.892 12.583 10.475 12.216 C 10.075 11.833 9.592 11.641 9.025 11.641 C 8.458 11.641 7.967 11.833 7.55 12.216 C 7.15 12.583 6.95 13.041 6.95 13.591 C 6.95 15.208 7.425 16.558 8.375 17.641 C 9.342 18.725 10.583 19.483 12.1 19.916 C 12.25 19.966 12.35 20.05 12.4 20.166 C 12.45 20.283 12.458 20.408 12.425 20.541 C 12.392 20.658 12.325 20.758 12.225 20.841 C 12.125 20.925 12 20.95 11.85 20.916 Z M 3.5 3.366 C 3.367 3.45 3.233 3.475 3.1 3.441 C 2.967 3.391 2.867 3.3 2.8 3.166 C 2.733 3.033 2.717 2.916 2.75 2.816 C 2.783 2.7 2.867 2.6 3 2.516 C 3.933 2.016 4.908 1.633 5.925 1.366 C 6.942 1.1 7.975 0.966 9.025 0.966 C 10.092 0.966 11.133 1.1 12.15 1.366 C 13.167 1.616 14.15 1.983 15.1 2.466 C 15.25 2.55 15.333 2.65 15.35 2.766 C 15.383 2.883 15.375 3 15.325 3.116 C 15.275 3.233 15.192 3.325 15.075 3.391 C 14.958 3.458 14.817 3.45 14.65 3.366 C 13.767 2.916 12.85 2.575 11.9 2.341 C 10.967 2.091 10.008 1.966 9.025 1.966 C 8.058 1.966 7.108 2.083 6.175 2.316 C 5.242 2.533 4.35 2.883 3.5 3.366 Z M 6.45 20.566 C 5.467 19.533 4.708 18.483 4.175 17.416 C 3.658 16.333 3.4 15.058 3.4 13.591 C 3.4 12.075 3.95 10.8 5.05 9.766 C 6.15 8.716 7.475 8.191 9.025 8.191 C 10.575 8.191 11.908 8.716 13.025 9.766 C 14.142 10.8 14.7 12.075 14.7 13.591 C 14.7 13.741 14.65 13.866 14.55 13.966 C 14.467 14.05 14.35 14.091 14.2 14.091 C 14.067 14.091 13.95 14.05 13.85 13.966 C 13.75 13.866 13.7 13.741 13.7 13.591 C 13.7 12.341 13.233 11.3 12.3 10.466 C 11.383 9.616 10.292 9.191 9.025 9.191 C 7.758 9.191 6.667 9.616 5.75 10.466 C 4.85 11.3 4.4 12.341 4.4 13.591 C 4.4 14.941 4.633 16.091 5.1 17.041 C 5.567 17.975 6.25 18.916 7.15 19.866 C 7.25 19.966 7.3 20.083 7.3 20.216 C 7.3 20.35 7.25 20.466 7.15 20.566 C 7.05 20.666 6.933 20.716 6.8 20.716 C 6.667 20.716 6.55 20.666 6.45 20.566 Z M 14 18.866 C 12.517 18.866 11.225 18.366 10.125 17.366 C 9.042 16.366 8.5 15.108 8.5 13.591 C 8.5 13.458 8.542 13.341 8.625 13.241 C 8.725 13.141 8.85 13.091 9 13.091 C 9.15 13.091 9.267 13.141 9.35 13.241 C 9.45 13.341 9.5 13.458 9.5 13.591 C 9.5 14.841 9.95 15.866 10.85 16.666 C 11.75 17.466 12.8 17.866 14 17.866 C 14.1 17.866 14.242 17.858 14.425 17.841 C 14.608 17.825 14.8 17.8 15 17.766 C 15.15 17.733 15.275 17.758 15.375 17.841 C 15.492 17.908 15.567 18.016 15.6 18.166 C 15.633 18.3 15.608 18.416 15.525 18.516 C 15.442 18.616 15.333 18.683 15.2 18.716 C 14.9 18.8 14.633 18.85 14.4 18.866 L 14 18.866 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
new file mode 100644
index 0000000..61800f1
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_passkeys_onboarding_password.xml
@@ -0,0 +1,31 @@
+<!--
+  ~ 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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="20dp"
+    android:height="17dp"
+    android:viewportWidth="20"
+    android:viewportHeight="17">
+    <path
+        android:name="path"
+        android:pathData="M 2 16.941 C 1.45 16.941 0.975 16.75 0.575 16.366 C 0.192 15.966 0 15.491 0 14.941 L 0 2.941 C 0 2.391 0.192 1.925 0.575 1.541 C 0.975 1.141 1.45 0.941 2 0.941 L 18 0.941 C 18.55 0.941 19.017 1.141 19.4 1.541 C 19.8 1.925 20 2.391 20 2.941 L 20 14.941 C 20 15.491 19.8 15.966 19.4 16.366 C 19.017 16.75 18.55 16.941 18 16.941 L 2 16.941 Z M 4.5 11.941 L 5.65 11.941 L 5.65 5.941 L 4.75 5.941 L 3 7.191 L 3.6 8.091 L 4.5 7.441 L 4.5 11.941 Z M 7.6 11.941 L 11.5 11.941 L 11.5 10.941 L 9.15 10.941 L 9.1 10.891 C 9.45 10.558 9.733 10.275 9.95 10.041 C 10.183 9.808 10.367 9.625 10.5 9.491 C 10.8 9.191 11.025 8.891 11.175 8.591 C 11.325 8.291 11.4 7.975 11.4 7.641 C 11.4 7.158 11.217 6.758 10.85 6.441 C 10.483 6.108 10.017 5.941 9.45 5.941 C 9.017 5.941 8.625 6.066 8.275 6.316 C 7.925 6.566 7.683 6.891 7.55 7.291 L 8.55 7.691 C 8.633 7.475 8.75 7.308 8.9 7.191 C 9.067 7.058 9.25 6.991 9.45 6.991 C 9.7 6.991 9.9 7.058 10.05 7.191 C 10.217 7.325 10.3 7.491 10.3 7.691 C 10.3 7.875 10.267 8.05 10.2 8.216 C 10.133 8.366 9.983 8.558 9.75 8.791 L 8.95 9.591 C 8.6 9.941 8.15 10.391 7.6 10.941 L 7.6 11.941 Z M 15 11.941 C 15.6 11.941 16.083 11.775 16.45 11.441 C 16.817 11.108 17 10.675 17 10.141 C 17 9.841 16.917 9.575 16.75 9.341 C 16.583 9.108 16.35 8.925 16.05 8.791 L 16.05 8.741 C 16.283 8.608 16.467 8.441 16.6 8.241 C 16.733 8.025 16.8 7.775 16.8 7.491 C 16.8 7.041 16.625 6.675 16.275 6.391 C 15.925 6.091 15.483 5.941 14.95 5.941 C 14.533 5.941 14.142 6.066 13.775 6.316 C 13.425 6.55 13.2 6.841 13.1 7.191 L 14.1 7.591 C 14.167 7.391 14.275 7.233 14.425 7.116 C 14.575 7 14.75 6.941 14.95 6.941 C 15.167 6.941 15.342 7.008 15.475 7.141 C 15.625 7.258 15.7 7.408 15.7 7.591 C 15.7 7.825 15.617 8.008 15.45 8.141 C 15.283 8.275 15.067 8.341 14.8 8.341 L 14.35 8.341 L 14.35 9.341 L 14.85 9.341 C 15.183 9.341 15.442 9.408 15.625 9.541 C 15.808 9.675 15.9 9.858 15.9 10.091 C 15.9 10.308 15.808 10.5 15.625 10.666 C 15.442 10.816 15.233 10.891 15 10.891 C 14.717 10.891 14.5 10.833 14.35 10.716 C 14.2 10.583 14.067 10.358 13.95 10.041 L 12.95 10.441 C 13.067 10.925 13.3 11.3 13.65 11.566 C 14.017 11.816 14.467 11.941 15 11.941 Z M 2 14.941 L 18 14.941 L 18 2.941 L 2 2.941 L 2 14.941 Z M 2 14.941 L 2 2.941 L 2 14.941 Z"
+        android:fillColor="#5e6144"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values-af/strings.xml b/packages/CredentialManager/res/values-af/strings.xml
index 377c13f..a548219 100644
--- a/packages/CredentialManager/res/values-af/strings.xml
+++ b/packages/CredentialManager/res/values-af/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Stoor in ’n ander plek"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Gebruik ’n ander toestel"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Stoor op ’n ander toestel"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"’n Maklike manier om veilig aan te meld"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik jou vingerafdruk, gesig of skermslot om aan te meld met ’n unieke wagwoordsleutel wat nie vergeet of gesteel kan word nie. Kom meer te wete"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Kies waar om <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Kies waar om <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"skep jou wagwoordsleutels"</string>
     <string name="save_your_password" msgid="6597736507991704307">"stoor jou wagwoord"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"stoor jou aanmeldinligting"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Stel ’n verstekwagwoordbestuurder om jou wagwoorde en wagwoordsleutels te stoor, en meld volgende keer vinniger aan."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Skep ’n wagwoordsleutel in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Stoor jou wagwoord in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Stoor jou aanmeldinligting in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"wagwoordsleutel"</string>
     <string name="password" msgid="6738570945182936667">"wagwoord"</string>
     <string name="sign_ins" msgid="4710739369149469208">"aanmeldings"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Skep wagwoordsleutel in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Stoor wagwoord in"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Stoor aanmelding in"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Skep ’n wagwoordsleutel op ’n ander toestel?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gebruik <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> vir al jou aanmeldings?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Hierdie wagwoordbestuurder sal jou wagwoorde en wagwoordsleutels berg om jou te help om maklik aan te meld."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Stel as verstek"</string>
     <string name="use_once" msgid="9027366575315399714">"Gebruik een keer"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wagwoorde, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> wagwoordsleutels"</string>
diff --git a/packages/CredentialManager/res/values-am/strings.xml b/packages/CredentialManager/res/values-am/strings.xml
index b80fe2c..6fdc696 100644
--- a/packages/CredentialManager/res/values-am/strings.xml
+++ b/packages/CredentialManager/res/values-am/strings.xml
@@ -3,45 +3,34 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- no translation found for app_name (4539824758261855508) -->
     <skip />
-    <!-- no translation found for string_cancel (6369133483981306063) -->
-    <skip />
-    <!-- no translation found for string_continue (1346732695941131882) -->
-    <skip />
-    <!-- no translation found for string_create_in_another_place (1033635365843437603) -->
-    <skip />
-    <!-- no translation found for string_save_to_another_place (7590325934591079193) -->
-    <skip />
-    <!-- no translation found for string_use_another_device (8754514926121520445) -->
-    <skip />
+    <string name="string_cancel" msgid="6369133483981306063">"ይቅር"</string>
+    <string name="string_continue" msgid="1346732695941131882">"ቀጥል"</string>
+    <string name="string_create_in_another_place" msgid="1033635365843437603">"በሌላ ቦታ ውስጥ ይፍጠሩ"</string>
+    <string name="string_save_to_another_place" msgid="7590325934591079193">"ወደ ሌላ ቦታ ያስቀምጡ"</string>
+    <string name="string_use_another_device" msgid="8754514926121520445">"ሌላ መሣሪያ ይጠቀሙ"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ወደ ሌላ መሣሪያ ያስቀምጡ"</string>
-    <!-- no translation found for passkey_creation_intro_title (402553911484409884) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
-    <!-- no translation found for passkey_creation_intro_body (7493320456005579290) -->
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
     <skip />
-    <!-- no translation found for choose_provider_title (7245243990139698508) -->
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"የት <xliff:g id="CREATETYPES">%1$s</xliff:g> እንደሚሆን ይምረጡ"</string>
     <!-- no translation found for create_your_passkeys (8901224153607590596) -->
     <skip />
-    <!-- no translation found for save_your_password (6597736507991704307) -->
-    <skip />
-    <!-- no translation found for save_your_sign_in_info (7213978049817076882) -->
-    <skip />
+    <string name="save_your_password" msgid="6597736507991704307">"የይለፍ ቃልዎን ያስቀምጡ"</string>
+    <string name="save_your_sign_in_info" msgid="7213978049817076882">"የመግቢያ መረጃዎን ያስቀምጡ"</string>
     <!-- no translation found for choose_provider_body (8045759834416308059) -->
     <skip />
-    <!-- no translation found for choose_create_option_passkey_title (4146408187146573131) -->
-    <skip />
-    <!-- no translation found for choose_create_option_password_title (8812546498357380545) -->
-    <skip />
-    <!-- no translation found for choose_create_option_sign_in_title (6318246378475961834) -->
-    <skip />
-    <!-- no translation found for choose_create_option_description (4419171903963100257) -->
-    <skip />
-    <!-- no translation found for passkey (632353688396759522) -->
-    <skip />
-    <!-- no translation found for password (6738570945182936667) -->
-    <skip />
-    <!-- no translation found for sign_ins (4710739369149469208) -->
-    <skip />
+    <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"በ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ውስጥ የይለፍ ቁልፍ ይፈጠር?"</string>
+    <string name="choose_create_option_password_title" msgid="8812546498357380545">"የይለፍ ቃልዎ ወደ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ይቀመጥ?"</string>
+    <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"የመግቢያ መረጃዎ ወደ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ይቀመጥ?"</string>
+    <string name="choose_create_option_description" msgid="4419171903963100257">"የእርስዎን <xliff:g id="APPDOMAINNAME">%1$s</xliff:g> <xliff:g id="TYPE">%2$s</xliff:g> በማንኛውም መሣሪያ ላይ መጠቀም ይችላሉ። ለ<xliff:g id="CREATEINFODISPLAYNAME">%4$s</xliff:g> ወደ <xliff:g id="PROVIDERINFODISPLAYNAME">%3$s</xliff:g> ተቀምጧል"</string>
+    <string name="passkey" msgid="632353688396759522">"የይለፍ ቁልፍ"</string>
+    <string name="password" msgid="6738570945182936667">"የይለፍ ቃል"</string>
+    <string name="sign_ins" msgid="4710739369149469208">"መግቢያዎች"</string>
     <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
     <skip />
     <!-- no translation found for save_password_to_title (3450480045270186421) -->
@@ -50,54 +39,31 @@
     <skip />
     <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
     <skip />
-    <!-- no translation found for use_provider_for_all_title (4201020195058980757) -->
-    <skip />
+    <string name="use_provider_for_all_title" msgid="4201020195058980757">"ለሁሉም መግቢያዎችዎ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ን ይጠቀሙ?"</string>
     <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
     <skip />
-    <!-- no translation found for set_as_default (4415328591568654603) -->
-    <skip />
-    <!-- no translation found for use_once (9027366575315399714) -->
-    <skip />
-    <!-- no translation found for more_options_usage_passwords_passkeys (4794903978126339473) -->
-    <skip />
-    <!-- no translation found for more_options_usage_passwords (1632047277723187813) -->
-    <skip />
-    <!-- no translation found for more_options_usage_passkeys (5390320437243042237) -->
-    <skip />
+    <string name="set_as_default" msgid="4415328591568654603">"እንደ ነባሪ ያዋቅሩ"</string>
+    <string name="use_once" msgid="9027366575315399714">"አንዴ ይጠቀሙ"</string>
+    <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> የይለፍ ቃሎች፣ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> የይለፍ ቁልፎች"</string>
+    <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> የይለፍ ቃሎች"</string>
+    <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> የይለፍ ቁልፎች"</string>
     <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
     <skip />
-    <!-- no translation found for another_device (5147276802037801217) -->
-    <skip />
-    <!-- no translation found for other_password_manager (565790221427004141) -->
-    <skip />
-    <!-- no translation found for close_sheet (1393792015338908262) -->
-    <skip />
-    <!-- no translation found for accessibility_back_arrow_button (3233198183497842492) -->
-    <skip />
-    <!-- no translation found for get_dialog_title_use_passkey_for (6236608872708021767) -->
-    <skip />
-    <!-- no translation found for get_dialog_title_use_sign_in_for (5283099528915572980) -->
-    <skip />
-    <!-- no translation found for get_dialog_title_choose_sign_in_for (1361715440877613701) -->
-    <skip />
-    <!-- no translation found for get_dialog_use_saved_passkey_for (4618100798664888512) -->
-    <skip />
-    <!-- no translation found for get_dialog_button_label_no_thanks (8114363019023838533) -->
-    <skip />
-    <!-- no translation found for get_dialog_button_label_continue (6446201694794283870) -->
-    <skip />
-    <!-- no translation found for get_dialog_title_sign_in_options (2092876443114893618) -->
-    <skip />
-    <!-- no translation found for get_dialog_heading_for_username (3456868514554204776) -->
-    <skip />
-    <!-- no translation found for get_dialog_heading_locked_password_managers (8911514851762862180) -->
-    <skip />
-    <!-- no translation found for locked_credential_entry_label_subtext (9213450912991988691) -->
-    <skip />
-    <!-- no translation found for get_dialog_heading_manage_sign_ins (3522556476480676782) -->
-    <skip />
-    <!-- no translation found for get_dialog_heading_from_another_device (1166697017046724072) -->
-    <skip />
-    <!-- no translation found for get_dialog_option_headline_use_a_different_device (8201578814988047549) -->
-    <skip />
+    <string name="another_device" msgid="5147276802037801217">"ሌላ መሣሪያ"</string>
+    <string name="other_password_manager" msgid="565790221427004141">"ሌሎች የይለፍ ቃል አስተዳዳሪዎች"</string>
+    <string name="close_sheet" msgid="1393792015338908262">"ሉህን ዝጋ"</string>
+    <string name="accessibility_back_arrow_button" msgid="3233198183497842492">"ወደ ቀዳሚው ገፅ ይመለሱ"</string>
+    <string name="get_dialog_title_use_passkey_for" msgid="6236608872708021767">"የተቀመጠ የይለፍ ቁልፍዎን ለ<xliff:g id="APP_NAME">%1$s</xliff:g> ይጠቀሙ?"</string>
+    <string name="get_dialog_title_use_sign_in_for" msgid="5283099528915572980">"የተቀመጠ መግቢያዎን ለ<xliff:g id="APP_NAME">%1$s</xliff:g> ይጠቀሙ?"</string>
+    <string name="get_dialog_title_choose_sign_in_for" msgid="1361715440877613701">"ለ<xliff:g id="APP_NAME">%1$s</xliff:g> የተቀመጠ መግቢያ ይጠቀሙ"</string>
+    <string name="get_dialog_use_saved_passkey_for" msgid="4618100798664888512">"በሌላ መንገድ ይግቡ"</string>
+    <string name="get_dialog_button_label_no_thanks" msgid="8114363019023838533">"አይ አመሰግናለሁ"</string>
+    <string name="get_dialog_button_label_continue" msgid="6446201694794283870">"ቀጥል"</string>
+    <string name="get_dialog_title_sign_in_options" msgid="2092876443114893618">"የመግቢያ አማራጮች"</string>
+    <string name="get_dialog_heading_for_username" msgid="3456868514554204776">"ለ<xliff:g id="USERNAME">%1$s</xliff:g>"</string>
+    <string name="get_dialog_heading_locked_password_managers" msgid="8911514851762862180">"የተቆለፉ የሚስጥር ቁልፍ አስተዳዳሪዎች"</string>
+    <string name="locked_credential_entry_label_subtext" msgid="9213450912991988691">"ለመክፈት መታ ያድርጉ"</string>
+    <string name="get_dialog_heading_manage_sign_ins" msgid="3522556476480676782">"መግቢያዎችን ያስተዳድሩ"</string>
+    <string name="get_dialog_heading_from_another_device" msgid="1166697017046724072">"ከሌላ መሣሪያ"</string>
+    <string name="get_dialog_option_headline_use_a_different_device" msgid="8201578814988047549">"የተለየ መሣሪያ ይጠቀሙ"</string>
 </resources>
diff --git a/packages/CredentialManager/res/values-ar/strings.xml b/packages/CredentialManager/res/values-ar/strings.xml
index a5c85c5..6a2c9a1 100644
--- a/packages/CredentialManager/res/values-ar/strings.xml
+++ b/packages/CredentialManager/res/values-ar/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"مدير بيانات الاعتماد"</string>
     <string name="string_cancel" msgid="6369133483981306063">"إلغاء"</string>
     <string name="string_continue" msgid="1346732695941131882">"متابعة"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"الإنشاء في مكان آخر"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"الحفظ في مكان آخر"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"استخدام جهاز آخر"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"الحفظ على جهاز آخر"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"طريقة بسيطة لتسجيل الدخول بأمان"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"استخدِم بصمة إصبعك أو وجهك أو قفل الشاشة لتسجيل الدخول باستخدام مفتاح مرور فريد لا يمكن نسيانه أو سرقته. مزيد من المعلومات"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"اختيار مكان <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"إنشاء مفاتيح مرورك"</string>
     <string name="save_your_password" msgid="6597736507991704307">"حفظ كلمة المرور"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"حفظ معلومات تسجيل الدخول"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"يمكنك ضبط خدمة تلقائية لـ \"مدير كلمات المرور\" من أجل حفظ كلمات المرور ومفاتيح المرور وتسجيل الدخول بشكل أسرع في المرة القادمة."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"هل تريد إنشاء مفتاح مرور في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"هل تريد حفظ كلمة مرورك في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"هل تريد حفظ معلومات تسجيل الدخول في \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"؟"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"مفتاح مرور"</string>
     <string name="password" msgid="6738570945182936667">"كلمة المرور"</string>
     <string name="sign_ins" msgid="4710739369149469208">"عمليات تسجيل الدخول"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"إنشاء مفتاح مرور في"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"حفظ كلمة المرور في"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"حفظ معلومات تسجيل الدخول في"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"هل تريد إنشاء مفتاح مرور في جهاز آخر؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"هل تريد استخدام \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" لكل عمليات تسجيل الدخول؟"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"سيخزِّن \"مدير كلمات المرور\" هذا كلمات المرور ومفاتيح المرور لمساعدتك في تسجيل الدخول بسهولة."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ضبط الخيار كتلقائي"</string>
     <string name="use_once" msgid="9027366575315399714">"الاستخدام مرة واحدة"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"عدد كلمات المرور هو <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>، و عدد مفاتيح المرور هو <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"عدد كلمات المرور: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"عدد مفاتيح المرور: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"مفتاح مرور"</string>
     <string name="another_device" msgid="5147276802037801217">"جهاز آخر"</string>
     <string name="other_password_manager" msgid="565790221427004141">"خدمات مدراء كلمات المرور الأخرى"</string>
     <string name="close_sheet" msgid="1393792015338908262">"إغلاق ورقة البيانات"</string>
diff --git a/packages/CredentialManager/res/values-as/strings.xml b/packages/CredentialManager/res/values-as/strings.xml
index 4d0ba68..7d3b5f8 100644
--- a/packages/CredentialManager/res/values-as/strings.xml
+++ b/packages/CredentialManager/res/values-as/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য ঠাইত ছেভ কৰক"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইচ ব্যৱহাৰ কৰক"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য এটা ডিভাইচত ছেভ কৰক"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"সুৰক্ষিতভাৱে ছাইন ইন কৰাৰ এক সৰল উপায়"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"পাহৰি নোযোৱা অথবা চুৰি কৰিব নোৱৰা এটা অদ্বিতীয় পাছকী ব্যৱহাৰ কৰি ছাইন ইন কৰিবলৈ আপোনাৰ ফিংগাৰপ্ৰিণ্ট, মুখাৱয়ব অথবা স্ক্ৰীন লক ব্যৱহাৰ কৰক। অধিক জানক"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"ক’ত <xliff:g id="CREATETYPES">%1$s</xliff:g> সেয়া বাছনি কৰক"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"আপোনাৰ পাছকী সৃষ্টি কৰক"</string>
     <string name="save_your_password" msgid="6597736507991704307">"আপোনাৰ পাছৱৰ্ড ছেভ কৰক"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰক"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"আপোনাৰ পাছৱৰ্ড আৰু পাছকী ছেভ কৰিবলৈ এটা ডিফ’ল্ট পাছৱৰ্ড পৰিচালক ছেট কৰক আৰু পৰৱৰ্তী বাৰ দ্ৰুতভাৱে ছাইন ইন কৰক।"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত পাছকী সৃষ্টি কৰিবনে?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ পাছৱৰ্ড ছেভ কৰিবনে?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ত আপোনাৰ ছাইন ইন কৰাৰ তথ্য ছেভ কৰিবনে?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"পাছকী"</string>
     <string name="password" msgid="6738570945182936667">"পাছৱৰ্ড"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ছাইন-ইন"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ইয়াত পাছকী সৃষ্টি কৰক"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ইয়াত পাছৱৰ্ড ছেভ কৰক"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ইয়াত ছাইন ইন কৰাৰ তথ্য ছেভ কৰক"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"অন্য এটা ডিভাইচত এটা পাছকী সৃষ্টি কৰিবনে?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপোনাৰ আটাইবোৰ ছাইন ইনৰ বাবে <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যৱহাৰ কৰিবনে?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"আপোনাক সহজে ছাইন ইন কৰাত সহায় কৰিবলৈ এই পাছৱৰ্ড পৰিচালকটোৱে আপোনাৰ পাছৱৰ্ড আৰু পাছকী ষ্ট’ৰ কৰিব।"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ডিফ’ল্ট হিচাপে ছেট কৰক"</string>
     <string name="use_once" msgid="9027366575315399714">"এবাৰ ব্যৱহাৰ কৰক"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> টা পাছৱৰ্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> টা পাছকী"</string>
diff --git a/packages/CredentialManager/res/values-az/strings.xml b/packages/CredentialManager/res/values-az/strings.xml
index 14313f7..45af67b 100644
--- a/packages/CredentialManager/res/values-az/strings.xml
+++ b/packages/CredentialManager/res/values-az/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Başqa yerdə yadda saxlayın"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Digər cihaz istifadə edin"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Başqa cihazda yadda saxlayın"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Təhlükəsiz daxil olmağın sadə yolu"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Unutmaq və ya oğurlamaq mümkün olmayan unikal giriş açarı ilə daxil olmaq üçün barmaq izi, üz və ya ekran kilidindən istifadə edin. Ətraflı məlumat"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> üçün yer seçin"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> üçün yer seçin"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"giriş açarları yaradın"</string>
     <string name="save_your_password" msgid="6597736507991704307">"parolunuzu yadda saxlayın"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"giriş məlumatınızı yadda saxlayın"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Parollarınızı və giriş açarlarınızı saxlamaq və növbəti dəfə daha sürətli daxil olmaq üçün defolt parol meneceri ayarlayın."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində giriş açarı yaradılsın?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Giriş məlumatınız <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xidmətində saxlanılsın?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"giriş açarı"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
     <string name="sign_ins" msgid="4710739369149469208">"girişlər"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Burada giriş açarı yaradın:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Parolu burada yadda saxlayın:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Girişi burada yadda saxlayın:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Başqa cihazda giriş açarı yaradılsın?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Bütün girişlər üçün <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> istifadə edilsin?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Bu parol meneceri asanlıqla daxil olmanıza kömək etmək üçün parollarınızı və giriş açarlarınızı saxlayacaq."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Defolt olaraq seçin"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir dəfə istifadə edin"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> giriş açarı"</string>
diff --git a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
index c58ec14..7a8e40d 100644
--- a/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
+++ b/packages/CredentialManager/res/values-b+sr+Latn/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Menadžer akreditiva"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Otkaži"</string>
     <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Napravi na drugom mestu"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvaj na drugom mestu"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Koristi drugi uređaj"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvaj na drugi uređaj"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način da se bezbedno prijavljujete"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, zaključavanje licem ili zaključavanje ekrana da biste se prijavili pomoću jedinstvenog pristupnog koda koji ne može da se zaboravi ili ukrade. Saznajte više"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite lokaciju za: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite lokaciju za: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"napravite pristupne kodove"</string>
     <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte podatke o prijavljivanju"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Podesite podrazumevani menadžer lozinki da biste sačuvali lozinke i pristupne kodove i sledeći put se prijavili brže."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite da napravite pristupni kôd kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite da sačuvate lozinku kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite da sačuvate podatke o prijavljivanju kod korisnika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"pristupni kôd"</string>
     <string name="password" msgid="6738570945182936667">"lozinka"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijavljivanja"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Napravite pristupni kôd u:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Sačuvajte lozinku na:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Sačuvajte podatke o prijavljivanju na:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Želite da napravite pristupni kôd na drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite da za sva prijavljivanja koristite: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ovaj menadžer lozinki će čuvati lozinke i pristupne kodove da biste se lako prijavljivali."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Podesi kao podrazumevano"</string>
     <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, pristupnih kodova:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Pristupnih kodova: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni kôd"</string>
     <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Drugi menadžeri lozinki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zatvorite tabelu"</string>
diff --git a/packages/CredentialManager/res/values-be/strings.xml b/packages/CredentialManager/res/values-be/strings.xml
index 3c23afd..4b60244 100644
--- a/packages/CredentialManager/res/values-be/strings.xml
+++ b/packages/CredentialManager/res/values-be/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Менеджар уліковых даных"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Скасаваць"</string>
     <string name="string_continue" msgid="1346732695941131882">"Далей"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Стварыць у іншым месцы"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Захаваць у іншым месцы"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Скарыстаць іншую прыладу"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Захаваць на іншую прыладу"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Просты спосаб бяспечнага ўваходу"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Для ўваходу з унікальным ключом доступу, які нельга згубіць ці ўкрасці, можна скарыстаць адбітак пальца, распазнаванне твару ці разблакіроўку экрана. Даведацца больш"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Выберыце, дзе <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Выберыце, дзе <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"стварыць ключы доступу"</string>
     <string name="save_your_password" msgid="6597736507991704307">"захаваць пароль"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"захаваць інфармацыю пра спосаб уваходу"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Каб у далейшым хутка выконваць уваход, наладзьце стандартны менеджар пароляў для захавання вашых пароляў і ключоў доступу."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Стварыць ключ доступу ў папцы \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Захаваць пароль у папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Захаваць інфармацыю пра спосаб уваходу ў папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
     <string name="sign_ins" msgid="4710739369149469208">"спосабы ўваходу"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Дзе стварыць ключ доступу:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Куды захаваць пароль:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Куды захаваць спосаб уваходу:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Стварыць ключ доступу на іншай прыладзе?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Выкарыстоўваць папку \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\" для ўсіх спосабаў уваходу?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Каб вам было прасцей уваходзіць у сістэму, вашы паролі і ключы доступу будуць захоўвацца ў менеджары пароляў."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Выкарыстоўваць стандартна"</string>
     <string name="use_once" msgid="9027366575315399714">"Скарыстаць адзін раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Пароляў: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Ключоў доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ключ доступу"</string>
     <string name="another_device" msgid="5147276802037801217">"Іншая прылада"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Іншыя спосабы ўваходу"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Закрыць аркуш"</string>
diff --git a/packages/CredentialManager/res/values-bg/strings.xml b/packages/CredentialManager/res/values-bg/strings.xml
index af7eb17..302cc3a 100644
--- a/packages/CredentialManager/res/values-bg/strings.xml
+++ b/packages/CredentialManager/res/values-bg/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Мениджър на идентификационни данни"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Отказ"</string>
     <string name="string_continue" msgid="1346732695941131882">"Напред"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Създаване другаде"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Запазване на друго място"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Използване на друго устройство"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Запазване на друго устройство"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Лесен начин за безопасно влизане в профил"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Използвайте отпечатъка, лицето или опцията си за заключване на екрана, за да влизате в профила си с помощта на уникален код за достъп, който не може да бъде забравен или откраднат. Научете повече"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете място за <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете място за <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"създаване на кодовете ви за достъп"</string>
     <string name="save_your_password" msgid="6597736507991704307">"запазване на паролата ви"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"запазване на данните ви за вход"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Задайте основен мениджър на пароли, за да запазвате своите пароли и кодове за достъп, така че следващия път да влезете по-бързо в профила си."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се създаде ли код за достъп в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Искате ли да запазите паролата си в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Искате ли да запазите данните си за вход в(ъв) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"код за достъп"</string>
     <string name="password" msgid="6738570945182936667">"парола"</string>
     <string name="sign_ins" msgid="4710739369149469208">"данни за вход"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Създаване на код за достъп в(ъв)"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Запазване на паролата в(ъв)"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Запазване на данните за вход в(ъв)"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Да се създаде ли код за достъп на друго устройство?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се използва ли <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за всичките ви данни за вход?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Този мениджър на пароли ще съхранява вашите пароли и кодове за достъп, за да влизате лесно в профила си."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Задаване като основно"</string>
     <string name="use_once" msgid="9027366575315399714">"Еднократно използване"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кода за достъп"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> пароли"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кода за достъп"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Код за достъп"</string>
     <string name="another_device" msgid="5147276802037801217">"Друго устройство"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Други мениджъри на пароли"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Затваряне на таблицата"</string>
diff --git a/packages/CredentialManager/res/values-bn/strings.xml b/packages/CredentialManager/res/values-bn/strings.xml
index 152e5bd..ad0bdea 100644
--- a/packages/CredentialManager/res/values-bn/strings.xml
+++ b/packages/CredentialManager/res/values-bn/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"বাতিল করুন"</string>
     <string name="string_continue" msgid="1346732695941131882">"চালিয়ে যান"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"অন্য জায়গায় তৈরি করুন"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"অন্য জায়গায় সেভ করুন"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"অন্য ডিভাইস ব্যবহার করুন"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"অন্য ডিভাইসে সেভ করুন"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"নিরাপদে সাইন-ইন করার একটি সহজ উপায়"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"একটি অনন্য পাসকী ব্যবহার করে সাইন-ইন করতে আপনার ফিঙ্গারপ্রিন্ট, মুখ বা স্ক্রিন লক ব্যবহার করুন যেটি ভুলে যাবেন না বা হারিয়ে যাবেন না। আরও জানুন"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> কোথায় সেভ করবেন তা বেছে নিন"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"আপনার পাসকী তৈরি করা"</string>
     <string name="save_your_password" msgid="6597736507991704307">"আপনার পাসওয়ার্ড সেভ করুন"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য সেভ করুন"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"আপনার পাসওয়ার্ড ও পাসকী সেভ করার জন্য একটি ডিফল্ট Password Manager সেট করুন এবং পরবর্তী সময়ে আরও ঝটপট সাইন-ইন করুন।"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ পাসকী তৈরি করবেন?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"আপনার পাসওয়ার্ড <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"আপনার সাইন-ইন করা সম্পর্কিত তথ্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-এ সেভ করবেন?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"পাসকী"</string>
     <string name="password" msgid="6738570945182936667">"পাসওয়ার্ড"</string>
     <string name="sign_ins" msgid="4710739369149469208">"সাইন-ইন"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"এখানে পাসকী তৈরি করুন"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"এখানে পাসওয়ার্ড সেভ করা"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"এখানে সাইন-ইন করা সম্পর্কিত ক্রেডেনশিয়াল সেভ করা"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"অন্য একটি ডিভাইসে পাসকী তৈরি করবেন?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"আপনার সব সাইন-ইনের জন্য <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ব্যবহার করবেন?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"এই Password Manager আপনার পাসওয়ার্ড ও পাসকী সেভ করবে, যাতে সহজেই সাইন-ইন করতে পারেন।"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ডিফল্ট হিসেবে সেট করুন"</string>
     <string name="use_once" msgid="9027366575315399714">"একবার ব্যবহার করুন"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>টি পাসকী"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>টি পাসওয়ার্ড"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>টি পাসকী"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"পাসকী"</string>
     <string name="another_device" msgid="5147276802037801217">"অন্য ডিভাইস"</string>
     <string name="other_password_manager" msgid="565790221427004141">"অন্যান্য Password Manager"</string>
     <string name="close_sheet" msgid="1393792015338908262">"শিট বন্ধ করুন"</string>
diff --git a/packages/CredentialManager/res/values-bs/strings.xml b/packages/CredentialManager/res/values-bs/strings.xml
index d774b88..2a96102 100644
--- a/packages/CredentialManager/res/values-bs/strings.xml
+++ b/packages/CredentialManager/res/values-bs/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Sačuvajte na drugom mjestu"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Koristite drugi uređaj"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Sačuvajte na drugom uređaju"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Koristite otisak prsta, lice ili zaključavanje ekrana da se prijavite jedinstvenim pristupnim ključem koji se ne može zaboraviti niti ukrasti. Saznajte više"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite gdje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite gdje <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"izradite pristupne ključeve"</string>
     <string name="save_your_password" msgid="6597736507991704307">"sačuvajte lozinku"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"sačuvajte informacije za prijavu"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Postavite zadani upravitelj zaporki da biste spremili zaporke i pristupne ključeve i sljedeći put se brže prijavili."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kreirati pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Sačuvati lozinku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Sačuvati informacije za prijavu na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
     <string name="password" msgid="6738570945182936667">"lozinka"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Izradite pristupni ključ u"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Spremite zaporku na"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Spremite podatke za prijavu na"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Želite li izraditi pristupni ključ na nekom drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Koristiti uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve vaše prijave?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Upravitelj zaporki pohranit će vaše zaporke i pristupne ključeve radi jednostavnije prijave."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
     <string name="use_once" msgid="9027366575315399714">"Koristi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj lozinki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-ca/strings.xml b/packages/CredentialManager/res/values-ca/strings.xml
index bfd9164..8b1e395280 100644
--- a/packages/CredentialManager/res/values-ca/strings.xml
+++ b/packages/CredentialManager/res/values-ca/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Gestor de credencials"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancel·la"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continua"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Crea en un altre lloc"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Desa en un altre lloc"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Utilitza un altre dispositiu"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Desa en un altre dispositiu"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una manera senzilla i segura d\'iniciar la sessió"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilitza l\'empremta digital, la cara o el bloqueig de pantalla per iniciar la sessió amb una clau d\'accés única que no es pot oblidar ni robar. Més informació"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Tria on vols <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crea les teves claus d\'accés"</string>
     <string name="save_your_password" msgid="6597736507991704307">"desar la contrasenya"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"desar la teva informació d\'inici de sessió"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Defineix un gestor de contrasenyes predeterminat per desar les teves contrasenyes i claus d\'accés d\'una manera més ràpida la pròxima vegada."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vols crear una clau d\'accés a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vols desar la contrasenya a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vols desar la teva informació d\'inici de sessió a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"clau d\'accés"</string>
     <string name="password" msgid="6738570945182936667">"contrasenya"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inicis de sessió"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Crea una clau d\'accés a"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Desa la contrasenya a"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Desa l\'inici de sessió a"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vols crear una clau d\'accés en un altre dispositiu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vols utilitzar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per a tots els teus inicis de sessió?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Aquest gestor de contrasenyes emmagatzemarà les teves contrasenyes i claus d\'accés per ajudar-te a iniciar la sessió fàcilment."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Estableix com a predeterminada"</string>
     <string name="use_once" msgid="9027366575315399714">"Utilitza un cop"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claus d\'accés"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasenyes"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> claus d\'accés"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Clau d\'accés"</string>
     <string name="another_device" msgid="5147276802037801217">"Un altre dispositiu"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Altres gestors de contrasenyes"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Tanca el full"</string>
diff --git a/packages/CredentialManager/res/values-cs/strings.xml b/packages/CredentialManager/res/values-cs/strings.xml
index 72a5f98..b032033 100644
--- a/packages/CredentialManager/res/values-cs/strings.xml
+++ b/packages/CredentialManager/res/values-cs/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Správce oprávnění"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Zrušit"</string>
     <string name="string_continue" msgid="1346732695941131882">"Pokračovat"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Vytvořit na jiném místě"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložit na jiné místo"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Použít jiné zařízení"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložit do jiného zařízení"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý způsob, jak se bezpečně přihlásit"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použijte svůj otisk prstu, obličej nebo zámek obrazovky k přihlášení pomocí jedinečného přístupového klíče, který nemůžete zapomenout a který vám nikdo nemůže odcizit. Další informace"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Zvolte, kde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Zvolte, kde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"vytvářet přístupové klíče"</string>
     <string name="save_your_password" msgid="6597736507991704307">"uložte si heslo"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"uložte své přihlašovací údaje"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Nastavte si výchozího správce hesel, do kterého si budete ukládat hesla a přístupové klíče za účelem rychlejšího přihlašování."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vytvořit přístupový klíč v <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Uložit heslo do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Uložit přihlašovací údaje do <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"přístupový klíč"</string>
     <string name="password" msgid="6738570945182936667">"heslo"</string>
     <string name="sign_ins" msgid="4710739369149469208">"přihlášení"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Vytvořit přístupový klíč v"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Uložit heslo do účtu"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Uložit přihlášení do"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vytvořit přístupový klíč v jiném zařízení?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Používat <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pro všechna přihlášení?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Tento správce hesel bude ukládat vaše hesla a přístupové klíče, abyste se mohli snadno přihlásit."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nastavit jako výchozí"</string>
     <string name="use_once" msgid="9027366575315399714">"Použít jednou"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Počet hesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Počet přístupových klíčů: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Přístupový klíč"</string>
     <string name="another_device" msgid="5147276802037801217">"Jiné zařízení"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Další správci hesel"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zavřít list"</string>
diff --git a/packages/CredentialManager/res/values-da/strings.xml b/packages/CredentialManager/res/values-da/strings.xml
index a05137e..0c63af5 100644
--- a/packages/CredentialManager/res/values-da/strings.xml
+++ b/packages/CredentialManager/res/values-da/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Loginstyring"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Annuller"</string>
     <string name="string_continue" msgid="1346732695941131882">"Fortsæt"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Opret et andet sted"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Gem et andet sted"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Brug en anden enhed"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Gem på en anden enhed"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"En nemmere og mere sikker måde at logge ind på"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Brug dit fingeraftryk, dit ansigt eller din skærmlås for at logge ind med en unik adgangsnøgle, der hverken kan glemmes eller stjæles. Få flere oplysninger"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Vælg, hvor du vil <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Vælg, hvor du vil <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"oprette dine adgangsnøgler"</string>
     <string name="save_your_password" msgid="6597736507991704307">"gem din adgangskode"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"gem dine loginoplysninger"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Angiv en standardadgangskodeadministrator for at gemme dine adgangskoder og adgangsnøgler og logge hurtigere ind næste gang."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du oprette en adgangsnøgle i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du gemme din adgangskode i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du gemme dine loginoplysninger i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"adgangsnøgle"</string>
     <string name="password" msgid="6738570945182936667">"adgangskode"</string>
     <string name="sign_ins" msgid="4710739369149469208">"loginmetoder"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Opret adgangsnøgle i"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Gem adgangskode i"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Gem loginmetode i"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vil du oprette en adgangsnøgle i en anden enhed?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruge <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> til alle dine loginmetoder?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Denne adgangskodeadministrator gemmer dine adgangskoder og adgangsnøgler for at hjælpe dig med nemt at logge ind."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Angiv som standard"</string>
     <string name="use_once" msgid="9027366575315399714">"Brug én gang"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> adgangsnøgler"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> adgangskoder"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> adgangsnøgler"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Adgangsnøgle"</string>
     <string name="another_device" msgid="5147276802037801217">"En anden enhed"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Andre adgangskodeadministratorer"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Luk arket"</string>
diff --git a/packages/CredentialManager/res/values-de/strings.xml b/packages/CredentialManager/res/values-de/strings.xml
index fa1e8f1..7d50aed 100644
--- a/packages/CredentialManager/res/values-de/strings.xml
+++ b/packages/CredentialManager/res/values-de/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"An anderem Ort speichern"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Anderes Gerät verwenden"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Auf einem anderen Gerät speichern"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einfach und sicher anmelden"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Verwende deinen Fingerabdruck oder deine Gesichts- bzw. Displaysperre, um dich mit einem eindeutigen Passkey anzumelden, der nicht vergessen oder gestohlen werden kann. Weitere Informationen"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Ort auswählen für: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Ort auswählen für: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"Passkeys erstellen"</string>
     <string name="save_your_password" msgid="6597736507991704307">"Passwort speichern"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"Anmeldedaten speichern"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Du kannst einen Passwortmanager festlegen, der standardmäßig zum Speichern deiner Passwörter und Passkeys verwendet wird, damit du dich das nächste Mal schneller anmelden kannst."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Passkey hier erstellen: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Passwort hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Anmeldedaten hier speichern: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"Passkey"</string>
     <string name="password" msgid="6738570945182936667">"Passwort"</string>
     <string name="sign_ins" msgid="4710739369149469208">"Anmeldungen"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Passkey hier erstellen:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Passwort hier speichern:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Anmeldedaten hier speichern:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Passkey auf anderem Gerät erstellen?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> für alle Anmeldungen verwenden?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Mit diesem Passwortmanager werden deine Passwörter und Passkeys gespeichert, damit du dich problemlos anmelden kannst."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Als Standard festlegen"</string>
     <string name="use_once" msgid="9027366575315399714">"Einmal verwenden"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> Passwörter, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> Passkeys"</string>
diff --git a/packages/CredentialManager/res/values-el/strings.xml b/packages/CredentialManager/res/values-el/strings.xml
index 706d9f3..a088d1d 100644
--- a/packages/CredentialManager/res/values-el/strings.xml
+++ b/packages/CredentialManager/res/values-el/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Αποθήκευση σε άλλη θέση"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Χρήση άλλης συσκευής"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Αποθήκευση σε άλλη συσκευή"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ένας απλός τρόπος για ασφαλή σύνδεση"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Χρησιμοποιήστε το δακτυλικό αποτύπωμα, το πρόσωπο ή το κλείδωμα οθόνης σας για να συνδεθείτε με ένα μοναδικό κλειδί πρόσβασης που δεν είναι δυνατό να ξεχάσετε ή να κλαπεί. Μάθετε περισσότερα"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Επιλέξτε θέση για <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Επιλέξτε θέση για <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"δημιουργήστε τα κλειδιά πρόσβασής σας"</string>
     <string name="save_your_password" msgid="6597736507991704307">"αποθήκευση του κωδικού πρόσβασής σας"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"αποθήκευση των στοιχείων σύνδεσής σας"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Για να συνδέεστε πιο γρήγορα, ορίστε ένα προεπιλεγμένο πρόγραμμα διαχείρισης κωδικών πρόσβασης για να αποθηκεύετε τους κωδικούς και τα κλειδιά πρόσβασής σας."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Να δημιουργηθει κλειδί πρόσβασης στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Να αποθηκευτεί ο κωδικός πρόσβασής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Να αποθηκευτούν τα στοιχεία σύνδεσής σας στο <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>;"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"κλειδί πρόσβασης"</string>
     <string name="password" msgid="6738570945182936667">"κωδικός πρόσβασης"</string>
     <string name="sign_ins" msgid="4710739369149469208">"στοιχεία σύνδεσης"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Δημιουργία κλειδιού πρόσβασης σε"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Αποθήκευση κωδικού πρόσβασης σε"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Αποθήκευση στοιχείων σύνδεσης σε"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Δημιουργία κλειδιού πρόσβασης σε άλλη συσκευή;"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Να χρησιμοποιηθεί το <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> για όλες τις συνδέσεις σας;"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Αυτός ο διαχειριστής κωδικών πρόσβασης θα αποθηκεύει τους κωδικούς πρόσβασης και τα κλειδιά πρόσβασης, για να συνδέεστε εύκολα."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ορισμός ως προεπιλογής"</string>
     <string name="use_once" msgid="9027366575315399714">"Χρήση μία φορά"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> κωδικοί πρόσβασης, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> κλειδιά πρόσβασης"</string>
diff --git a/packages/CredentialManager/res/values-en-rAU/strings.xml b/packages/CredentialManager/res/values-en-rAU/strings.xml
index 7adeded..a9a6f02 100644
--- a/packages/CredentialManager/res/values-en-rAU/strings.xml
+++ b/packages/CredentialManager/res/values-en-rAU/strings.xml
@@ -8,8 +8,14 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
     <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
diff --git a/packages/CredentialManager/res/values-en-rCA/strings.xml b/packages/CredentialManager/res/values-en-rCA/strings.xml
index 8a8b884..c895a41 100644
--- a/packages/CredentialManager/res/values-en-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-en-rCA/strings.xml
@@ -8,8 +8,10 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"Safer with passkeys"</string>
+    <string name="passkey_creation_intro_body_password" msgid="312712407571126228">"No need to create or remember complex passwords"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="691816235541508203">"Use your fingerprint, face, or screen lock to create a unique passkey"</string>
+    <string name="passkey_creation_intro_body_device" msgid="477121861162321129">"Passkeys are saved to a password manager, so you can sign in on other devices"</string>
     <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
     <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
diff --git a/packages/CredentialManager/res/values-en-rGB/strings.xml b/packages/CredentialManager/res/values-en-rGB/strings.xml
index 7adeded..a9a6f02 100644
--- a/packages/CredentialManager/res/values-en-rGB/strings.xml
+++ b/packages/CredentialManager/res/values-en-rGB/strings.xml
@@ -8,8 +8,14 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
     <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
diff --git a/packages/CredentialManager/res/values-en-rIN/strings.xml b/packages/CredentialManager/res/values-en-rIN/strings.xml
index 7adeded..a9a6f02 100644
--- a/packages/CredentialManager/res/values-en-rIN/strings.xml
+++ b/packages/CredentialManager/res/values-en-rIN/strings.xml
@@ -8,8 +8,14 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Save to another place"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Use another device"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Save to another device"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A simple way to sign in safely"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Choose where to <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"create your passkeys"</string>
     <string name="save_your_password" msgid="6597736507991704307">"save your password"</string>
diff --git a/packages/CredentialManager/res/values-en-rXC/strings.xml b/packages/CredentialManager/res/values-en-rXC/strings.xml
index 85e94df..efa0633 100644
--- a/packages/CredentialManager/res/values-en-rXC/strings.xml
+++ b/packages/CredentialManager/res/values-en-rXC/strings.xml
@@ -8,8 +8,10 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‏‏‎‎‏‎‎‎‎‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‎Save to another place‎‏‎‎‏‎"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‎‎‏‎‎‎‎‎‎‏‎‏‎‏‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‎‏‎Use another device‎‏‎‎‏‎"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‎‏‎‏‏‎‏‎‎Save to another device‎‏‎‎‏‎"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎A simple way to sign in safely‎‏‎‎‏‎"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‎Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_title" msgid="4251037543787718844">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‏‏‏‎‎‎Safer with passkeys‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_body_password" msgid="312712407571126228">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‏‏‏‏‎‏‎‏‎‎‎No need to create or remember complex passwords‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_body_fingerprint" msgid="691816235541508203">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‎‎‎‏‏‎‏‎‏‏‎Use your fingerprint, face, or screen lock to create a unique passkey‎‏‎‎‏‎"</string>
+    <string name="passkey_creation_intro_body_device" msgid="477121861162321129">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‏‎‎‎‏‎‎‏‏‏‎‏‎‎‏‎Passkeys are saved to a password manager, so you can sign in on other devices‎‏‎‎‏‎"</string>
     <string name="choose_provider_title" msgid="7245243990139698508">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‎‏‎‎‎‏‏‏‎‎‎‎‎‏‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎Choose where to ‎‏‎‎‏‏‎<xliff:g id="CREATETYPES">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‎‎‏‎‎‎create your passkeys‎‏‎‎‏‎"</string>
     <string name="save_your_password" msgid="6597736507991704307">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‎‏‏‎save your password‎‏‎‎‏‎"</string>
diff --git a/packages/CredentialManager/res/values-es-rUS/strings.xml b/packages/CredentialManager/res/values-es-rUS/strings.xml
index 5b8e442..21f0720 100644
--- a/packages/CredentialManager/res/values-es-rUS/strings.xml
+++ b/packages/CredentialManager/res/values-es-rUS/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otra ubicación"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo simple y seguro de ingresar"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa tu huella dactilar, tu rostro o el bloqueo de pantalla para acceder con una llave de acceso única que no olvidarás ni podrán robarte. Más información"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crea tus llaves de acceso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de acceso"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Configura un administrador de contraseñas predeterminado para guardar tus contraseñas y llaves de acceso, y acceder más rápido la próxima vez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Quieres crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Quieres guardar tu contraseña de <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Quieres guardar tu información de acceso a <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contraseña"</string>
     <string name="sign_ins" msgid="4710739369149469208">"accesos"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Crear llave de acceso en"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Guardar contraseña en"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Guardar acceso en"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"¿Quieres crear una llave de acceso en otro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Quieres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus accesos?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este administrador de contraseñas almacenará tus contraseñas y llaves de acceso para ayudarte a acceder fácilmente."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> llaves de acceso, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> contraseñas"</string>
diff --git a/packages/CredentialManager/res/values-es/strings.xml b/packages/CredentialManager/res/values-es/strings.xml
index 19fde72..409bdac 100644
--- a/packages/CredentialManager/res/values-es/strings.xml
+++ b/packages/CredentialManager/res/values-es/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Gestor de credenciales"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Cancelar"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuar"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Crear en otro lugar"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar en otro lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar otro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar en otro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Una forma sencilla y segura de iniciar sesión"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa la huella digital, la cara o el bloqueo de pantalla para iniciar sesión con una llave de acceso única que no se puede olvidar ni robar. Más información"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Elige dónde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crea tus llaves de acceso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"guardar tu contraseña"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"guardar tu información de inicio de sesión"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Define un gestor de contraseñas predeterminado para guardar tus contraseñas y llaves de acceso e iniciar sesión más rápido la próxima vez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"¿Crear una llave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"¿Guardar tu contraseña en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"¿Guardar tu información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"llave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contraseña"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inicios de sesión"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Crear llave de acceso en"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Guardar contraseña en"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Guardar inicio de sesión en"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"¿Crear una llave de acceso en otro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"¿Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos tus inicios de sesión?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gestor de contraseñas almacenará tus contraseñas y llaves de acceso para que puedas iniciar sesión fácilmente."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Fijar como predeterminado"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar una vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> llaves de acceso"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contraseñas"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> llaves de acceso"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Llave de acceso"</string>
     <string name="another_device" msgid="5147276802037801217">"Otro dispositivo"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Otros gestores de contraseñas"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Cerrar hoja"</string>
diff --git a/packages/CredentialManager/res/values-et/strings.xml b/packages/CredentialManager/res/values-et/strings.xml
index 5b1b070..bfaec78 100644
--- a/packages/CredentialManager/res/values-et/strings.xml
+++ b/packages/CredentialManager/res/values-et/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Mandaatide haldur"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Tühista"</string>
     <string name="string_continue" msgid="1346732695941131882">"Jätka"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Loo teises kohas"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvesta teise kohta"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Kasuta teist seadet"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvesta teise seadmesse"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Lihtne viis turvaliselt sisselogimiseks"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Kasutage sõrmejälge, nägu või ekraanilukku, et logida sisse unikaalse pääsuvõtmega, mida ei saa unustada ega varastada. Lisateave"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Valige, kus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Valige, kus <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"looge oma pääsuvõtmed"</string>
     <string name="save_your_password" msgid="6597736507991704307">"parool salvestada"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"sisselogimisandmed salvestada"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Määrake vaikeparoolihaldur, et oma paroolid ja pääsuvõtmed salvestada ning järgmisel korral kiiremini sisse logida."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kas luua teenuses <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pääsuvõti?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Kas salvestada parool teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kas salvestada sisselogimisteave teenusesse <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"pääsukood"</string>
     <string name="password" msgid="6738570945182936667">"parool"</string>
     <string name="sign_ins" msgid="4710739369149469208">"sisselogimisandmed"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Loo pääsuvõti:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Salvesta parool:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Salvesta sisselogimisandmed:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Kas luua pääsuvõti teises seadmes?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Kas kasutada teenust <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kõigi teie sisselogimisandmete puhul?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"See paroolihaldur salvestab teie paroolid ja pääsuvõtmed, et aidata teil hõlpsalt sisse logida."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Määra vaikeseadeks"</string>
     <string name="use_once" msgid="9027366575315399714">"Kasuta ühe korra"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> pääsuvõtit"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parooli"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> pääsuvõtit"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pääsukood"</string>
     <string name="another_device" msgid="5147276802037801217">"Teine seade"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Muud paroolihaldurid"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sule leht"</string>
diff --git a/packages/CredentialManager/res/values-eu/strings.xml b/packages/CredentialManager/res/values-eu/strings.xml
index b2c1fe5..5a1494b 100644
--- a/packages/CredentialManager/res/values-eu/strings.xml
+++ b/packages/CredentialManager/res/values-eu/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Kredentzialen kudeatzailea"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Utzi"</string>
     <string name="string_continue" msgid="1346732695941131882">"Egin aurrera"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Sortu beste toki batean"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Gorde beste toki batean"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Erabili beste gailu bat"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Gorde beste gailu batean"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Segurtasun osoz saioa hasteko modu erraza"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Erabili hatz-marka, aurpegia edo pantailaren blokeoa ahaztu edo lapurtu ezin den sarbide-gako baten bidez saioa hasteko. Lortu informazio gehiago"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Aukeratu non <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Aukeratu non <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"sortu sarbide-gakoak"</string>
     <string name="save_your_password" msgid="6597736507991704307">"gorde pasahitza"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"gorde kredentzialei buruzko informazioa"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Ezarri pasahitz-kudeatzaile lehenetsia pasahitzak eta sarbide-gakoak gordetzeko, eta hurrengoan saioa bizkorrago hasteko."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sarbide-gako bat sortu nahi duzu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Pasahitza <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Kredentzialei buruzko informazioa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> aplikazioan gorde nahi duzu?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"sarbide-gakoa"</string>
     <string name="password" msgid="6738570945182936667">"pasahitza"</string>
     <string name="sign_ins" msgid="4710739369149469208">"kredentzialak"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Sortu sarbide-gako bat hemen:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Gorde pasahitza hemen:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Gorde kredentzialak hemen:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Sarbide-gako bat sortu nahi duzu beste gailu batean?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> erabili nahi duzu kredentzial guztietarako?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Pasahitz-kudeatzaile honek pasahitzak eta sarbide-gakoak gordeko ditu saioa erraz has dezazun."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ezarri lehenetsi gisa"</string>
     <string name="use_once" msgid="9027366575315399714">"Erabili behin"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz eta <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> sarbide-gako"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> pasahitz"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> sarbide-gako"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Sarbide-gakoa"</string>
     <string name="another_device" msgid="5147276802037801217">"Beste gailu bat"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Beste pasahitz-kudeatzaile batzuk"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Itxi orria"</string>
diff --git a/packages/CredentialManager/res/values-fa/strings.xml b/packages/CredentialManager/res/values-fa/strings.xml
index 98b487c..065bde4 100644
--- a/packages/CredentialManager/res/values-fa/strings.xml
+++ b/packages/CredentialManager/res/values-fa/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"مدیر اعتبارنامه"</string>
     <string name="string_cancel" msgid="6369133483981306063">"لغو"</string>
     <string name="string_continue" msgid="1346732695941131882">"ادامه"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"ایجاد در مکانی دیگر"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"ذخیره در مکانی دیگر"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"استفاده از دستگاهی دیگر"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ذخیره در دستگاهی دیگر"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"روشی ساده برای ورود به سیستم ایمن"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"برای ورود به سیستم با گذرکلیدی یکتا که غیرقابل فراموش شدن یا دزدیده شدن باشد، از اثر انگشت، چهره، یا قفل صفحه استفاده کنید. بیشتر بدانید"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"انتخاب محل <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"انتخاب محل <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ایجاد گذرکلید"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ذخیره گذرواژه"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ذخیره اطلاعات ورود به سیستم"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"برای ذخیره کردن گذرواژه‌ها و گذرکلیدهایتان، مدیر گذرواژه پیش‌فرض تنظیم کنید تا دفعه بعدی سریع‌تر به سیستم وارد شوید."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"گذرکلید در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ایجاد شود؟"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"گذرواژه در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اطلاعات ورود به سیستم در <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ذخیره شود؟"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"گذرکلید"</string>
     <string name="password" msgid="6738570945182936667">"گذرواژه"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ورود به سیستم‌ها"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ایجاد گذرکلید در"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ذخیره گذرواژه در"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ذخیره ورود به سیستم در"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"گذرکلید در دستگاهی دیگر ایجاد شود؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"از <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> برای همه ورود به سیستم‌ها استفاده شود؟"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"این مدیر گذرواژه گذرکلیدها و گذرواژه‌های شما را ذخیره خواهد کرد تا به‌راحتی بتوانید به سیستم وارد شوید."</string>
     <string name="set_as_default" msgid="4415328591568654603">"تنظیم به‌عنوان پیش‌فرض"</string>
     <string name="use_once" msgid="9027366575315399714">"یک‌بار استفاده"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> گذرکلید"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> گذرواژه"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> گذرکلید"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"گذرکلید"</string>
     <string name="another_device" msgid="5147276802037801217">"دستگاهی دیگر"</string>
     <string name="other_password_manager" msgid="565790221427004141">"دیگر مدیران گذرواژه"</string>
     <string name="close_sheet" msgid="1393792015338908262">"بستن برگ"</string>
diff --git a/packages/CredentialManager/res/values-fi/strings.xml b/packages/CredentialManager/res/values-fi/strings.xml
index 9ad178a..cbea24be 100644
--- a/packages/CredentialManager/res/values-fi/strings.xml
+++ b/packages/CredentialManager/res/values-fi/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Kirjautumistietojen hallinta"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Peru"</string>
     <string name="string_continue" msgid="1346732695941131882">"Jatka"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Luo muualla"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Tallenna muualle"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Käytä toista laitetta"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Tallenna toiselle laitteelle"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Helppo tapa kirjautua turvallisesti sisään"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Käytä sormenjälkeä, kasvoja tai näytön lukitusta, niin voit kirjautua sisään yksilöllisellä avainkoodilla, jota ei voi unohtaa tai varastaa. Lue lisää"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Valitse paikka: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"luo avainkoodeja"</string>
     <string name="save_your_password" msgid="6597736507991704307">"tallenna salasanasi"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"tallenna kirjautumistiedot"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Aseta salasanojen ylläpidolle oletustyökalu, niin voit tallentaa salasanat ja avainkoodit sekä kirjautua sisään nopeammin ensi kerralla."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Luodaanko avainkoodi (<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>)?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Tallennetaanko salasanasi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Tallennetaanko kirjautumistietosi tänne: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"avainkoodi"</string>
     <string name="password" msgid="6738570945182936667">"salasana"</string>
     <string name="sign_ins" msgid="4710739369149469208">"sisäänkirjautumiset"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Luo avainkoodi tänne:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Tallenna salasana tänne:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Tallenna kirjautumistiedot tänne:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Luodaanko avainkoodi toisella laitteella?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Otetaanko <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> käyttöön kaikissa sisäänkirjautumisissa?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Tämä salasanojen ylläpitotyökalu tallentaa salasanat ja avainkoodit, jotta voit kirjautua helposti sisään."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Aseta oletukseksi"</string>
     <string name="use_once" msgid="9027366575315399714">"Käytä kerran"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> avainkoodia"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> salasanaa"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> avainkoodia"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Avainkoodi"</string>
     <string name="another_device" msgid="5147276802037801217">"Toinen laite"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Muut salasanojen ylläpitotyökalut"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sulje taulukko"</string>
diff --git a/packages/CredentialManager/res/values-fr-rCA/strings.xml b/packages/CredentialManager/res/values-fr-rCA/strings.xml
index a2e7581..b122fd2 100644
--- a/packages/CredentialManager/res/values-fr-rCA/strings.xml
+++ b/packages/CredentialManager/res/values-fr-rCA/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer à un autre emplacement"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une manière simple de se connecter en toute sécurité"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez vos empreintes digitales, votre visage ou un écran de verrouillage pour vous connecter avec une clé d\'accès unique qui ne peut pas être oubliée ou volée. En savoir plus"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"créer vos clés d\'accès"</string>
     <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos données de connexion"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Sélectionnez un gestionnaire de mots de passe par défaut où seront enregistrés vos mots de passe et vos clés d\'accès, et connectez-vous plus rapidement la prochaine fois."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos données de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
     <string name="password" msgid="6738570945182936667">"mot de passe"</string>
     <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Créer une clé d\'accès dans"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Enregistrer le mot de passe dans"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Enregistrer la connexion dans"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Créer une clé d\'accès dans un autre appareil?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ce gestionnaire de mots de passe va enregistrer vos mots de passe et vos clés d\'accès pour vous aider à vous connecter facilement."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
     <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
diff --git a/packages/CredentialManager/res/values-fr/strings.xml b/packages/CredentialManager/res/values-fr/strings.xml
index 2b280fb..cf69329 100644
--- a/packages/CredentialManager/res/values-fr/strings.xml
+++ b/packages/CredentialManager/res/values-fr/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Enregistrer ailleurs"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Utiliser un autre appareil"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Enregistrer sur un autre appareil"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Une façon simple et sécurisée de vous connecter"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Utilisez votre empreinte digitale, votre visage ou le verrouillage de l\'écran pour vous connecter avec une clé d\'accès unique que vous ne pourrez pas oublier ni vous faire voler. En savoir plus"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Choisir où <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"créer vos clés d\'accès"</string>
     <string name="save_your_password" msgid="6597736507991704307">"enregistrer votre mot de passe"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"enregistrer vos informations de connexion"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Définissez un gestionnaire de mots de passe par défaut pour enregistrer vos mots de passe et clés d\'accès et vous connecter plus rapidement la prochaine fois."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Créer une clé d\'accès dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Enregistrer votre mot de passe dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Enregistrer vos informations de connexion dans <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"clé d\'accès"</string>
     <string name="password" msgid="6738570945182936667">"mot de passe"</string>
     <string name="sign_ins" msgid="4710739369149469208">"connexions"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Créer une clé d\'accès dans"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Enregistrer le mot de passe dans"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Enregistrer les informations de connexion dans"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Créer une clé d\'accès dans un autre appareil ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Utiliser <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pour toutes vos connexions ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ce gestionnaire de mots de passe stockera vos mots de passe et clés d\'accès pour vous permettre de vous connecter facilement."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Définir par défaut"</string>
     <string name="use_once" msgid="9027366575315399714">"Utiliser une fois"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mots de passe, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> clés d\'accès"</string>
diff --git a/packages/CredentialManager/res/values-gl/strings.xml b/packages/CredentialManager/res/values-gl/strings.xml
index cc03ca4..c41dca0 100644
--- a/packages/CredentialManager/res/values-gl/strings.xml
+++ b/packages/CredentialManager/res/values-gl/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Gardar noutro lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Gardar noutro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un xeito fácil de iniciar sesión de forma segura"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa a impresión dixital, a cara ou o bloqueo de pantalla para iniciar sesión cunha clave de acceso única que non podes esquecer nin cha poden roubar. Máis información"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolle onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crear as túas claves de acceso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"gardar o contrasinal"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"gardar a información de inicio de sesión"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Configura un xestor de contrasinais como predefinido para gardar os teus contrasinais e as túas claves de acceso, e iniciar sesión máis rápido a próxima vez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Queres crear unha clave de acceso en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Queres gardar o contrasinal en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Queres gardar a información de inicio de sesión en <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"clave de acceso"</string>
     <string name="password" msgid="6738570945182936667">"contrasinal"</string>
     <string name="sign_ins" msgid="4710739369149469208">"métodos de inicio de sesión"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Crear clave de acceso en"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Gardar contrasinal en"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Gardar método de inicio de sesión en"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Queres crear unha clave de acceso noutro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Queres usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cada vez que inicies sesión?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este xestor de contrasinais almacenará os contrasinais e as claves de acceso para axudarche a iniciar sesión facilmente."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Establecer como predeterminado"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar unha vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> contrasinais, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> claves de acceso"</string>
diff --git a/packages/CredentialManager/res/values-gu/strings.xml b/packages/CredentialManager/res/values-gu/strings.xml
index f796d20..17df19e 100644
--- a/packages/CredentialManager/res/values-gu/strings.xml
+++ b/packages/CredentialManager/res/values-gu/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"કોઈ અન્ય સ્થાન પર સાચવો"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"કોઈ અન્ય ડિવાઇસનો ઉપયોગ કરો"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"અન્ય ડિવાઇસ પર સાચવો"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"સલામત રીતે સાઇન ઇન કરવાની સરળ રીત"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ભૂલી ન શકાય કે ચોરાઈ ન જાય, તેવી કોઈ વિશિષ્ટ પાસકી વડે સાઇન ઇન કરવા માટે, તમારી ફિંગરપ્રિન્ટ, ચહેરો અથવા સ્ક્રીન લૉકનો ઉપયોગ કરો. વધુ જાણો"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી છે, તે પસંદ કરો"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ક્યાં સાચવવી છે, તે પસંદ કરો"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"તમારી પાસકી બનાવો"</string>
     <string name="save_your_password" msgid="6597736507991704307">"તમારો પાસવર્ડ સાચવો"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"તમારી સાઇન-ઇનની માહિતી સાચવો"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"તમારા પાસવર્ડ અને પાસકી સાચવવા માટે કોઈ ડિફૉલ્ટ પાસવર્ડ મેનેજર સેટ કરો અને આગલી વખતે વધુ ઝડપથી સાઇન ઇન કરો."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"શું <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં કોઈ પાસકી બનાવીએ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"શું તમારો પાસવર્ડ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"શું તમારી સાઇન-ઇનની માહિતી <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>માં સાચવીએ?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"પાસકી"</string>
     <string name="password" msgid="6738570945182936667">"પાસવર્ડ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"સાઇન-ઇન"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"આમાં પાસકી બનાવો"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"પાસવર્ડ અહીં સાચવો"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"સાઇન-ઇનની માહિતી અહીં સાચવો"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"શું અન્ય ડિવાઇસમાં પાસકી બનાવવા માગો છો?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"શું તમારા બધા સાઇન-ઇન માટે <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>નો ઉપયોગ કરીએ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"આ પાસવર્ડ મેનેજર તમને સરળતાથી સાઇન ઇન કરવામાં સહાય કરવા માટે, તમારા પાસવર્ડ અને પાસકીને સ્ટોર કરશે."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ડિફૉલ્ટ તરીકે સેટ કરો"</string>
     <string name="use_once" msgid="9027366575315399714">"એકવાર ઉપયોગ કરો"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> પાસવર્ડ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> પાસકી"</string>
diff --git a/packages/CredentialManager/res/values-hi/strings.xml b/packages/CredentialManager/res/values-hi/strings.xml
index fbf1c02..0147139 100644
--- a/packages/CredentialManager/res/values-hi/strings.xml
+++ b/packages/CredentialManager/res/values-hi/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"CredentialManager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द करें"</string>
     <string name="string_continue" msgid="1346732695941131882">"जारी रखें"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"दूसरी जगह पर बनाएं"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"दूसरी जगह पर सेव करें"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"दूसरे डिवाइस का इस्तेमाल करें"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"दूसरे डिवाइस पर सेव करें"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरीके से साइन इन करने का आसान तरीका"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"साइन इन करने के लिए फ़िंगरप्रिंट, फ़ेस या स्क्रीन लॉक जैसी यूनीक पासकी का इस्तेमाल करें. इन्हें, न तो भुलाया जा सकता है न ही चुराया जा सकता है. ज़्यादा जानें"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"चुनें कि <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां पर सेव करना है"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"चुनें कि <xliff:g id="CREATETYPES">%1$s</xliff:g> कहां पर सेव करना है"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"अपनी पासकी बनाएं"</string>
     <string name="save_your_password" msgid="6597736507991704307">"अपना पासवर्ड सेव करें"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"साइन इन से जुड़ी अपनी जानकारी सेव करें"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"डिफ़ॉल्ट पासवर्ड मैनेजर सेट करें, ताकि आपके पासवर्ड और पासकी सेव की जा सकें और अगली बार आप तेज़ी से साइन इन कर सकें."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"क्या आपको <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में पासकी बनानी है?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"क्या आपको अपना पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करना है?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"क्या आपको साइन इन करने से जुड़ी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> में सेव करनी है?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
     <string name="sign_ins" msgid="4710739369149469208">"साइन इन"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"पासकी इसमें बनाएं"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"पासवर्ड इसमें सेव करें"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"साइन इन से जुड़ी जानकारी इसमें सेव करें"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"क्या आपको किसी दूसरे डिवाइस में पासकी बनानी है?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"क्या आपको साइन इन से जुड़ी सारी जानकारी सेव करने के लिए, <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> का इस्तेमाल करना है?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"पासवर्ड और पासकी को इस पासवर्ड मैनेजर में सेव करके, आसानी से साइन इन किया जा सकता है."</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="use_once" msgid="9027366575315399714">"इसका इस्तेमाल एक बार किया जा सकता है"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड और <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"पासकी"</string>
     <string name="another_device" msgid="5147276802037801217">"दूसरा डिवाइस"</string>
     <string name="other_password_manager" msgid="565790221427004141">"दूसरे पासवर्ड मैनेजर"</string>
     <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करें"</string>
diff --git a/packages/CredentialManager/res/values-hr/strings.xml b/packages/CredentialManager/res/values-hr/strings.xml
index 6c1952f..a5bd3ba 100644
--- a/packages/CredentialManager/res/values-hr/strings.xml
+++ b/packages/CredentialManager/res/values-hr/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Upravitelj vjerodajnicama"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Odustani"</string>
     <string name="string_continue" msgid="1346732695941131882">"Nastavi"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Izradi na drugom mjestu"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Spremi na drugom mjestu"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Upotrijebite neki drugi uređaj"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Spremi na drugi uređaj"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednostavan način za sigurnu prijavu"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Prijavite se otiskom prsta, licem ili zaključavanjem zaslona kao jedinstvenim pristupnim ključem koji je nemoguće zaboraviti ili ukrasti. Saznajte više"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite mjesto za sljedeće: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Odaberite mjesto za sljedeće: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"izradite pristupne ključeve"</string>
     <string name="save_your_password" msgid="6597736507991704307">"spremi zaporku"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"spremi podatke za prijavu"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Postavite zadani upravitelj zaporki da biste spremili zaporke i pristupne ključeve i sljedeći put se brže prijavili."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite li izraditi pristupni ključ na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite li spremiti zaporku na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite li spremiti podatke o prijavi na usluzi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"pristupni ključ"</string>
     <string name="password" msgid="6738570945182936667">"zaporka"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Izradite pristupni ključ u"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Spremite zaporku na"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Spremite podatke za prijavu na"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Želite li izraditi pristupni ključ na nekom drugom uređaju?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite li upotrebljavati uslugu <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> za sve prijave?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Upravitelj zaporki pohranit će vaše zaporke i pristupne ključeve radi jednostavnije prijave."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Postavi kao zadano"</string>
     <string name="use_once" msgid="9027366575315399714">"Upotrijebi jednom"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Broj zaporki: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Broj pristupnih ključeva: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Pristupni ključ"</string>
     <string name="another_device" msgid="5147276802037801217">"Drugi uređaj"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Drugi upravitelji zaporki"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zatvaranje lista"</string>
diff --git a/packages/CredentialManager/res/values-hu/strings.xml b/packages/CredentialManager/res/values-hu/strings.xml
index 0efa3e8..4c77038 100644
--- a/packages/CredentialManager/res/values-hu/strings.xml
+++ b/packages/CredentialManager/res/values-hu/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Mentés másik helyre"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Másik eszköz használata"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Mentés másik eszközre"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"A biztonságos bejelentkezés egyszerű módja"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ujjlenyomatát, arcát vagy képernyőzárát használva egyedi azonosítókulccsal jelentkezhet be, amelyet nem lehet elfelejteni vagy ellopni. További információ."</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Válassza ki a(z) <xliff:g id="CREATETYPES">%1$s</xliff:g> helyét"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Válassza ki a(z) <xliff:g id="CREATETYPES">%1$s</xliff:g> helyét"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"azonosítókulcsok létrehozása"</string>
     <string name="save_your_password" msgid="6597736507991704307">"jelszó mentése"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"bejelentkezési adatok mentése"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Állítson be alapértelmezett jelszókezelőt jelszavai és azonosítókulcsai mentéséhez és a gyorsabb bejelentkezéshez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Létrehoz azonosítókulcsot a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásban?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Menti jelszavát a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Menti bejelentkezési adatait a(z) <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> szolgáltatásba?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"azonosítókulcs"</string>
     <string name="password" msgid="6738570945182936667">"jelszó"</string>
     <string name="sign_ins" msgid="4710739369149469208">"bejelentkezési adatok"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Azonosítókulcs létrehozása itt:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Jelszó mentése ide:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Bejelentkezési adatok mentése ide:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Létrehoz azonosítókulcsot egy másik eszközön?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Szeretné a következőt használni az összes bejelentkezési adatához: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ez a jelszókezelő a bejelentkezés megkönnyítése érdekében tárolja jelszavait és azonosítókulcsait."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Beállítás alapértelmezettként"</string>
     <string name="use_once" msgid="9027366575315399714">"Egyszeri használat"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> jelszó, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> azonosítókulcs"</string>
diff --git a/packages/CredentialManager/res/values-hy/strings.xml b/packages/CredentialManager/res/values-hy/strings.xml
index de47e9f..afa529a 100644
--- a/packages/CredentialManager/res/values-hy/strings.xml
+++ b/packages/CredentialManager/res/values-hy/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Մուտքի տվյալների կառավարիչ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Չեղարկել"</string>
     <string name="string_continue" msgid="1346732695941131882">"Շարունակել"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Ստեղծել այլ տեղում"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Պահել այլ տեղում"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Օգտագործել այլ սարք"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Պահել մեկ այլ սարքում"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Մուտք գործելու անվտանգ և պարզ եղանակ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Օգտագործեք ձեր մատնահետքը, դեմքը կամ էկրանի կողպումը՝ մուտք գործելու հաշիվ եզակի անցաբառի միջոցով, որը հնարավոր չէ կոտրել կամ մոռանալ։ Իմանալ ավելին"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Ընտրեք, թե որտեղ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Ընտրեք, թե որտեղ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ստեղծել ձեր անցաբառերը"</string>
     <string name="save_your_password" msgid="6597736507991704307">"պահել գաղտնաբառը"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"պահել մուտքի տվյալները"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Սահմանեք գաղտնաբառերի կանխադրված կառավարիչ՝ ձեր գաղտնաբառերն ու անցաբառերը պահելու և հաջորդ անգամ ավելի արագ մուտք գործելու համար։"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ստեղծե՞լ անցաբառ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Պահե՞լ ձեր գաղտնաբառը <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Պահե՞լ ձեր մուտքի տվյալները <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածում"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"անցաբառ"</string>
     <string name="password" msgid="6738570945182936667">"գաղտնաբառ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"մուտք"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Ստեղծել անցաբառ…"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Պահել գաղտնաբառը…"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Պահել մուտքի տվյալները…"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Ստեղծե՞լ անցաբառ մեկ այլ սարքում"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Միշտ մուտք գործե՞լ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> հավելվածի միջոցով"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Գաղտնաբառերի այս կառավարիչը կպահի ձեր գաղտնաբառերն ու անցաբառերը՝ օգնելու ձեզ հեշտությամբ մուտք գործել հաշիվ։"</string>
     <string name="set_as_default" msgid="4415328591568654603">"Նշել որպես կանխադրված"</string>
     <string name="use_once" msgid="9027366575315399714">"Օգտագործել մեկ անգամ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> անցաբառ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> գաղտնաբառ"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> անցաբառ"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Անցաբառ"</string>
     <string name="another_device" msgid="5147276802037801217">"Այլ սարք"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Գաղտնաբառերի այլ կառավարիչներ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Փակել թերթը"</string>
diff --git a/packages/CredentialManager/res/values-in/strings.xml b/packages/CredentialManager/res/values-in/strings.xml
index d980d44..5cad3f3 100644
--- a/packages/CredentialManager/res/values-in/strings.xml
+++ b/packages/CredentialManager/res/values-in/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan ke tempat lain"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan perangkat lain"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan ke perangkat lain"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk login dengan aman"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan sidik jari, wajah, atau kunci layar untuk login dengan kunci sandi unik yang mudah diingat dan tidak dapat dicuri. Pelajari lebih lanjut"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"membuat kunci sandi Anda"</string>
     <string name="save_your_password" msgid="6597736507991704307">"menyimpan sandi Anda"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"menyimpan info login Anda"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Setel pengelola sandi default untuk menyimpan sandi dan kunci sandi sehingga nantinya Anda dapat login lebih cepat."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci sandi di <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan sandi ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan info login ke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"kunci sandi"</string>
     <string name="password" msgid="6738570945182936667">"sandi"</string>
     <string name="sign_ins" msgid="4710739369149469208">"login"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Buat kunci sandi di"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Simpan sandi ke"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Simpan info login ke"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Buat kunci sandi di perangkat lain?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua info login Anda?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Pengelola sandi ini akan menyimpan sandi dan kunci sandi untuk membantu Anda login dengan mudah."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Setel sebagai default"</string>
     <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> sandi, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> kunci sandi"</string>
diff --git a/packages/CredentialManager/res/values-is/strings.xml b/packages/CredentialManager/res/values-is/strings.xml
index 3fd6af2..f34dd69e 100644
--- a/packages/CredentialManager/res/values-is/strings.xml
+++ b/packages/CredentialManager/res/values-is/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Skilríkjastjórnun"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Hætta við"</string>
     <string name="string_continue" msgid="1346732695941131882">"Áfram"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Búa til annarsstaðar"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Vista annarsstaðar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Nota annað tæki"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Vista í öðru tæki"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Einföld leið við örugga innskráningu"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Notaðu fingrafar, andlit eða skjálás til að skrá þig inn með einkvæmum aðgangslykli sem ekki er hægt að gleyma eða stela. Nánar"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Veldu hvar á að <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Veldu hvar á að <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"búa til aðgangslykla"</string>
     <string name="save_your_password" msgid="6597736507991704307">"vistaðu aðgangsorðið"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"vistaðu innskráningarupplýsingarnar"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Stilltu sjálfgefna aðgangsorðastjórnun til að vista aðgangsorð og aðgangslykla og skrá þig hraðar inn næst."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Búa til aðgangslykil í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vista aðgangsorð í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vista innskráningarupplýsingar í <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"aðgangslykill"</string>
     <string name="password" msgid="6738570945182936667">"aðgangsorð"</string>
     <string name="sign_ins" msgid="4710739369149469208">"innskráningar"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Búa til aðgangslykil í"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Vista aðgangsorð á"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Vista innskráningu á"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Viltu búa til aðgangslykil í öðru tæki?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Nota <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> fyrir allar innskráningar?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Þessi aðgangsorðastjórnun vistar aðgangsorð og aðgangslykla til að auðvelda þér að skrá þig inn."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Stilla sem sjálfgefið"</string>
     <string name="use_once" msgid="9027366575315399714">"Nota einu sinni"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> aðgangslyklar"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> aðgangsorð"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> aðgangslyklar"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Aðgangslykill"</string>
     <string name="another_device" msgid="5147276802037801217">"Annað tæki"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Önnur aðgangsorðastjórnun"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Loka blaði"</string>
diff --git a/packages/CredentialManager/res/values-it/strings.xml b/packages/CredentialManager/res/values-it/strings.xml
index 3a7b0fb..ddb2f3a 100644
--- a/packages/CredentialManager/res/values-it/strings.xml
+++ b/packages/CredentialManager/res/values-it/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Salva in un altro luogo"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usa un altro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Salva su un altro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un modo semplice per accedere in sicurezza"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Usa l\'impronta digitale, il volto o il blocco schermo per accedere con una passkey unica che non può essere dimenticata o rubata. Scopri di più"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Scegli dove <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Scegli dove <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"Crea le tue passkey"</string>
     <string name="save_your_password" msgid="6597736507991704307">"salva la password"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"salva le tue informazioni di accesso"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Imposta un gestore delle password predefinito per salvare le tue password e passkey in modo da poter accedere più velocemente la prossima volta."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vuoi creare una passkey su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vuoi salvare la password su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vuoi salvare le informazioni di accesso su <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
     <string name="sign_ins" msgid="4710739369149469208">"accessi"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Crea passkey su"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Salva password su"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Salva accesso su"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vuoi creare una passkey su un altro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vuoi usare <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> per tutti gli accessi?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Questo gestore delle password archivierà le password e le passkey per aiutarti ad accedere facilmente."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Imposta come valore predefinito"</string>
     <string name="use_once" msgid="9027366575315399714">"Usa una volta"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
diff --git a/packages/CredentialManager/res/values-iw/strings.xml b/packages/CredentialManager/res/values-iw/strings.xml
index 397ad60..50752eb 100644
--- a/packages/CredentialManager/res/values-iw/strings.xml
+++ b/packages/CredentialManager/res/values-iw/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"שמירה במקום אחר"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"שימוש במכשיר אחר"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"שמירה במכשיר אחר"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"דרך פשוטה להיכנס לחשבון בבטחה"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"אפשר להשתמש בטביעת אצבע, בזיהוי פנים או בנעילת מסך כדי להיכנס לחשבון עם מפתח גישה ייחודי שאי אפשר לשכוח או לגנוב אותו. מידע נוסף"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"צריך לבחור לאן <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"צריך לבחור לאן <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"יצירת מפתחות גישה"</string>
     <string name="save_your_password" msgid="6597736507991704307">"שמירת הסיסמה שלך"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"שמירת פרטי הכניסה שלך"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"יש לקבוע את מנהל הסיסמאות שיוגדר כברירת מחדל כדי לשמור את הסיסמאות ומפתחות הגישה, ולהיכנס לחשבון מהר יותר בפעם הבאה."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ליצור מפתח גישה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"לשמור את הסיסמה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"לשמור את פרטי הכניסה ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"מפתח גישה"</string>
     <string name="password" msgid="6738570945182936667">"סיסמה"</string>
     <string name="sign_ins" msgid="4710739369149469208">"פרטי כניסה"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"יצירת מפתח גישה ב"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"שמירת הסיסמה של"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"שמירת פרטי הכניסה של"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"ליצור מפתח גישה במכשיר אחר?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"להשתמש ב-<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> בכל הכניסות?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"במנהל הסיסמאות הזה יאוחסנו הסיסמאות ומפתחות הגישה שלך, כדי לעזור לך להיכנס לחשבון בקלות."</string>
     <string name="set_as_default" msgid="4415328591568654603">"הגדרה כברירת מחדל"</string>
     <string name="use_once" msgid="9027366575315399714">"שימוש פעם אחת"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> סיסמאות, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> מפתחות גישה"</string>
diff --git a/packages/CredentialManager/res/values-ja/strings.xml b/packages/CredentialManager/res/values-ja/strings.xml
index 0340b66..783a444 100644
--- a/packages/CredentialManager/res/values-ja/strings.xml
+++ b/packages/CredentialManager/res/values-ja/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"別の場所に保存"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"別のデバイスを使用"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"他のデバイスに保存"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全にログインする簡単な方法"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"忘れたり盗まれたりする可能性がある一意のパスキーと合わせて、ログインに指紋認証、顔認証、画面ロックを使用できます。詳細"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> の保存場所の選択"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> の保存場所の選択"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"パスキーの作成"</string>
     <string name="save_your_password" msgid="6597736507991704307">"パスワードを保存"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ログイン情報を保存"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"デフォルトのパスワード マネージャーを設定すると、パスワードやパスキーを保存して、次回から素早くログインできるようになります。"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> でパスキーを作成しますか?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にパスワードを保存しますか?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> にログイン情報を保存しますか?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"パスキー"</string>
     <string name="password" msgid="6738570945182936667">"パスワード"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ログイン"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"パスキーの作成先"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"パスワードの保存先"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ログイン情報の保存先"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"他のデバイスでパスキーを作成しますか?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ログインのたびに <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> を使用しますか?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"このパスワード マネージャーに、パスワードやパスキーが保存され、簡単にログインできるようになります。"</string>
     <string name="set_as_default" msgid="4415328591568654603">"デフォルトに設定"</string>
     <string name="use_once" msgid="9027366575315399714">"1 回使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 件のパスワード、<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 件のパスキー"</string>
diff --git a/packages/CredentialManager/res/values-ka/strings.xml b/packages/CredentialManager/res/values-ka/strings.xml
index 3da7ea3..eba01d4 100644
--- a/packages/CredentialManager/res/values-ka/strings.xml
+++ b/packages/CredentialManager/res/values-ka/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"სხვა სივრცეში შენახვა"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"სხვა მოწყობილობის გამოყენება"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"სხვა მოწყობილობაზე შენახვა"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"უსაფრთხოდ შესვლის მარტივი გზა"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"გამოიყენეთ თქვენი თითის ანაბეჭდი, სახის ამოცნობა და ეკრანის დაბლოკვა სისტემაში უნიკალური წვდომის გასაღებით შესასვლელად, რომლის დავიწყება ან მოპარვა შეუძლებელია. შეიტყვეთ მეტი"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"აირჩიეთ, სად უნდა <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"აირჩიეთ, სად უნდა <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"შექმენით თქვენი პაროლი"</string>
     <string name="save_your_password" msgid="6597736507991704307">"შეინახეთ თქვენი პაროლი"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"შეინახეთ თქვენი სისტემაში შესვლის ინფორმაცია"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"დააყენეთ პაროლის ნაგულისხმევი მენეჯერი, რათა შეინახოთ თქვენი პაროლები და გასაღებები და შეხვიდეთ უფრო სწრაფად შემდეგ ჯერზე."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"გსურთ წვდომის გასაღების შექმნა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"გსურთ თქვენი პაროლის შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"გსურთ თქვენი სისტემაში შესვლის მონაცემების შენახვა <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-ში?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"წვდომის გასაღები"</string>
     <string name="password" msgid="6738570945182936667">"პაროლი"</string>
     <string name="sign_ins" msgid="4710739369149469208">"სისტემაში შესვლა"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"წვდომის გასაღების შექმნა"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"პაროლის შენახვა"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"შესვლის შენახვა"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"გსურთ პაროლის შექმნა სხვა მოწყობილობაში?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"გსურთ, გამოიყენოთ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> სისტემაში ყველა შესვლისთვის?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"მოცემული პაროლების მმართველი შეინახავს თქვენს პაროლებს და წვდომის გასაღებს, რომლებიც დაგეხმარებათ სისტემაში მარტივად შესვლაში."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ნაგულისხმევად დაყენება"</string>
     <string name="use_once" msgid="9027366575315399714">"ერთხელ გამოყენება"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> პაროლი, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> წვდომის გასაღები"</string>
diff --git a/packages/CredentialManager/res/values-kk/strings.xml b/packages/CredentialManager/res/values-kk/strings.xml
index 9491f8e..d7d255b 100644
--- a/packages/CredentialManager/res/values-kk/strings.xml
+++ b/packages/CredentialManager/res/values-kk/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Тіркелу деректері менеджері"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Бас тарту"</string>
     <string name="string_continue" msgid="1346732695941131882">"Жалғастыру"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Басқа орында жасау"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Басқа орынға сақтау"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Басқа құрылғыны пайдалану"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Басқа құрылғыға сақтау"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Қауіпсіз кірудің оңай жолы"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Ұмытылмайтын немесе ұрланбайтын бірегей кіру кілтінің көмегімен кіру үшін саусақ ізін, бетті анықтау функциясын немесе экран құлпын пайдаланыңыз. Толық ақпарат"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> таңдау"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"кіру кілттерін жасаңыз"</string>
     <string name="save_your_password" msgid="6597736507991704307">"құпия сөзді сақтау"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"тіркелу деректерін сақтау"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Құпия сөздер мен кіру кілттерін сақтап, келесі рет жылдам кіру үшін әдепкі құпия сөз менеджерін орнатыңыз."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасында кіру кілті жасалсын ба?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына құпия сөз сақталсын ба?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> қолданбасына тіркелу деректері сақталсын ба?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"кіру кілті"</string>
     <string name="password" msgid="6738570945182936667">"құпия сөз"</string>
     <string name="sign_ins" msgid="4710739369149469208">"кіру әрекеттері"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Кіру кілтін жасау"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Құпия сөзді келесіге сақтау:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Кіру деректерін келесіге сақтау:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Кіру кілті басқа құрылғыда жасалсын ба?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Барлық кіру әрекеті үшін <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> пайдаланылсын ба?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Аккаунтқа оңай кіру үшін құпия сөз менеджері құпия сөздер мен кіру кілттерін сақтайды."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Әдепкі етіп орнату"</string>
     <string name="use_once" msgid="9027366575315399714">"Бір рет пайдалану"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> кіру кілті"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> құпия сөз"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> кіру кілті"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Кіру кілті"</string>
     <string name="another_device" msgid="5147276802037801217">"Басқа құрылғы"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Басқа құпия сөз менеджерлері"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Парақты жабу"</string>
diff --git a/packages/CredentialManager/res/values-km/strings.xml b/packages/CredentialManager/res/values-km/strings.xml
index 80167fc..b104661 100644
--- a/packages/CredentialManager/res/values-km/strings.xml
+++ b/packages/CredentialManager/res/values-km/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"កម្មវិធី​គ្រប់គ្រង​ព័ត៌មាន​ផ្ទៀងផ្ទាត់"</string>
     <string name="string_cancel" msgid="6369133483981306063">"បោះបង់"</string>
     <string name="string_continue" msgid="1346732695941131882">"បន្ត"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"បង្កើតនៅកន្លែងផ្សេងទៀត"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"រក្សាទុកក្នុងកន្លែងផ្សេងទៀត"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ប្រើ​ឧបករណ៍​ផ្សេងទៀត"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"រក្សាទុកទៅក្នុងឧបករណ៍ផ្សេង"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"វិធីដ៏សាមញ្ញ ដើម្បីចូលគណនីដោយសុវត្ថិភាព"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ប្រើស្នាមម្រាមដៃ មុខ ឬការចាក់សោអេក្រង់របស់អ្នក ដើម្បីចូលគណនីដោយប្រើកូដសម្ងាត់ខុសប្លែកពីគេដែលមិនអាចភ្លេច ឬត្រូវគេលួច។ ស្វែងយល់បន្ថែម"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"ជ្រើសរើសកន្លែងដែលត្រូវ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"ជ្រើសរើសកន្លែងដែលត្រូវ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"បង្កើត​កូដសម្ងាត់របស់អ្នក"</string>
     <string name="save_your_password" msgid="6597736507991704307">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នក"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នក"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"កំណត់​កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់​លំនាំដើម ដើម្បីរក្សាទុក​ពាក្យសម្ងាត់និង​កូដសម្ងាត់របស់អ្នក និងចូលគណនី​កាន់តែរហ័ស​នៅពេលលើកក្រោយ។"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"បង្កើតកូដសម្ងាត់នៅក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"រក្សាទុកពាក្យសម្ងាត់របស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"រក្សាទុកព័ត៌មានចូលគណនីរបស់អ្នកក្នុង <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ឬ?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"កូដសម្ងាត់"</string>
     <string name="password" msgid="6738570945182936667">"ពាក្យសម្ងាត់"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ការចូល​គណនី"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"បង្កើតកូដសម្ងាត់នៅក្នុង"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"រក្សាទុក​ពាក្យសម្ងាត់ទៅ"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"រក្សាទុក​ការចូល​គណនីទៅ"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"បង្កើតកូដសម្ងាត់​នៅក្នុងឧបករណ៍​ផ្សេងទៀតឬ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ប្រើ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> សម្រាប់ការចូលគណនីទាំងអស់របស់អ្នកឬ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់នេះ​នឹងរក្សាទុកពាក្យសម្ងាត់ និងកូដសម្ងាត់​របស់អ្នក ដើម្បីជួយឱ្យអ្នក​ចូលគណនី​បានយ៉ាងងាយស្រួល។"</string>
     <string name="set_as_default" msgid="4415328591568654603">"កំណត់ជាលំនាំដើម"</string>
     <string name="use_once" msgid="9027366575315399714">"ប្រើម្ដង"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"ពាក្យសម្ងាត់ <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"កូដសម្ងាត់ <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"កូដសម្ងាត់"</string>
     <string name="another_device" msgid="5147276802037801217">"ឧបករណ៍​ផ្សេងទៀត"</string>
     <string name="other_password_manager" msgid="565790221427004141">"កម្មវិធីគ្រប់គ្រងពាក្យសម្ងាត់ផ្សេងទៀត"</string>
     <string name="close_sheet" msgid="1393792015338908262">"បិទសន្លឹក"</string>
diff --git a/packages/CredentialManager/res/values-kn/strings.xml b/packages/CredentialManager/res/values-kn/strings.xml
index 96304ac..033eec1 100644
--- a/packages/CredentialManager/res/values-kn/strings.xml
+++ b/packages/CredentialManager/res/values-kn/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"ಮತ್ತೊಂದು ಸ್ಥಳದಲ್ಲಿ ಉಳಿಸಿ"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ಬೇರೊಂದು ಸಾಧನವನ್ನು ಬಳಸಿ"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ಬೇರೊಂದು ಸಾಧನದಲ್ಲಿ ಉಳಿಸಿ"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ಸುರಕ್ಷಿತವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವ ಸುಲಭ ವಿಧಾನ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ಮರೆಯಲಾಗದ ಅಥವಾ ಕದಿಯಲಾಗದ ಅನನ್ಯ ಪಾಸ್‌ಕೀ ಮೂಲಕ ಸೈನ್ ಇನ್ ಮಾಡಲು ನಿಮ್ಮ ಫಿಂಗರ್‌ಪ್ರಿಂಟ್, ಫೇಸ್ ಲಾಕ್ ಅಥವಾ ಸ್ಕ್ರೀನ್ ಲಾಕ್ ಬಳಸಿ. ಇನ್ನಷ್ಟು ತಿಳಿಯಿರಿ"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ಅನ್ನು ಎಲ್ಲಿ ಉಳಿಸಬೇಕು ಎಂದು ಆಯ್ಕೆಮಾಡಿ"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ಅನ್ನು ಎಲ್ಲಿ ಉಳಿಸಬೇಕು ಎಂದು ಆಯ್ಕೆಮಾಡಿ"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ನಿಮ್ಮ ಪಾಸ್‌ಕೀಗಳನ್ನು ರಚಿಸಿ"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ ಉಳಿಸಿ"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ನಿಮ್ಮ ಸೈನ್-ಇನ್ ಮಾಹಿತಿ ಉಳಿಸಿ"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಮತ್ತು ಪಾಸ್‌ಕೀಗಳನ್ನು ಉಳಿಸಲು ಡೀಫಾಲ್ಟ್ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವನ್ನು ಸೆಟ್ ಮಾಡಿ ಹಾಗೂ ಮುಂದಿನ ಬಾರಿ ವೇಗವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡಿ."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ನಲ್ಲಿ ಪಾಸ್‌ಕೀ ರಚಿಸಬೇಕೆ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ನಿಮ್ಮ ಸೈನ್ ಇನ್ ಮಾಹಿತಿಯನ್ನು <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಗೆ ಉಳಿಸಬೇಕೆ?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"ಪಾಸ್‌ಕೀ"</string>
     <string name="password" msgid="6738570945182936667">"ಪಾಸ್‌ವರ್ಡ್"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ಸೈನ್-ಇನ್‌ಗಳು"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ಇಲ್ಲಿ ಪಾಸ್‌ಕೀ ರಚಿಸಿ"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ಇಲ್ಲಿಗೆ ಪಾಸ್‌ವರ್ಡ್ ಉಳಿಸಿ"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ಇಲ್ಲಿಗೆ ಸೈನ್-ಇನ್ ಮಾಹಿತಿ ಉಳಿಸಿ"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"ಮತ್ತೊಂದು ಸಾಧನದಲ್ಲಿ ಪಾಸ್‌ಕೀ ರಚಿಸಬೇಕೆ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ನಿಮ್ಮ ಎಲ್ಲಾ ಸೈನ್-ಇನ್‌ಗಳಿಗಾಗಿ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ಅನ್ನು ಬಳಸುವುದೇ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"ಈ ಪಾಸ್‌ವರ್ಡ್ ನಿರ್ವಾಹಕವು ನಿಮಗೆ ಸುಲಭವಾಗಿ ಸೈನ್ ಇನ್ ಮಾಡುವುದಕ್ಕೆ ಸಹಾಯ ಮಾಡಲು ನಿಮ್ಮ ಪಾಸ್‌ವರ್ಡ್‌ಗಳು ಮತ್ತು ಪಾಸ್‌ಕೀಗಳನ್ನು ಸಂಗ್ರಹಿಸುತ್ತದೆ."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ಡೀಫಾಲ್ಟ್ ಆಗಿ ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="use_once" msgid="9027366575315399714">"ಒಂದು ಬಾರಿ ಬಳಸಿ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ಪಾಸ್‌ವರ್ಡ್‌ಗಳು, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ಪಾಸ್‌ಕೀಗಳು"</string>
diff --git a/packages/CredentialManager/res/values-ko/strings.xml b/packages/CredentialManager/res/values-ko/strings.xml
index 58518c1..44f22d7 100644
--- a/packages/CredentialManager/res/values-ko/strings.xml
+++ b/packages/CredentialManager/res/values-ko/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"인증서 관리자"</string>
     <string name="string_cancel" msgid="6369133483981306063">"취소"</string>
     <string name="string_continue" msgid="1346732695941131882">"계속"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"다른 위치에 만들기"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"다른 위치에 저장"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"다른 기기 사용"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"다른 기기에 저장"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"안전하게 로그인하는 간단한 방법"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"지문, 얼굴 인식 또는 화면 잠금을 통해 잊어버리거나 분실할 염려가 없는 고유한 패스키로 로그인하세요. 자세히 알아보기"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 작업을 위한 위치 선택"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> 작업을 위한 위치 선택"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"패스키 만들기"</string>
     <string name="save_your_password" msgid="6597736507991704307">"비밀번호 저장"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"로그인 정보 저장"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"기본 비밀번호 관리자를 설정하여 비밀번호와 패스키를 저장하고 다음에 더 빠르게 로그인하세요."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 패스키를 만드시겠습니까?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 비밀번호를 저장하시겠습니까?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>에 로그인 정보를 저장하시겠습니까?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"패스키"</string>
     <string name="password" msgid="6738570945182936667">"비밀번호"</string>
     <string name="sign_ins" msgid="4710739369149469208">"로그인 정보"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"다음 위치에 패스키 만들기"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"비밀번호를 다음에 저장"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"로그인 정보를 다음에 저장"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"다른 기기에서 패스키를 만들까요?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"모든 로그인에 <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>을(를) 사용하시겠습니까?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"이 비밀번호 관리자는 비밀번호와 패스키를 저장하여 사용자가 간편하게 로그인하도록 돕습니다."</string>
     <string name="set_as_default" msgid="4415328591568654603">"기본값으로 설정"</string>
     <string name="use_once" msgid="9027366575315399714">"한 번 사용"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개, 패스키 <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>개"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"비밀번호 <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>개"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"패스키 <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>개"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"패스키"</string>
     <string name="another_device" msgid="5147276802037801217">"다른 기기"</string>
     <string name="other_password_manager" msgid="565790221427004141">"기타 비밀번호 관리자"</string>
     <string name="close_sheet" msgid="1393792015338908262">"시트 닫기"</string>
diff --git a/packages/CredentialManager/res/values-ky/strings.xml b/packages/CredentialManager/res/values-ky/strings.xml
index 298657e..c4621cf 100644
--- a/packages/CredentialManager/res/values-ky/strings.xml
+++ b/packages/CredentialManager/res/values-ky/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Жок"</string>
     <string name="string_continue" msgid="1346732695941131882">"Улантуу"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Башка жерде түзүү"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Башка жерге сактоо"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Башка түзмөк колдонуу"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Башка түзмөккө сактоо"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Коопсуз кирүүнүн жөнөкөй жолу"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Унутуп калууга же уурдатууга мүмкүн эмес болгон уникалдуу ачкыч менен манжа изин, жүзүнөн таанып ачуу же экранды кулпулоо функцияларын колдонуп өзүңүздү ырастай аласыз. Кененирээк"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> үчүн жер тандаңыз"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"мүмкүндүк алуу ачкычтарын түзүү"</string>
     <string name="save_your_password" msgid="6597736507991704307">"сырсөзүңүздү сактаңыз"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"кирүү маалыматын сактаңыз"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Сырсөздөр менен мүмкүндүк алуу ачкычтары сактала турган демейки сырсөздөрдү башкаргычты тандап, кийинки жолу аккаунтуңузга тезирээк кириңиз."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда мүмкүндүк алуу ачкычын түзөсүзбү?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сырсөзүңүздү <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Кирүү маалыматын <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> колдонмосунда сактайсызбы?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"мүмкүндүк алуу ачкычы"</string>
     <string name="password" msgid="6738570945182936667">"сырсөз"</string>
     <string name="sign_ins" msgid="4710739369149469208">"кирүүлөр"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Мүмкүндүк алуу ачкычын бул жерден түзүү:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Сырсөздү каякка сактайсыз:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Аккаунтка кирүү маалыматын каякка сактайсыз:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Мүмкүндүк алуу ачкычы башка түзмөктө түзүлсүнбү?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> бардык аккаунттарга кирүү үчүн колдонулсунбу?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Сырсөздөрүңүздү жана ачкычтарыңызды Сырсөздөрдү башкаргычка сактап коюп, каалаган убакта колдоно берсеңиз болот."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Демейки катары коюу"</string>
     <string name="use_once" msgid="9027366575315399714">"Бир жолу колдонуу"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> сырсөз"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> мүмкүндүк алуу ачкычы"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Мүмкүндүк алуу ачкычы"</string>
     <string name="another_device" msgid="5147276802037801217">"Башка түзмөк"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Башка сырсөздөрдү башкаргычтар"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Баракты жабуу"</string>
diff --git a/packages/CredentialManager/res/values-lo/strings.xml b/packages/CredentialManager/res/values-lo/strings.xml
index 215262b..7429389 100644
--- a/packages/CredentialManager/res/values-lo/strings.xml
+++ b/packages/CredentialManager/res/values-lo/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"ຕົວຈັດການຂໍ້ມູນການເຂົ້າສູ່ລະບົບ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ຍົກເລີກ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ສືບຕໍ່"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"ສ້າງໃນບ່ອນອື່ນ"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"ບັນທຶກໃສ່ບ່ອນອື່ນ"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ໃຊ້ອຸປະກອນອື່ນ"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ບັນທຶກໃສ່ອຸປະກອນອື່ນ"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ວິທີງ່າຍໆໃນການເຂົ້າສູ່ລະບົບຢ່າງປອດໄພ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ໃຊ້ລາຍນິ້ວມື, ໃບໜ້າ ຫຼື ລັອກໜ້າຈໍຂອງທ່ານເພື່ອເຂົ້າສູ່ລະບົບດ້ວຍກະແຈຜ່ານທີ່ບໍ່ຊ້ຳກັນເພື່ອບໍ່ໃຫ້ລືມ ຫຼື ຖືກລັກໄດ້. ສຶກສາເພີ່ມເຕີມ"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"ເລືອກບ່ອນທີ່ຈະ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"ເລືອກບ່ອນທີ່ຈະ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ສ້າງກະແຈຜ່ານຂອງທ່ານ"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານ"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານ"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ຕັ້ງຄ່າເລີ່ມຕົ້ນຂອງຕົວຈັດການລະຫັດຜ່ານເພື່ອບັນທຶກລະຫັດຜ່ານ ແລະ ກະແຈຜ່ານຂອງທ່ານ ແລະ ເຂົ້າສູ່ລະບົບໄດ້ໄວຂຶ້ນໃນເທື່ອຕໍ່ໄປ."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ສ້າງກະແຈຜ່ານໃນ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"ບັນທຶກລະຫັດຜ່ານຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ບັນທຶກຂໍ້ມູນການເຂົ້າສູ່ລະບົບຂອງທ່ານໃສ່ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ບໍ?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ກະແຈຜ່ານ"</string>
     <string name="password" msgid="6738570945182936667">"ລະຫັດຜ່ານ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ການເຂົ້າສູ່ລະບົບ"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ສ້າງກະແຈຜ່ານໃນ"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ບັນທຶກລະຫັດຜ່ານໃສ່"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ບັນທຶກການເຂົ້າສູ່ລະບົບໃສ່"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"ສ້າງກະແຈຜ່ານໃນອຸປະກອນອື່ນບໍ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ໃຊ້ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ສຳລັບການເຂົ້າສູ່ລະບົບທັງໝົດຂອງທ່ານບໍ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"ຕົວຈັດການລະຫັດຜ່ານນີ້ຈະຈັດເກັບລະຫັດຜ່ານ ແລະ ກະແຈຜ່ານຂອງທ່ານໄວ້ເພື່ອຊ່ວຍໃຫ້ທ່ານເຂົ້າສູ່ລະບົບໄດ້ໂດຍງ່າຍ."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ"</string>
     <string name="use_once" msgid="9027366575315399714">"ໃຊ້ເທື່ອດຽວ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ກະແຈຜ່ານ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ລະຫັດຜ່ານ"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ກະແຈຜ່ານ"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"ກະແຈຜ່ານ"</string>
     <string name="another_device" msgid="5147276802037801217">"ອຸປະກອນອື່ນ"</string>
     <string name="other_password_manager" msgid="565790221427004141">"ຕົວຈັດການລະຫັດຜ່ານອື່ນໆ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ປິດຊີດ"</string>
diff --git a/packages/CredentialManager/res/values-lt/strings.xml b/packages/CredentialManager/res/values-lt/strings.xml
index 6125fe3..aa5d97a 100644
--- a/packages/CredentialManager/res/values-lt/strings.xml
+++ b/packages/CredentialManager/res/values-lt/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Išsaugoti kitoje vietoje"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Naudoti kitą įrenginį"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Išsaugoti kitame įrenginyje"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Paprastas saugaus prisijungimo metodas"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Naudodami piršto atspaudą, veidą ar ekrano užraktą prisijunkite su unikaliu „passkey“, kurio neįmanoma pamiršti ar pavogti. Sužinokite daugiau"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Pasirinkite, kur <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pasirinkite, kur <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"kurkite slaptažodžius"</string>
     <string name="save_your_password" msgid="6597736507991704307">"išsaugoti slaptažodį"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"išsaugoti prisijungimo informaciją"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Nustatykite numatytąją slaptažodžių tvarkyklę, kad išsaugotumėte slaptažodžius ir kitą kartą galėtumėte sparčiau prisijungti."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Sukurti „passkey“ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Išsaugoti slaptažodį <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Išsaugoti prisijungimo informaciją <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"„passkey“"</string>
     <string name="password" msgid="6738570945182936667">"slaptažodis"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prisijungimo informacija"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Slaptažodžio kūrimas"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Išsaugoti slaptažodį"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Išsaugoti prisijungimo informaciją"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Sukurti slaptažodį kitame įrenginyje?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Naudoti <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> visada prisijungiant?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Šioje slaptažodžių tvarkyklėje bus saugomi jūsų slaptažodžiai, kad galėtumėte lengvai prisijungti."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nustatyti kaip numatytąjį"</string>
     <string name="use_once" msgid="9027366575315399714">"Naudoti vieną kartą"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"slaptažodžių: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, „passkey“: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-lv/strings.xml b/packages/CredentialManager/res/values-lv/strings.xml
index 43c036f..06446ab 100644
--- a/packages/CredentialManager/res/values-lv/strings.xml
+++ b/packages/CredentialManager/res/values-lv/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Saglabāt citur"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Izmantot citu ierīci"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Saglabāt citā ierīcē"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Vienkāršs veids, kā droši pierakstīties"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Izmantojiet pirksta nospiedumu, autorizāciju pēc sejas vai ekrāna bloķēšanu, lai pierakstītos ar unikālu piekļuves atslēgu, ko nevar aizmirst vai nozagt. Uzziniet vairāk."</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Izvēlieties, kur: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"veidot piekļuves atslēgas"</string>
     <string name="save_your_password" msgid="6597736507991704307">"saglabāt paroli"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"saglabāt pierakstīšanās informāciju"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Iestatiet noklusējuma paroļu pārvaldnieku, lai saglabātu paroles un piekļuves atslēgas un nākamajā reizē pierakstītos ātrāk."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vai izveidot piekļuves atslēgu šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vai saglabāt paroli šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vai saglabāt pierakstīšanās informāciju šeit: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"piekļuves atslēga"</string>
     <string name="password" msgid="6738570945182936667">"parole"</string>
     <string name="sign_ins" msgid="4710739369149469208">"pierakstīšanās informācija"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Izveidot piekļuves atslēgu šeit:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Saglabāt paroli šeit:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Saglabāt pierakstīšanās informāciju šeit:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vai izveidot piekļuves atslēgu citā ierīcē?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vai vienmēr izmantot <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>, lai pierakstītos?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Šis paroļu pārvaldnieks glabās jūsu paroles un piekļuves atslēgas, lai atvieglotu pierakstīšanos."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Iestatīt kā noklusējumu"</string>
     <string name="use_once" msgid="9027366575315399714">"Izmantot vienreiz"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> paroles, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> piekļuves atslēgas"</string>
diff --git a/packages/CredentialManager/res/values-mk/strings.xml b/packages/CredentialManager/res/values-mk/strings.xml
index 059f042..8093d74 100644
--- a/packages/CredentialManager/res/values-mk/strings.xml
+++ b/packages/CredentialManager/res/values-mk/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Зачувајте на друго место"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Употребете друг уред"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Зачувајте на друг уред"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Едноставен начин за безбедно најавување"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користете го отпечатокот, заклучувањето со лик или заклучувањето екран за да се најавите со единствен криптографски клуч што не може да се заборави или украде. Дознајте повеќе"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете каде да <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Изберете каде да <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"создајте криптографски клучеви"</string>
     <string name="save_your_password" msgid="6597736507991704307">"се зачува лозинката"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"се зачуваат податоците за најавување"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Поставете стандарден управник со лозинки за да ги складира вашите лозинки и криптографски клучеви и најавете се побрзо следниот пат."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Да се создаде криптографски клуч во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Да се зачува вашата лозинка во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Да се зачуваат вашите податоци за најавување во <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"криптографски клуч"</string>
     <string name="password" msgid="6738570945182936667">"лозинка"</string>
     <string name="sign_ins" msgid="4710739369149469208">"најавувања"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Создајте криптографски клуч во"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Зачувајте ја лозинката во"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Зачувајте го најавувањето во"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Да се создаде криптографски клуч во друг уред?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Да се користи <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> за сите ваши најавувања?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Овој управник со лозинки ќе ги складира вашите лозинки и криптографски клучеви за да ви помогне лесно да се најавите."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Постави како стандардна опција"</string>
     <string name="use_once" msgid="9027366575315399714">"Употребете еднаш"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> лозинки, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> криптографски клучеви"</string>
diff --git a/packages/CredentialManager/res/values-ml/strings.xml b/packages/CredentialManager/res/values-ml/strings.xml
index e4f6d69..68a6419 100644
--- a/packages/CredentialManager/res/values-ml/strings.xml
+++ b/packages/CredentialManager/res/values-ml/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"മറ്റൊരു സ്ഥലത്തേക്ക് സംരക്ഷിക്കുക"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"മറ്റൊരു ഉപകരണം ഉപയോഗിക്കുക"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"മറ്റൊരു ഉപകരണത്തിലേക്ക് സംരക്ഷിക്കുക"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"സുരക്ഷിതമായി സൈൻ ഇൻ ചെയ്യാനുള്ള ലളിതമായ മാർഗ്ഗം"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"മറന്നുപോകാനും മോഷ്‌ടിക്കാനും സാധ്യതയില്ലാത്ത തനത് പാസ്‌കീ ഉപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യാൻ നിങ്ങളുടെ ഫിംഗർപ്രിന്റോ മുഖമോ സ്‌ക്രീൻ ലോക്കോ ഉപയോഗിക്കുക. കൂടുതലറിയുക"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"എവിടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എന്ന് തിരഞ്ഞെടുക്കുക"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"എവിടെ <xliff:g id="CREATETYPES">%1$s</xliff:g> എന്ന് തിരഞ്ഞെടുക്കുക"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"നിങ്ങളുടെ പാസ്‌കീകൾ സൃഷ്‌ടിക്കുക"</string>
     <string name="save_your_password" msgid="6597736507991704307">"നിങ്ങളുടെ പാസ്‌വേഡ് സംരക്ഷിക്കുക"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"നിങ്ങളുടെ സൈൻ ഇൻ വിവരങ്ങൾ സംരക്ഷിക്കുക"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"നിങ്ങളുടെ പാസ്‌വേഡുകളും പാസ്‌കീകളും സംരക്ഷിക്കാനും അടുത്ത തവണ വേഗത്തിൽ സൈൻ ഇൻ ചെയ്യാനും ഒരു ഡിഫോൾട്ട് പാസ്‌വേഡ് മാനേജർ സജ്ജീകരിക്കുക."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിൽ ഒരു പാസ്‌കീ സൃഷ്‌ടിക്കണോ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"പാസ്‌വേഡ് <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"സൈൻ ഇൻ വിവരങ്ങൾ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> എന്നതിലേക്ക് സംരക്ഷിക്കണോ?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"പാസ്‌കീ"</string>
     <string name="password" msgid="6738570945182936667">"പാസ്‌വേഡ്"</string>
     <string name="sign_ins" msgid="4710739369149469208">"സൈൻ ഇന്നുകൾ"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ഇനിപ്പറയുന്നതിൽ പാസ്‌കീ സൃഷ്‌ടിക്കുക"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"പാസ്‌വേഡ് ഇനിപ്പറയുന്നതിൽ സംരക്ഷിക്കുക"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ഇനിപ്പറയുന്നതിലേക്ക് സൈൻ ഇൻ സംരക്ഷിക്കുക"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"മറ്റൊരു ഉപകരണത്തിൽ പാസ്‌കീ സൃഷ്ടിക്കണോ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"നിങ്ങളുടെ എല്ലാ സൈൻ ഇന്നുകൾക്കും <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ഉപയോഗിക്കണോ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"എളുപ്പത്തിൽ സൈൻ ഇൻ ചെയ്യാൻ സഹായിക്കുന്നതിന് ഈ പാസ്‌വേഡ് മാനേജർ നിങ്ങളുടെ പാസ്‌വേഡുകളും പാസ്‌കീകളും സംഭരിക്കും."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ഡിഫോൾട്ടായി സജ്ജീകരിക്കുക"</string>
     <string name="use_once" msgid="9027366575315399714">"ഒരു തവണ ഉപയോഗിക്കുക"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> പാസ്‌വേഡുകൾ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> പാസ്‌കീകൾ"</string>
diff --git a/packages/CredentialManager/res/values-mn/strings.xml b/packages/CredentialManager/res/values-mn/strings.xml
index 3f8d4ca..40ef5e5 100644
--- a/packages/CredentialManager/res/values-mn/strings.xml
+++ b/packages/CredentialManager/res/values-mn/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Өөр газар хадгалах"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Өөр төхөөрөмж ашиглах"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Өөр төхөөрөмжид хадгалах"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Аюулгүй нэвтрэх энгийн арга"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Мартах эсвэл хулгайд алдах боломжгүй өвөрмөц passkey-н хамт нэвтрэх хурууны хээ, царай эсвэл дэлгэцийн түгжээгээ ашиглана уу. Нэмэлт мэдээлэл авах"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Хаана <xliff:g id="CREATETYPES">%1$s</xliff:g>-г сонгоно уу"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Хаана <xliff:g id="CREATETYPES">%1$s</xliff:g>-г сонгоно уу"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"passkey-үүдээ үүсгэнэ үү"</string>
     <string name="save_your_password" msgid="6597736507991704307">"нууц үгээ хадгалах"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"нэвтрэх мэдээллээ хадгалах"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Нууц үгнүүд болон passkey-г хадгалах болон дараагийн удаа илүү хурдан нэвтрэхийн тулд өгөгдмөл нууц үгний менежерийг тохируулна уу"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д passkey үүсгэх үү?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Нууц үгээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Нэвтрэх мэдээллээ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-д хадгалах уу?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"нууц үг"</string>
     <string name="sign_ins" msgid="4710739369149469208">"нэвтрэлт"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Дараахад passkey үүсгэх"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Нууц үгийг дараахад хадгалах"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Нэвтрэх мэдээллийг дараахад хадгалах"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Өөр төхөөрөмжид passkey үүсгэх үү?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>-г бүх нэвтрэлтдээ ашиглах уу?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Танд хялбархан нэвтрэхэд туслахын тулд энэ нууц үгний менежер таны нууц үг болон passkey-г хадгална."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Өгөгдмөлөөр тохируулах"</string>
     <string name="use_once" msgid="9027366575315399714">"Нэг удаа ашиглах"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> нууц үг, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> passkey"</string>
diff --git a/packages/CredentialManager/res/values-mr/strings.xml b/packages/CredentialManager/res/values-mr/strings.xml
index aa6f253..60c969d 100644
--- a/packages/CredentialManager/res/values-mr/strings.xml
+++ b/packages/CredentialManager/res/values-mr/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"क्रेडेंशियल व्यवस्थापक"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द करा"</string>
     <string name="string_continue" msgid="1346732695941131882">"पुढे सुरू ठेवा"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"दुसऱ्या ठिकाणी तयार करा"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"दुसऱ्या ठिकाणी सेव्ह करा"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"दुसरे डिव्‍हाइस वापरा"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"दुसऱ्या डिव्हाइसवर सेव्ह करा"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षितपणे साइन इन करण्याचा सोपा मार्ग"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"युनिक पासकीसह साइन इन करण्यासाठी तुमचे फिंगरप्रिंट, फेस किंवा स्क्रीन लॉक वापरा, जे विसरता येणार नाही किंवा चोरीला जाणार नाही. अधिक जाणून घ्या"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे करायचे ते निवडा"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> कुठे करायचे ते निवडा"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"तुमच्या पासकी तयार करा"</string>
     <string name="save_your_password" msgid="6597736507991704307">"तुमचा पासवर्ड सेव्ह करा"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"तुमची साइन-इन माहिती सेव्ह करा"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"तुमचे पासवर्ड आणि पासकी सेव्ह करण्यासाठी डीफॉल्ट पासवर्ड मॅनेजर सेट करा व पुढच्या वेळी आणखी जलद साइन इन करा."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मध्ये पासकी तयार करायची का?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"तुमचा पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायचा का?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तुमची साइन-इन माहिती <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> वर सेव्ह करायची का?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
     <string name="sign_ins" msgid="4710739369149469208">"साइन-इन"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"यामध्ये पासकी तयार करा"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"यावर पासवर्ड सेव्ह करा"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"यावर साइन-इन सेव्ह करा"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"दुसऱ्या डिव्हाइसमध्ये पासकी तयार करायची आहे का?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"तुमच्या सर्व साइन-इन साठी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>वापरायचे का?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"तुम्हाला सहजरीत्या साइन इन करण्यात मदत करण्यासाठी हा पासवर्ड व्यवस्थापक तुमचे पासवर्ड आणि पासकी स्टोअर करेल."</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफॉल्ट म्हणून सेट करा"</string>
     <string name="use_once" msgid="9027366575315399714">"एकदा वापरा"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> पासवर्ड"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> पासकी"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"पासकी"</string>
     <string name="another_device" msgid="5147276802037801217">"इतर डिव्हाइस"</string>
     <string name="other_password_manager" msgid="565790221427004141">"इतर पासवर्ड व्यवस्थापक"</string>
     <string name="close_sheet" msgid="1393792015338908262">"शीट बंद करा"</string>
diff --git a/packages/CredentialManager/res/values-ms/strings.xml b/packages/CredentialManager/res/values-ms/strings.xml
index d5f8c0e..a590e95 100644
--- a/packages/CredentialManager/res/values-ms/strings.xml
+++ b/packages/CredentialManager/res/values-ms/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Simpan di tempat lain"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Gunakan peranti lain"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Simpan kepada peranti lain"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cara mudah untuk log masuk dengan selamat"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gunakan cap jari, wajah atau kunci skrin anda untuk log masuk menggunakan kunci laluan unik yang tidak boleh dilupakan atau dicuri. Ketahui lebih lanjut"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Pilih tempat untuk <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"buat kunci laluan anda"</string>
     <string name="save_your_password" msgid="6597736507991704307">"simpan kata laluan anda"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"simpan maklumat log masuk anda"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Tetapkan pengurus kata laluan lalai untuk menyimpan kata laluan dan kunci laluan dan log masuk dengan lebih pantas pada masa akan datang."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Buat kunci laluan dalam <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Simpan kata laluan anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Simpan maklumat log masuk anda pada <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"kunci laluan"</string>
     <string name="password" msgid="6738570945182936667">"kata laluan"</string>
     <string name="sign_ins" msgid="4710739369149469208">"log masuk"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Buat kunci laluan dalam"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Simpan kata laluan pada"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Simpan maklumat log masuk pada"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Buat kunci laluan dalam peranti lain?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gunakan <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> untuk semua log masuk anda?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Pengurus kata laluan ini akan menyimpan kata laluan dan kunci laluan anda untuk membantu anda log masuk dengan mudah."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Tetapkan sebagai lalai"</string>
     <string name="use_once" msgid="9027366575315399714">"Gunakan sekali"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Kata laluan <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, kunci laluan <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-my/strings.xml b/packages/CredentialManager/res/values-my/strings.xml
index eda2f741..c7c88a7 100644
--- a/packages/CredentialManager/res/values-my/strings.xml
+++ b/packages/CredentialManager/res/values-my/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"အထောက်အထားမန်နေဂျာ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"မလုပ်တော့"</string>
     <string name="string_continue" msgid="1346732695941131882">"ရှေ့ဆက်ရန်"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"နောက်တစ်နေရာတွင် ပြုလုပ်ရန်"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"နောက်တစ်နေရာတွင် သိမ်းရန်"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"စက်နောက်တစ်ခု သုံးရန်"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"စက်နောက်တစ်ခုတွင် သိမ်းရန်"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"လုံခြုံစွာလက်မှတ်ထိုးဝင်ရန် ရိုးရှင်းသောနည်း"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"မေ့မသွား (သို့) ခိုးမသွားနိုင်သော သီးခြားလျှို့ဝှက်ကီးဖြင့် လက်မှတ်ထိုးဝင်ရန် သင့်လက်ဗွေ၊ မျက်နှာ (သို့) ဖန်သားပြင်လော့ခ် သုံးနိုင်သည်။ ပိုမိုလေ့လာရန်"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ရန် နေရာရွေးပါ"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ရန် နေရာရွေးပါ"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"သင့်လျှို့ဝှက်ကီး ပြုလုပ်ခြင်း"</string>
     <string name="save_your_password" msgid="6597736507991704307">"သင့်စကားဝှက် သိမ်းရန်"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို သိမ်းရန်"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"သင့်စကားဝှက်၊ လျှို့ဝှက်ကီးများ သိမ်းဆည်းရန်နှင့် နောက်တစ်ကြိမ်တွင် မြန်ဆန်စွာ လက်မှတ်ထိုးဝင်ရောက်ရန် မူရင်းစကားဝှက်မန်နေဂျာကို သတ်မှတ်ပါ။"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် လျှို့ဝှက်ကီး ပြုလုပ်မလား။"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"သင့်စကားဝှက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"သင်၏ လက်မှတ်ထိုးဝင်သည့်အချက်အလက်ကို <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> တွင် သိမ်းမလား။"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"လျှို့ဝှက်ကီး"</string>
     <string name="password" msgid="6738570945182936667">"စကားဝှက်"</string>
     <string name="sign_ins" msgid="4710739369149469208">"လက်မှတ်ထိုးဝင်မှုများ"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ဤနေရာတွင် လျှို့ဝှက်ကီးပြုလုပ်ရန်"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"စကားဝှက်ကို ဤနေရာတွင် သိမ်းရန်"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"လက်မှတ်ထိုးဝင်မှုကို ဤနေရာတွင် သိမ်းရန်"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"အခြားစက်တွင် လျှို့ဝှက်ကီးပြုလုပ်မလား။"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"သင်၏လက်မှတ်ထိုးဝင်မှု အားလုံးအတွက် <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> သုံးမလား။"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"သင်အလွယ်တကူ လက်မှတ်ထိုးဝင်နိုင်ရန် ဤစကားဝှက်မန်နေဂျာက စကားဝှက်နှင့် လျှို့ဝှက်ကီးများကို သိမ်းပါမည်။"</string>
     <string name="set_as_default" msgid="4415328591568654603">"မူရင်းအဖြစ် သတ်မှတ်ရန်"</string>
     <string name="use_once" msgid="9027366575315399714">"တစ်ကြိမ်သုံးရန်"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု၊ လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ခု"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"စကားဝှက် <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ခု"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"လျှို့ဝှက်ကီး <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ခု"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"လျှို့ဝှက်ကီး"</string>
     <string name="another_device" msgid="5147276802037801217">"စက်နောက်တစ်ခု"</string>
     <string name="other_password_manager" msgid="565790221427004141">"အခြားစကားဝှက်မန်နေဂျာများ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"စာမျက်နှာ ပိတ်ရန်"</string>
diff --git a/packages/CredentialManager/res/values-nb/strings.xml b/packages/CredentialManager/res/values-nb/strings.xml
index 82854b8..f113f72 100644
--- a/packages/CredentialManager/res/values-nb/strings.xml
+++ b/packages/CredentialManager/res/values-nb/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Legitimasjonslagring"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Avbryt"</string>
     <string name="string_continue" msgid="1346732695941131882">"Fortsett"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Opprett på et annet sted"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Lagre på et annet sted"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Bruk en annen enhet"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Lagre på en annen enhet"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"En enkel og trygg påloggingsmåte"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Bruk fingeravtrykk, ansiktet eller en skjermlås til å logge på med en unik tilgangsnøkkel du verken kan glemme eller miste. Finn ut mer"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Velg hvor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"opprette tilgangsnøklene dine"</string>
     <string name="save_your_password" msgid="6597736507991704307">"lagre passordet ditt"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"lagre påloggingsinformasjonen din"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Angi et standardverktøy for passordlagring for å lagre passordene og tilgangsnøklene dine og logge på raskere neste gang."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vil du opprette en tilgangsnøkkel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vil du lagre passordet i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vil du lagre påloggingsinformasjonen i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"tilgangsnøkkel"</string>
     <string name="password" msgid="6738570945182936667">"passord"</string>
     <string name="sign_ins" msgid="4710739369149469208">"pålogginger"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Opprett en tilgangsnøkkel på"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Lagre passordet i"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Lagre påloggingen i"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vil du opprette en tilgangsnøkkel på en annen enhet?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vil du bruke <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> for alle pålogginger?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Dette verktøyet for passordlagring lagrer passord og tilgangsnøkler, så det blir lett å logge på."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Angi som standard"</string>
     <string name="use_once" msgid="9027366575315399714">"Bruk én gang"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> tilgangsnøkler"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> passord"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> tilgangsnøkler"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Tilgangsnøkkel"</string>
     <string name="another_device" msgid="5147276802037801217">"En annen enhet"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Andre løsninger for passordlagring"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Lukk arket"</string>
diff --git a/packages/CredentialManager/res/values-ne/strings.xml b/packages/CredentialManager/res/values-ne/strings.xml
index 23f4f43..cc8a38a 100644
--- a/packages/CredentialManager/res/values-ne/strings.xml
+++ b/packages/CredentialManager/res/values-ne/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"क्रिडेन्सियल म्यानेजर"</string>
     <string name="string_cancel" msgid="6369133483981306063">"रद्द गर्नुहोस्"</string>
     <string name="string_continue" msgid="1346732695941131882">"जारी राख्नुहोस्"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"अर्को ठाउँमा बनाउनुहोस्"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"अर्को ठाउँमा सेभ गर्नुहोस्"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"अर्को डिभाइस प्रयोग गर्नुहोस्"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"अर्को डिभाइसमा सेभ गर्नुहोस्"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"सुरक्षित तरिकाले साइन इन गर्ने सरल तरिका"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"नभुलिने वा चोरी नहुने खालको अद्वितीय पासकीका साथै आफ्ना फिंगरप्रिन्ट, अनुहार वा स्क्रिन लक प्रयोग गरी साइन इन गर्नुहोस्। थप जान्नुहोस्"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> सेभ गर्ने ठाउँ छनौट गर्नुहोस्"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> सेभ गर्ने ठाउँ छनौट गर्नुहोस्"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"आफ्ना पासकीहरू बाउनुहोस्"</string>
     <string name="save_your_password" msgid="6597736507991704307">"आफ्नो पासवर्ड सेभ गर्नुहोस्"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"आफ्नो साइन इनसम्बन्धी जानकारी सेभ गर्नुहोस्"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"आफ्ना पासवर्ड तथा पासकीहरू सेभ गर्न र अर्को पटक अझ छिटो साइन इन गर्न डिफल्ट पासवर्ड म्यानेजर सेट गर्नुहोस्।"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा पासकी बनाउने हो?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"तपाईंको पासवर्ड <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"तपाईंको साइन इनसम्बन्धी जानकारी <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> मा सेभ गर्ने हो?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"पासकी"</string>
     <string name="password" msgid="6738570945182936667">"पासवर्ड"</string>
     <string name="sign_ins" msgid="4710739369149469208">"साइन इनसम्बन्धी जानकारी"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"यसमा पासकी बनाउनुहोस्:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"यसमा पासवर्ड सेभ गर्नुहोस्:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"यसमा साइन इनसम्बन्धी जानकारी सेभ गर्नुहोस्:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"अर्को डिभाइसमा पासकी बनाउने हो?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"तपाईंले साइन इन गर्ने सबै डिभाइसहरूमा <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> प्रयोग गर्ने हो?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"तपाईं सजिलै साइन इन गर्न सक्नुहोस् भन्नाका लागि यो पासवर्ड म्यानेजरले तपाईंका पासवर्ड तथा पासकीहरू सेभ गर्ने छ।"</string>
     <string name="set_as_default" msgid="4415328591568654603">"डिफल्ट जानकारीका रूपमा सेट गर्नुहोस्"</string>
     <string name="use_once" msgid="9027366575315399714">"एक पटक प्रयोग गर्नुहोस्"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> वटा पासकी"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> वटा पासवर्ड"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> वटा पासकी"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"पासकी"</string>
     <string name="another_device" msgid="5147276802037801217">"अर्को डिभाइस"</string>
     <string name="other_password_manager" msgid="565790221427004141">"अन्य पासवर्ड म्यानेजरहरू"</string>
     <string name="close_sheet" msgid="1393792015338908262">"पाना बन्द गर्नुहोस्"</string>
diff --git a/packages/CredentialManager/res/values-nl/strings.xml b/packages/CredentialManager/res/values-nl/strings.xml
index c91a318..72dff73 100644
--- a/packages/CredentialManager/res/values-nl/strings.xml
+++ b/packages/CredentialManager/res/values-nl/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Op een andere locatie opslaan"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Een ander apparaat gebruiken"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Opslaan op een ander apparaat"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Een makkelijke manier om beveiligd in te loggen"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gebruik je vingerafdruk, gezichtsvergrendeling of schermvergrendeling om in te loggen met een unieke toegangssleutel die je niet kunt vergeten en die anderen niet kunnen stelen. Meer informatie"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Een locatie kiezen voor <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"je toegangssleutels maken"</string>
     <string name="save_your_password" msgid="6597736507991704307">"je wachtwoord opslaan"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"je inloggegevens opslaan"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Stel een standaard wachtwoordmanager in om je wachtwoorden en toegangssleutels op te slaan zodat je de volgende keer sneller kunt inloggen."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Een toegangssleutel maken in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Je wachtwoord opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Je inloggegevens opslaan in <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"toegangssleutel"</string>
     <string name="password" msgid="6738570945182936667">"wachtwoord"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inloggegevens"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Toegangssleutel maken in"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Wachtwoord opslaan in"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Inloggegevens opslaan in"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Toegangssleutel maken op een ander apparaat?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> elke keer gebruiken als je inlogt?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Deze wachtwoordmanager slaat je wachtwoorden en toegangssleutels op zodat je makkelijk kunt inloggen."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Instellen als standaard"</string>
     <string name="use_once" msgid="9027366575315399714">"Eén keer gebruiken"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> wachtwoorden, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> toegangssleutels"</string>
diff --git a/packages/CredentialManager/res/values-or/strings.xml b/packages/CredentialManager/res/values-or/strings.xml
index 838ddfe..a1bbf1c 100644
--- a/packages/CredentialManager/res/values-or/strings.xml
+++ b/packages/CredentialManager/res/values-or/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"କ୍ରେଡେନସିଆଲ ମେନେଜର"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ଜାରି ରଖନ୍ତୁ"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ତିଆରି କରନ୍ତୁ"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"ଅନ୍ୟ ଏକ ସ୍ଥାନରେ ସେଭ କରନ୍ତୁ"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ଅନ୍ୟ ଏହି ଡିଭାଇସ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ସେଭ କରନ୍ତୁ"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ସୁରକ୍ଷିତ ଭାବେ ସାଇନ ଇନ କରିବାର ଏକ ସରଳ ଉପାୟ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ଏକ ସ୍ୱତନ୍ତ୍ର ପାସକୀ ମାଧ୍ୟମରେ ସାଇନ ଇନ କରିବା ପାଇଁ ଆପଣଙ୍କ ଟିପଚିହ୍ନ, ଫେସ କିମ୍ବା ସ୍କ୍ରିନ ଲକ ବ୍ୟବହାର କରନ୍ତୁ ଯାହାକୁ ଭୁଲି ପାରିବେ ନାହିଁ କିମ୍ବା ଚୋରି ହୋଇପାରିବ ନାହିଁ। ଅଧିକ ଜାଣନ୍ତୁ"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"କେଉଁଠି <xliff:g id="CREATETYPES">%1$s</xliff:g> କରିବେ, ତାହା ବାଛନ୍ତୁ"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"କେଉଁଠି <xliff:g id="CREATETYPES">%1$s</xliff:g> କରିବେ, ତାହା ବାଛନ୍ତୁ"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ଆପଣଙ୍କ ପାସକୀଗୁଡ଼ିକ ତିଆରି କରନ୍ତୁ"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ଆପଣଙ୍କ ପାସୱାର୍ଡ ସେଭ କରନ୍ତୁ"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନା ସେଭ କରନ୍ତୁ"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ଆପଣଙ୍କ ପାସୱାର୍ଡ ଓ ପାସକୀଗୁଡ଼ିକୁ ସେଭ କରିବା ପାଇଁ ଏକ ଡିଫଲ୍ଟ Password Manager ସେଟ କରନ୍ତୁ ଏବଂ ପରବର୍ତ୍ତୀ ଥର ଶୀଘ୍ର ସାଇନ ଇନ କରନ୍ତୁ।"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ଏକ ପାସକୀ ତିଆରି କରିବେ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"ଆପଣଙ୍କ ପାସୱାର୍ଡକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ଆପଣଙ୍କ ସାଇନ-ଇନ ସୂଚନାକୁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ରେ ସେଭ କରିବେ?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ପାସକୀ"</string>
     <string name="password" msgid="6738570945182936667">"ପାସୱାର୍ଡ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ସାଇନ-ଇନ"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ଏଥିରେ ପାସକୀ ତିଆରି କରନ୍ତୁ"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ଏଥିରେ ପାସୱାର୍ଡ ସେଭ କରନ୍ତୁ"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ଏଥିରେ ସାଇନ-ଇନ ସେଭ କରନ୍ତୁ"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"ଅନ୍ୟ ଏକ ଡିଭାଇସରେ ପାସକୀ ତିଆରି କରିବେ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ଆପଣଙ୍କ ସମସ୍ତ ସାଇନ-ଇନ ପାଇଁ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ବ୍ୟବହାର କରିବେ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"ଏହି Password Manager ସହଜରେ ସାଇନ ଇନ କରିବାରେ ଆପଣଙ୍କୁ ସାହାଯ୍ୟ କରିବା ପାଇଁ ଆପଣଙ୍କ ପାସୱାର୍ଡ ଏବଂ ପାସକୀଗୁଡ଼ିକୁ ଷ୍ଟୋର କରିବ।"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ଡିଫଲ୍ଟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
     <string name="use_once" msgid="9027366575315399714">"ଥରେ ବ୍ୟବହାର କରନ୍ତୁ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ଟି ପାସକୀ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ଟି ପାସୱାର୍ଡ"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ଟି ପାସକୀ"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"ପାସକୀ"</string>
     <string name="another_device" msgid="5147276802037801217">"ଅନ୍ୟ ଏକ ଡିଭାଇସ"</string>
     <string name="other_password_manager" msgid="565790221427004141">"ଅନ୍ୟ Password Manager"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ସିଟ ବନ୍ଦ କରନ୍ତୁ"</string>
diff --git a/packages/CredentialManager/res/values-pa/strings.xml b/packages/CredentialManager/res/values-pa/strings.xml
index 74b2ab1..36989c6 100644
--- a/packages/CredentialManager/res/values-pa/strings.xml
+++ b/packages/CredentialManager/res/values-pa/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"ਕ੍ਰੀਡੈਂਸ਼ੀਅਲ ਪ੍ਰਬੰਧਕ"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ਰੱਦ ਕਰੋ"</string>
     <string name="string_continue" msgid="1346732695941131882">"ਜਾਰੀ ਰੱਖੋ"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਬਣਾਓ"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"ਕਿਸੇ ਹੋਰ ਥਾਂ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ਕੋਈ ਹੋਰ ਡੀਵਾਈਸ ਦੀ ਵਰਤੋਂ ਕਰੋ"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਰੱਖਿਅਤ ਕਰੋ"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"ਸੁਰੱਖਿਅਤ ਢੰਗ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਦਾ ਆਸਾਨ ਤਰੀਕਾ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ਵਿਲੱਖਣ ਪਾਸਕੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਾਸਤੇ ਆਪਣੇ ਫਿੰਗਰਪ੍ਰਿੰਟ, ਚਿਹਰੇ ਜਾਂ ਸਕ੍ਰੀਨ ਲਾਕ ਦੀ ਵਰਤੋਂ ਕਰੋ ਜਿਸਨੂੰ ਭੁੱਲਿਆ ਜਾਂ ਚੋਰੀ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ। ਹੋਰ ਜਾਣੋ"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> ਲਈ ਕੋਈ ਥਾਂ ਚੁਣੋ"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ਆਪਣੀਆਂ ਪਾਸਕੀਆਂ ਬਣਾਓ"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ਆਪਣਾ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰੋ"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰੋ"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ਆਪਣੇ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਾਸਕੀਆਂ ਨੂੰ ਰੱਖਿਅਤ ਕਰਨ ਲਈ ਕੋਈ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਸੈੱਟ ਕਰੋ ਅਤੇ ਅਗਲੀ ਵਾਰ ਤੇਜ਼ੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰੋ।"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"ਕੀ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਪਾਸਕੀ ਨੂੰ ਬਣਾਉਣਾ ਹੈ?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"ਕੀ ਆਪਣੇ ਪਾਸਵਰਡ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ਕੀ ਆਪਣੀ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਨੂੰ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਵਿੱਚ ਰੱਖਿਅਤ ਕਰਨਾ ਹੈ?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ਪਾਸਕੀ"</string>
     <string name="password" msgid="6738570945182936667">"ਪਾਸਵਰਡ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"ਸਾਈਨ-ਇਨਾਂ ਦੀ ਜਾਣਕਾਰੀ"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"ਇਸ ਵਿੱਚ ਪਾਸਕੀ ਬਣਾਓ"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"ਇਸ \'ਤੇ ਪਾਸਵਰਡ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"ਇਸ \'ਤੇ ਸਾਈਨ-ਇਨ ਜਾਣਕਾਰੀ ਰੱਖਿਅਤ ਕਰੋ"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"ਕੀ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ ਵਿੱਚ ਕੋਈ ਪਾਸਕੀ ਬਣਾਉਣੀ ਹੈ?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ਕੀ ਆਪਣੇ ਸਾਰੇ ਸਾਈਨ-ਇਨਾਂ ਲਈ<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਹੈ?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"ਇਹ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ ਤੁਹਾਡੀ ਆਸਾਨੀ ਨਾਲ ਸਾਈਨ-ਇਨ ਕਰਨ ਵਿੱਚ ਮਦਦ ਕਰਨ ਲਈ ਤੁਹਾਡੇ ਪਾਸਵਰਡਾਂ ਅਤੇ ਪਾਸਕੀਆਂ ਨੂੰ ਸਟੋਰ ਕਰੇਗਾ।"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
     <string name="use_once" msgid="9027366575315399714">"ਇੱਕ ਵਾਰ ਵਰਤੋ"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ਪਾਸਕੀਆਂ"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ਪਾਸਵਰਡ"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ਪਾਸਕੀਆਂ"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"ਪਾਸਕੀ"</string>
     <string name="another_device" msgid="5147276802037801217">"ਹੋਰ ਡੀਵਾਈਸ"</string>
     <string name="other_password_manager" msgid="565790221427004141">"ਹੋਰ ਪਾਸਵਰਡ ਪ੍ਰਬੰਧਕ"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ਸ਼ੀਟ ਬੰਦ ਕਰੋ"</string>
diff --git a/packages/CredentialManager/res/values-pl/strings.xml b/packages/CredentialManager/res/values-pl/strings.xml
index af6bc9d..462ded0 100644
--- a/packages/CredentialManager/res/values-pl/strings.xml
+++ b/packages/CredentialManager/res/values-pl/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Menedżer danych logowania"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anuluj"</string>
     <string name="string_continue" msgid="1346732695941131882">"Dalej"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Utwórz w innym miejscu"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Zapisz w innym miejscu"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Użyj innego urządzenia"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Zapisz na innym urządzeniu"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Prosty sposób na bezpieczne logowanie"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Używaj odcisku palca, rozpoznawania twarzy lub blokady ekranu, aby logować się z wykorzystaniem unikalnego klucza, którego nie można zapomnieć ani utracić w wyniku kradzieży. Więcej informacji"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Wybierz, gdzie chcesz <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Wybierz, gdzie chcesz <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"tworzyć klucze"</string>
     <string name="save_your_password" msgid="6597736507991704307">"zapisać hasło"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"zapisać dane logowania"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Ustaw domyślny menedżer haseł, aby zapisywać swoje hasła oraz klucze dostępu i aby logować się szybciej w przyszłości."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Utworzyć klucz w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Zapisać hasło w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Zapisać dane logowania w usłudze <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"klucz"</string>
     <string name="password" msgid="6738570945182936667">"hasło"</string>
     <string name="sign_ins" msgid="4710739369149469208">"dane logowania"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Utwórz klucz w usłudze"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Zapisz hasło w usłudze"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Zapisz dane logowania w usłudze"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Utworzyć klucz na innym urządzeniu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Używać usługi <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> w przypadku wszystkich danych logowania?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Menedżer haseł będzie zapisywał Twoje hasła i klucze, aby ułatwić Ci logowanie."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ustaw jako domyślną"</string>
     <string name="use_once" msgid="9027366575315399714">"Użyj raz"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, klucze: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Hasła: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Klucze: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Klucz"</string>
     <string name="another_device" msgid="5147276802037801217">"Inne urządzenie"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Inne menedżery haseł"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Zamknij arkusz"</string>
diff --git a/packages/CredentialManager/res/values-pt-rBR/strings.xml b/packages/CredentialManager/res/values-pt-rBR/strings.xml
index d950bb4..945adc2 100644
--- a/packages/CredentialManager/res/values-pt-rBR/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rBR/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crie suas chaves de acesso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Defina um gerenciador de senhas padrão para salvar suas senhas e chaves de acesso e fazer login mais rápido na próxima vez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
     <string name="password" msgid="6738570945182936667">"senha"</string>
     <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Criar chave de acesso em"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Salvar senha em"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Salvar informações de login em"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Criar uma chave de acesso em outro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gerenciador de senhas vai armazenar suas senhas e chaves de acesso para facilitar o processo de login."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-pt-rPT/strings.xml b/packages/CredentialManager/res/values-pt-rPT/strings.xml
index c46143c..93a7a5f 100644
--- a/packages/CredentialManager/res/values-pt-rPT/strings.xml
+++ b/packages/CredentialManager/res/values-pt-rPT/strings.xml
@@ -8,8 +8,14 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Guardar noutro lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Guardar noutro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma forma simples de iniciar sessão em segurança"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a sua impressão digital, rosto ou bloqueio de ecrã para iniciar sessão com uma chave de acesso única que não pode ser esquecida nem perdida. Saiba mais"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde quer guardar <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"criar as suas chaves de acesso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"guardar a sua palavra-passe"</string>
diff --git a/packages/CredentialManager/res/values-pt/strings.xml b/packages/CredentialManager/res/values-pt/strings.xml
index d950bb4..945adc2 100644
--- a/packages/CredentialManager/res/values-pt/strings.xml
+++ b/packages/CredentialManager/res/values-pt/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvar em outro lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Usar outro dispositivo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvar em outro dispositivo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Uma maneira simples de fazer login com segurança"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Use a impressão digital, o reconhecimento facial ou um bloqueio de tela para fazer login com uma única chave de acesso que não pode ser esquecida ou perdida. Saiba mais"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Escolha onde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"crie suas chaves de acesso"</string>
     <string name="save_your_password" msgid="6597736507991704307">"salvar sua senha"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvar suas informações de login"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Defina um gerenciador de senhas padrão para salvar suas senhas e chaves de acesso e fazer login mais rápido na próxima vez."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Criar uma chave de acesso em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvar sua senha em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvar suas informações de login em <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"chave de acesso"</string>
     <string name="password" msgid="6738570945182936667">"senha"</string>
     <string name="sign_ins" msgid="4710739369149469208">"logins"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Criar chave de acesso em"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Salvar senha em"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Salvar informações de login em"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Criar uma chave de acesso em outro dispositivo?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Usar <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para todos os seus logins?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Este gerenciador de senhas vai armazenar suas senhas e chaves de acesso para facilitar o processo de login."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Definir como padrão"</string>
     <string name="use_once" msgid="9027366575315399714">"Usar uma vez"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> senhas, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chaves de acesso"</string>
diff --git a/packages/CredentialManager/res/values-ro/strings.xml b/packages/CredentialManager/res/values-ro/strings.xml
index 6947281..da7ec1a 100644
--- a/packages/CredentialManager/res/values-ro/strings.xml
+++ b/packages/CredentialManager/res/values-ro/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Manager de date de conectare"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anulează"</string>
     <string name="string_continue" msgid="1346732695941131882">"Continuă"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Creează în alt loc"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Salvează în alt loc"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Folosește alt dispozitiv"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Salvează pe alt dispozitiv"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Un mod simplu de a te conecta în siguranță"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Folosește-ți amprenta, fața sau blocarea ecranului ca să te conectezi cu o cheie de acces unică, pe care nu o poți uita și care nu poate fi furată. Află mai multe"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Alege unde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Alege unde <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"creează cheile de acces"</string>
     <string name="save_your_password" msgid="6597736507991704307">"salvează parola"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"salvează informațiile de conectare"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Setează un manager de parole implicit pentru a salva parolele și cheile de acces și a te conecta mai rapid data viitoare."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Creezi o cheie de acces în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Salvezi parola în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Salvezi informațiile de conectare în <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"cheie de acces"</string>
     <string name="password" msgid="6738570945182936667">"parolă"</string>
     <string name="sign_ins" msgid="4710739369149469208">"date de conectare"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Creează o cheie de acces în"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Salvează parola în"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Salvează datele de conectare în"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Creezi o cheie de acces în alt dispozitiv?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Folosești <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> pentru toate conectările?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Managerul de parole îți va stoca parolele și cheile de acces, pentru a te ajuta să te conectezi cu ușurință."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Setează ca prestabilite"</string>
     <string name="use_once" msgid="9027366575315399714">"Folosește o dată"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> chei de acces"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> parole"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> chei de acces"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Cheie de acces"</string>
     <string name="another_device" msgid="5147276802037801217">"Alt dispozitiv"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Alți manageri de parole"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Închide foaia"</string>
diff --git a/packages/CredentialManager/res/values-ru/strings.xml b/packages/CredentialManager/res/values-ru/strings.xml
index dcc643e..7dc51a2 100644
--- a/packages/CredentialManager/res/values-ru/strings.xml
+++ b/packages/CredentialManager/res/values-ru/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Сохранить в другом месте"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Использовать другое устройство"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Сохранить на другом устройстве"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Простой и безопасный способ входа"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"С уникальным ключом доступа, который невозможно украсть или забыть, вы можете подтверждать свою личность по отпечатку пальца, с помощью фейсконтроля или блокировки экрана. Подробнее…"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Выберите, где <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"создать ключи доступа"</string>
     <string name="save_your_password" msgid="6597736507991704307">"сохранить пароль"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"сохранить данные для входа"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Задайте менеджер паролей по умолчанию, чтобы сохранять пароли и ключи доступа и быстро выполнять вход."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Создать ключ доступа в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Сохранить пароль в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Сохранить учетные данные в приложении \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"ключ доступа"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
     <string name="sign_ins" msgid="4710739369149469208">"входы"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Выберите, где создать ключ доступа"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Выберите, где сохранить пароль"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Выберите, где сохранить учетные данные"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Создать ключ доступа на другом устройстве?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Всегда входить с помощью приложения \"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>\"?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"В этом менеджере паролей можно сохранять учетные данные, например ключи доступа, чтобы потом использовать их."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Использовать по умолчанию"</string>
     <string name="use_once" msgid="9027366575315399714">"Использовать один раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Пароли (<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>) и ключи доступа (<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>)"</string>
diff --git a/packages/CredentialManager/res/values-si/strings.xml b/packages/CredentialManager/res/values-si/strings.xml
index bf885a9..fd94e5a 100644
--- a/packages/CredentialManager/res/values-si/strings.xml
+++ b/packages/CredentialManager/res/values-si/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"අක්තපත්‍ර කළමනාකරු"</string>
     <string name="string_cancel" msgid="6369133483981306063">"අවලංගු කරන්න"</string>
     <string name="string_continue" msgid="1346732695941131882">"ඉදිරියට යන්න"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"වෙනත් ස්ථානයක තනන්න"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"වෙනත් ස්ථානයකට සුරකින්න"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"වෙනත් උපාංගයක් භාවිතා කරන්න"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"වෙනත් උපාංගයකට සුරකින්න"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"සුරක්ෂිතව පුරනය වීමට සරල ක්‍රමයක්"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"අමතක කළ නොහැකි හෝ සොරකම් කළ නොහැකි අනන්‍ය මුරයතුරක් සමග පුරනය වීමට ඔබේ ඇඟිලි සලකුණ, මුහුණ හෝ තිර අගුල භාවිතා කරන්න. තව දැන ගන්න⁠"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> කොතැනක ද යන්න තෝරා ගන්න"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ඔබේ මුරයතුරු තනන්න"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ඔබේ මුරපදය සුරකින්න"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ඔබේ පුරනය වීමේ තතු සුරකින්න"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ඔබේ මුරපද සහ මුරයතුරු සුරැකීමට පෙරනිමි මුරපද කළමනාකරු සකසන්න සහ මීළඟ වතාවේ වේගයෙන් පුරනය වන්න."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> තුළ මුරයතුරක් තනන්න ද?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"ඔබේ මුරපදය <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"ඔබේ පුරනය වීමේ තතු <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> වෙත සුරකින්න ද?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"මුරයතුර"</string>
     <string name="password" msgid="6738570945182936667">"මුරපදය"</string>
     <string name="sign_ins" msgid="4710739369149469208">"පුරනය වීම්"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"මුරයතුර තනන්නේ"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"මෙයට මුරපදය සුරකින්න"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"පුරනය වීම සුරකින්නේ"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"වෙනත් උපාංගයක මුරයතුරක් තනන්න ද?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ඔබේ සියලු පුරනය වීම් සඳහා <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> භාවිතා කරන්න ද?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"මෙම මුරපද කළමනාකරු ඔබට පහසුවෙන් පුරනය වීමට උදවු කිරීම සඳහා ඔබේ මුරපද සහ මුරයතුරු ගබඩා කරනු ඇත."</string>
     <string name="set_as_default" msgid="4415328591568654603">"පෙරනිමි ලෙස සකසන්න"</string>
     <string name="use_once" msgid="9027366575315399714">"වරක් භාවිතා කරන්න"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්, මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>ක්"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"මුරපද <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>ක්"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"මුරයතුරු <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>ක්"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"මුරයතුර"</string>
     <string name="another_device" msgid="5147276802037801217">"වෙනත් උපාංගයක්"</string>
     <string name="other_password_manager" msgid="565790221427004141">"වෙනත් මුරපද කළමනාකරුවන්"</string>
     <string name="close_sheet" msgid="1393792015338908262">"පත්‍රය වසන්න"</string>
diff --git a/packages/CredentialManager/res/values-sk/strings.xml b/packages/CredentialManager/res/values-sk/strings.xml
index 1c73c57..cd81361 100644
--- a/packages/CredentialManager/res/values-sk/strings.xml
+++ b/packages/CredentialManager/res/values-sk/strings.xml
@@ -8,8 +8,14 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Uložiť inde"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Použiť iné zariadenie"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Uložiť do iného zariadenia"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Jednoduchý spôsob bezpečného prihlasovania"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Použite odtlačok prsta, tvár alebo zámku obrazovky a prihláste sa jedinečným prístupovým kľúčom, ktorý sa nedá zabudnúť ani ukradnúť. Ďalšie informácie"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Vyberte, kam <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <string name="create_your_passkeys" msgid="8901224153607590596">"vytvoriť prístupové kľúče"</string>
     <string name="save_your_password" msgid="6597736507991704307">"uložiť heslo"</string>
diff --git a/packages/CredentialManager/res/values-sl/strings.xml b/packages/CredentialManager/res/values-sl/strings.xml
index 969f290..c769e19 100644
--- a/packages/CredentialManager/res/values-sl/strings.xml
+++ b/packages/CredentialManager/res/values-sl/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Shranjevanje na drugo mesto"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Uporabi drugo napravo"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Shrani v drugo napravo"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Preprost način za varno prijavo"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Če se želite prijaviti z enoličnim ključem za dostop, ki ga ni mogoče pozabiti ali ukrasti, uporabite prstni odtis, obraz ali nastavljeni način za odklepanje zaslona. Več o tem"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Izberite mesto za <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Izberite mesto za <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"ustvarjanje ključev za dostop"</string>
     <string name="save_your_password" msgid="6597736507991704307">"shranjevanje gesla"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"shranjevanje podatkov za prijavo"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Nastavite privzetega upravitelja gesel za shranjevanje gesel in ključev za dostop, da se boste naslednjič lahko hitreje prijavili."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Želite ustvariti ključ za dostop pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Želite shraniti geslo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Želite shraniti podatke za prijavo pod »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"ključ za dostop"</string>
     <string name="password" msgid="6738570945182936667">"geslo"</string>
     <string name="sign_ins" msgid="4710739369149469208">"prijave"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Ustvarjanje ključa za dostop v"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Shranjevanje gesla v"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Shranjevanje podatkov za prijavo v"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Želite ustvariti ključ za dostop v drugi napravi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Želite za vse prijave uporabiti »<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>«?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"V tem upravitelju gesel bodo shranjeni gesla in ključi za dostop, kar vam bo olajšalo prijavo."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Nastavi kot privzeto"</string>
     <string name="use_once" msgid="9027366575315399714">"Uporabi enkrat"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Št. gesel: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, št. ključev za dostop: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
diff --git a/packages/CredentialManager/res/values-sq/strings.xml b/packages/CredentialManager/res/values-sq/strings.xml
index bce0683..7e656d6 100644
--- a/packages/CredentialManager/res/values-sq/strings.xml
+++ b/packages/CredentialManager/res/values-sq/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Menaxheri i kredencialeve"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Anulo"</string>
     <string name="string_continue" msgid="1346732695941131882">"Vazhdo"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Krijo në një vend tjetër"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Ruaj në një vend tjetër"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Përdor një pajisje tjetër"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Ruaj në një pajisje tjetër"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Një mënyrë e thjeshtë për t\'u identifikuar në mënyrë të sigurt"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Përdor gjurmën e gishtit, fytyrën ose kyçjen e ekranit për t\'u identifikuar me një çelës unik kalimi i cili nuk mund të harrohet ose të vidhet. Mëso më shumë"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Zgjidh se ku të <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"do t\'i krijosh çelësat e tu të kalimit"</string>
     <string name="save_your_password" msgid="6597736507991704307">"ruaj fjalëkalimin"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"ruaj informacionet e tua të identifikimit"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Cakto një menaxher të parazgjedhur të fjalëkalimeve për të ruajtur fjalëkalimet dhe çelësat e kalimit dhe për t\'u identifikuar më shpejt herën tjetër."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Të krijohet një çelës kalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Të ruhet fjalëkalimi në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Të ruhen informacionet e tua të identifikimit në <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"çelësi i kalimit"</string>
     <string name="password" msgid="6738570945182936667">"fjalëkalimi"</string>
     <string name="sign_ins" msgid="4710739369149469208">"identifikimet"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Krijo çelësin e kalimit te"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Ruaj fjalëkalimin në"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Ruaj identifikimin në"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Të krijohet një çelës kalimi në një pajisje tjetër?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Të përdoret <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> për të gjitha identifikimet?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Ky menaxher i fjalëkalimeve do të ruajë fjalëkalimet dhe çelësat e kalimit për të të ndihmuar të identifikohesh me lehtësi."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Cakto si parazgjedhje"</string>
     <string name="use_once" msgid="9027366575315399714">"Përdor një herë"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> çelësa kalimi"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> fjalëkalime"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> çelësa kalimi"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Çelësi i kalimit"</string>
     <string name="another_device" msgid="5147276802037801217">"Një pajisje tjetër"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Menaxherët e tjerë të fjalëkalimeve"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Mbyll fletën"</string>
diff --git a/packages/CredentialManager/res/values-sr/strings.xml b/packages/CredentialManager/res/values-sr/strings.xml
index 6a5235c..cfb6c05 100644
--- a/packages/CredentialManager/res/values-sr/strings.xml
+++ b/packages/CredentialManager/res/values-sr/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Менаџер акредитива"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Откажи"</string>
     <string name="string_continue" msgid="1346732695941131882">"Настави"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Направи на другом месту"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Сачувај на другом месту"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Користи други уређај"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Сачувај на други уређај"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Једноставан начин да се безбедно пријављујете"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користите отисак прста, закључавање лицем или закључавање екрана да бисте се пријавили помоћу јединственог приступног кода који не може да се заборави или украде. Сазнајте више"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Одаберите локацију за: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Одаберите локацију за: <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"направите приступне кодове"</string>
     <string name="save_your_password" msgid="6597736507991704307">"сачувајте лозинку"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"сачувајте податке о пријављивању"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Подесите подразумевани менаџер лозинки да бисте сачували лозинке и приступне кодове и следећи пут се пријавили брже."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Желите да направите приступни кôд код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Желите да сачувате лозинку код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Желите да сачувате податке о пријављивању код корисника <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"приступни кôд"</string>
     <string name="password" msgid="6738570945182936667">"лозинка"</string>
     <string name="sign_ins" msgid="4710739369149469208">"пријављивања"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Направите приступни кôд у:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Сачувајте лозинку на:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Сачувајте податке о пријављивању на:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Желите да направите приступни кôд на другом уређају?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Желите да за сва пријављивања користите: <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Овај менаџер лозинки ће чувати лозинке и приступне кодове да бисте се лако пријављивали."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Подеси као подразумевано"</string>
     <string name="use_once" msgid="9027366575315399714">"Користи једном"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, приступних кодова:<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Лозинки: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Приступних кодова: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Приступни кôд"</string>
     <string name="another_device" msgid="5147276802037801217">"Други уређај"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Други менаџери лозинки"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Затворите табелу"</string>
diff --git a/packages/CredentialManager/res/values-sv/strings.xml b/packages/CredentialManager/res/values-sv/strings.xml
index a4fffb9..842d787 100644
--- a/packages/CredentialManager/res/values-sv/strings.xml
+++ b/packages/CredentialManager/res/values-sv/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Spara på en annan plats"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Använd en annan enhet"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Spara på en annan enhet"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Ett enkelt sätt att logga in säkert på"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Använd ditt fingeravtryck, ansikte eller skärmlås om du vill logga in med en unik nyckel som inte kan glömmas bort eller bli stulen. Läs mer"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Välj var du <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"skapa nycklar"</string>
     <string name="save_your_password" msgid="6597736507991704307">"spara lösenordet"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"spara inloggningsuppgifterna"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Ställ in vilken lösenordshanterare som ska användas som standard om du vill spara lösenord, nycklar och inloggningsuppgifter snabbare nästa gång."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Vill du skapa en nyckel i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Vill du spara ditt lösenord i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Vill du spara dina inloggningsuppgifter i <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"nyckel"</string>
     <string name="password" msgid="6738570945182936667">"lösenord"</string>
     <string name="sign_ins" msgid="4710739369149469208">"inloggningar"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Skapa nyckel i"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Spara lösenordet i"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Spara inloggningen i"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Vill du skapa en nyckel på en annan enhet?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Vill du använda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> för alla dina inloggningar?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Den här lösenordshanteraren sparar dina lösenord och nycklar för att underlätta inloggning."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Ange som standard"</string>
     <string name="use_once" msgid="9027366575315399714">"Använd en gång"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> lösenord, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> nycklar"</string>
diff --git a/packages/CredentialManager/res/values-sw/strings.xml b/packages/CredentialManager/res/values-sw/strings.xml
index bfd1074..3eb7432 100644
--- a/packages/CredentialManager/res/values-sw/strings.xml
+++ b/packages/CredentialManager/res/values-sw/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Kidhibiti cha Vitambulisho"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Ghairi"</string>
     <string name="string_continue" msgid="1346732695941131882">"Endelea"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Unda katika sehemu nyingine"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Hifadhi sehemu nyingine"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Tumia kifaa kingine"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Hifadhi kwenye kifaa kingine"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Njia rahisi ya kuingia katika akaunti kwa usalama"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Tumia alama ya vidole, uso au kipengele cha kufunga skrini ili uingie katika kaunti kwa kutumia nenosiri la kipekee ambalo haliwezi kusahaulika au kuibiwa. Pata maelezo zaidi"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Chagua mahali pa <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Chagua mahali pa <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"unda funguo zako za siri"</string>
     <string name="save_your_password" msgid="6597736507991704307">"hifadhi nenosiri lako"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"hifadhi maelezo yako ya kuingia katika akaunti"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Weka kidhibiti chaguomsingi cha manenosiri ili uhifadhi manenosiri na funguo zako za siri na uingie katika akaunti kwa haraka wakati mwingine."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Ungependa kuunda ufunguo wa siri katika <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Ungependa kuhifadhi nenosiri lako kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Ungependa kuhifadhi maelezo yako ya kuingia katika akaunti kwenye <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ufunguo wa siri"</string>
     <string name="password" msgid="6738570945182936667">"nenosiri"</string>
     <string name="sign_ins" msgid="4710739369149469208">"michakato ya kuingia katika akaunti"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Unda ufunguo wa siri kwenye"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Hifadhi nenosiri kwenye"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Hifadhi kitambulisho cha kuingia katika akaunti kwenye"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Ungependa kuunda ufunguo wa siri kwenye kifaa kingine?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Ungependa kutumia <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kwa ajili ya michakato yako yote ya kuingia katika akaunti?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Kidhibiti hiki cha manenosiri kitahifadhi manenosiri na funguo zako za siri ili kukusaidia uingie katika akaunti kwa urahisi."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Weka iwe chaguomsingi"</string>
     <string name="use_once" msgid="9027366575315399714">"Tumia mara moja"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, funguo <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> za siri"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Manenosiri <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Funguo <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> za siri"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ufunguo wa siri"</string>
     <string name="another_device" msgid="5147276802037801217">"Kifaa kingine"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Vidhibiti vinginevyo vya manenosiri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Funga laha"</string>
diff --git a/packages/CredentialManager/res/values-ta/strings.xml b/packages/CredentialManager/res/values-ta/strings.xml
index 10c5259..2d7d84e 100644
--- a/packages/CredentialManager/res/values-ta/strings.xml
+++ b/packages/CredentialManager/res/values-ta/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"அனுமதிச் சான்று நிர்வாகி"</string>
     <string name="string_cancel" msgid="6369133483981306063">"ரத்துசெய்"</string>
     <string name="string_continue" msgid="1346732695941131882">"தொடர்க"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"மற்றொரு இடத்தில் உருவாக்கவும்"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"மற்றொரு இடத்தில் சேமிக்கவும்"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"மற்றொரு சாதனத்தைப் பயன்படுத்தவும்"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"வேறொரு சாதனத்தில் சேமியுங்கள்"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"பாதுகாப்பாக உள்நுழைவதற்கான எளிய வழி"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"தனித்துவமான கடவுக்குறியீடு (மறக்காதவை அல்லது திருடமுடியாதவை) மூலம் உள்நுழைய, உங்கள் கைரேகை, முகம் அல்லது திரைப்பூட்டைப் பயன்படுத்தி உள்நுழையவும். மேலும் அறிக"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே காட்டப்பட வேண்டும் என்பதைத் தேர்வுசெய்தல்"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> எங்கே காட்டப்பட வேண்டும் என்பதைத் தேர்வுசெய்தல்"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"உங்கள் கடவுச்சாவிகளை உருவாக்குங்கள்"</string>
     <string name="save_your_password" msgid="6597736507991704307">"உங்கள் கடவுச்சொல்லைச் சேமிக்கவும்"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"உங்கள் உள்நுழைவு தகவலைச் சேமிக்கவும்"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"கடவுச்சொற்கள் &amp; கடவுச்சாவிகளைச் சேமிக்கவும் அடுத்தமுறை விரைவாக உள்நுழையவும் ஓர் இயல்பான Password Managerரை அமையுங்கள்."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் கடவுக்குறியீட்டை உருவாக்க வேண்டுமா?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"உங்கள் கடவுச்சொல்லை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"உங்கள் உள்நுழைவுத் தகவலை <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ல் சேமிக்க வேண்டுமா?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"கடவுக்குறியீடு"</string>
     <string name="password" msgid="6738570945182936667">"கடவுச்சொல்"</string>
     <string name="sign_ins" msgid="4710739369149469208">"உள்நுழைவுகள்"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"இதில் கடவுச்சாவியை உருவாக்குங்கள்:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"கடவுச்சொல்லை இதில் சேமியுங்கள்:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"உள்நுழைவுத் தகவலை இதில் சேமியுங்கள்:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"வேறொரு சாதனத்தில் கடவுச்சாவியை உருவாக்கவா?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"உங்கள் அனைத்து உள்நுழைவுகளுக்கும் <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ஐப் பயன்படுத்தவா?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"எளிதில் உள்நுழைவதற்கு உதவும் வகையில் இந்த Password Manager உங்கள் கடவுச்சொற்களையும் கடவுச்சாவிகளையும் சேமிக்கும்."</string>
     <string name="set_as_default" msgid="4415328591568654603">"இயல்பானதாக அமை"</string>
     <string name="use_once" msgid="9027366575315399714">"ஒருமுறை பயன்படுத்தவும்"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள், <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> கடவுக்குறியீடுகள்"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> கடவுச்சொற்கள்"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> கடவுக்குறியீடுகள்"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"கடவுக்குறியீடு"</string>
     <string name="another_device" msgid="5147276802037801217">"மற்றொரு சாதனம்"</string>
     <string name="other_password_manager" msgid="565790221427004141">"பிற கடவுச்சொல் நிர்வாகிகள்"</string>
     <string name="close_sheet" msgid="1393792015338908262">"ஷீட்டை மூடும்"</string>
diff --git a/packages/CredentialManager/res/values-te/strings.xml b/packages/CredentialManager/res/values-te/strings.xml
index f7617b3..664252d 100644
--- a/packages/CredentialManager/res/values-te/strings.xml
+++ b/packages/CredentialManager/res/values-te/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"డాక్యుమెంట్ ప్రూఫ్ మేనేజర్"</string>
     <string name="string_cancel" msgid="6369133483981306063">"రద్దు చేయండి"</string>
     <string name="string_continue" msgid="1346732695941131882">"కొనసాగించండి"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"మరొక స్థలంలో క్రియేట్ చేయండి"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"మరొక స్థలంలో సేవ్ చేయండి"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"మరొక పరికరాన్ని ఉపయోగించండి"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"మరొక పరికరంలో సేవ్ చేయండి"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"సురక్షితంగా సైన్ ఇన్ చేయడానికి సులభమైన మార్గం"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"మర్చిపోలేని లేదా దొంగిలించలేని ప్రత్యేకమైన పాస్-కీతో సైన్ ఇన్ చేయడానికి మీ వేలిముద్ర, ముఖం లేదా స్క్రీన్ లాక్‌ను ఉపయోగించండి. మరింత తెలుసుకోండి"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"ఎక్కడ <xliff:g id="CREATETYPES">%1$s</xliff:g> చేయాలో ఎంచుకోండి"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"ఎక్కడ <xliff:g id="CREATETYPES">%1$s</xliff:g> చేయాలో ఎంచుకోండి"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"మీ పాస్-కీలను క్రియేట్ చేయండి"</string>
     <string name="save_your_password" msgid="6597736507991704307">"మీ పాస్‌వర్డ్‌ను సేవ్ చేయండి"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"మీ సైన్ ఇన్ సమాచారాన్ని సేవ్ చేయండి"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"మీ పాస్‌వర్డ్‌లు, పాస్‌కీలను సేవ్ చేయడానికి ఆటోమేటిక్ సెట్టింగ్ పాస్‌వర్డ్ మేనేజర్‌ను సెట్ చేయండి, తదుపరిసారి వేగంగా సైన్ ఇన్ చేయండి."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>లో పాస్-కీని క్రియేట్ చేయాలా?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"మీ పాస్‌వర్డ్‌ను <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"మీ సైన్ ఇన్ సమాచారాన్ని <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>కు సేవ్ చేయాలా?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"పాస్-కీ"</string>
     <string name="password" msgid="6738570945182936667">"పాస్‌వర్డ్"</string>
     <string name="sign_ins" msgid="4710739369149469208">"సైన్‌ ఇన్‌లు"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"వీటిలో పాస్-కీని క్రియేట్ చేయండి"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"పాస్‌వర్డ్‌ను దీనికి సేవ్ చేయండి"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"సైన్ ఇన్‌ను దీనికి సేవ్ చేయండి"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"మరొక పరికరంలో పాస్-కీని క్రియేట్ చేయాలా?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"మీ అన్ని సైన్-ఇన్ వివరాల కోసం <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>ను ఉపయోగించాలా?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"మీరు సులభంగా సైన్ ఇన్ చేయడంలో సహాయపడటానికి ఈ పాస్‌వర్డ్ మేనేజర్ మీ పాస్‌వర్డ్‌లు, పాస్-కీలను స్టోర్ చేస్తుంది."</string>
     <string name="set_as_default" msgid="4415328591568654603">"ఆటోమేటిక్ సెట్టింగ్‌గా సెట్ చేయండి"</string>
     <string name="use_once" msgid="9027366575315399714">"ఒకసారి ఉపయోగించండి"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> పాస్-కీలు"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> పాస్‌వర్డ్‌లు"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> పాస్-కీలు"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"పాస్-కీ"</string>
     <string name="another_device" msgid="5147276802037801217">"మరొక పరికరం"</string>
     <string name="other_password_manager" msgid="565790221427004141">"ఇతర పాస్‌వర్డ్ మేనేజర్‌లు"</string>
     <string name="close_sheet" msgid="1393792015338908262">"షీట్‌ను మూసివేయండి"</string>
diff --git a/packages/CredentialManager/res/values-th/strings.xml b/packages/CredentialManager/res/values-th/strings.xml
index d70e94a..106c456 100644
--- a/packages/CredentialManager/res/values-th/strings.xml
+++ b/packages/CredentialManager/res/values-th/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"บันทึกลงในตำแหน่งอื่น"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"ใช้อุปกรณ์อื่น"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"ย้ายไปยังอุปกรณ์อื่น"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"วิธีง่ายๆ ในการลงชื่อเข้าใช้อย่างปลอดภัย"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"ใช้ลายนิ้วมือ ใบหน้า หรือล็อกหน้าจอในการลงชื่อเข้าใช้ด้วยพาสคีย์ที่ไม่ซ้ำกันเพื่อไม่ให้ลืมหรือถูกขโมยได้ ดูข้อมูลเพิ่มเติม"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"เลือกตำแหน่งที่จะ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"เลือกตำแหน่งที่จะ <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"สร้างพาสคีย์"</string>
     <string name="save_your_password" msgid="6597736507991704307">"บันทึกรหัสผ่าน"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"บันทึกข้อมูลการลงชื่อเข้าใช้"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"ตั้งค่าเครื่องมือจัดการรหัสผ่านเริ่มต้นเพื่อบันทึกรหัสผ่านและพาสคีย์ของคุณ และลงชื่อเข้าใช้ได้เร็วขึ้นในครั้งถัดไป"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"สร้างพาสคีย์ใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"บันทึกรหัสผ่านลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"บันทึกข้อมูลการลงชื่อเข้าใช้ลงใน <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ใช่ไหม"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"พาสคีย์"</string>
     <string name="password" msgid="6738570945182936667">"รหัสผ่าน"</string>
     <string name="sign_ins" msgid="4710739369149469208">"การลงชื่อเข้าใช้"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"สร้างพาสคีย์ใน"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"บันทึกรหัสผ่านลงใน"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"บันทึกการลงชื่อเข้าใช้ไปยัง"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"สร้างพาสคีย์ในอุปกรณ์อื่นไหม"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"ใช้ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> สำหรับการลงชื่อเข้าใช้ทั้งหมดใช่ไหม"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"เครื่องมือจัดการรหัสผ่านนี้จะจัดเก็บรหัสผ่านและพาสคีย์ไว้เพื่อช่วยให้คุณลงชื่อเข้าใช้ได้โดยง่าย"</string>
     <string name="set_as_default" msgid="4415328591568654603">"ตั้งเป็นค่าเริ่มต้น"</string>
     <string name="use_once" msgid="9027366575315399714">"ใช้ครั้งเดียว"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"รหัสผ่าน <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> รายการ พาสคีย์ <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> รายการ"</string>
diff --git a/packages/CredentialManager/res/values-tl/strings.xml b/packages/CredentialManager/res/values-tl/strings.xml
index 01fd2f0..d550190 100644
--- a/packages/CredentialManager/res/values-tl/strings.xml
+++ b/packages/CredentialManager/res/values-tl/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"I-save sa ibang lugar"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Gumamit ng ibang device"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"I-save sa ibang device"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Simpleng paraan para mag-sign in lang ligtas"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Gamitin ang iyong fingerprint, mukha, o lock ng screen para mag-sign in gamit ang natatanging passkey na hindi makakalimutan o mananakaw. Matuto pa"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Piliin kung saan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Piliin kung saan <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"gawin ang iyong mga passkey"</string>
     <string name="save_your_password" msgid="6597736507991704307">"i-save ang iyong password"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"i-save ang iyong impormasyon sa pag-sign in"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Magtakda ng default na password manager para i-save ang iyong mga password at passkey at mag-sign in nang mas mabilis sa susunod."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Gumawa ng passkey sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"I-save ang iyong password sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"I-save ang iyong impormasyon sa pag-sign in sa <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"passkey"</string>
     <string name="password" msgid="6738570945182936667">"password"</string>
     <string name="sign_ins" msgid="4710739369149469208">"mga sign-in"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Gumawa ng passkey sa"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"I-save ang password sa"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"I-save ang sign-in sa"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Gumawa ng passkey sa ibang device?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Gamitin ang <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> para sa lahat ng iyong pag-sign in?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Iso-store ng password manager na ito ang iyong mga password at passkey para madali kang makapag-sign in."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Itakda bilang default"</string>
     <string name="use_once" msgid="9027366575315399714">"Gamitin nang isang beses"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> (na) password, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> (na) passkey"</string>
diff --git a/packages/CredentialManager/res/values-tr/strings.xml b/packages/CredentialManager/res/values-tr/strings.xml
index 30ed43e..09c6590 100644
--- a/packages/CredentialManager/res/values-tr/strings.xml
+++ b/packages/CredentialManager/res/values-tr/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Kimlik Bilgisi Yöneticisi"</string>
     <string name="string_cancel" msgid="6369133483981306063">"İptal"</string>
     <string name="string_continue" msgid="1346732695941131882">"Devam"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Başka bir yerde oluşturun"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Başka bir yere kaydedin"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Başka bir cihaz kullan"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Başka bir cihaza kaydet"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Güvenli bir şekilde oturum açmanın basit yolu"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Parmak iziniz, yüzünüz ya da ekran kilidinizi kullanarak unutması veya çalınması mümkün olmayan benzersiz bir şifre anahtarıyla oturum açın. Daha fazla bilgi"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> yerini seçin"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"şifre anahtarlarınızı oluşturun"</string>
     <string name="save_your_password" msgid="6597736507991704307">"şifrenizi kaydedin"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"oturum açma bilgilerinizi kaydedin"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Şifrelerinizi ve şifre anahtarlarınızı kaydedip bir dahaki sefere daha hızlı oturum açmak için varsayılan bir şifre yöneticisi belirleyin."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içinde şifre anahtarı oluşturulsun mu?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Şifreniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Oturum açma bilgileriniz <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> içine kaydedilsin mi?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"şifre anahtarı"</string>
     <string name="password" msgid="6738570945182936667">"şifre"</string>
     <string name="sign_ins" msgid="4710739369149469208">"oturum aç"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Şifre anahtarının oluşturulacağı yer:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Şifreyi şuraya kaydet:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Oturum açma bilgilerinin kaydedileceği yer:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Başka bir cihazda şifre anahtarı oluşturulsun mu?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Tüm oturum açma işlemlerinizde <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> kullanılsın mı?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Bu şifre yöneticisi, şifrelerinizi ve şifre anahtarlarınızı saklayarak kolayca oturum açmanıza yardımcı olur."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Varsayılan olarak ayarla"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir kez kullanın"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> şifre anahtarı"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> şifre"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> şifre anahtarı"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Şifre anahtarı"</string>
     <string name="another_device" msgid="5147276802037801217">"Başka bir cihaz"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Diğer şifre yöneticileri"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Sayfayı kapat"</string>
diff --git a/packages/CredentialManager/res/values-uk/strings.xml b/packages/CredentialManager/res/values-uk/strings.xml
index 69d4612..9a12e33 100644
--- a/packages/CredentialManager/res/values-uk/strings.xml
+++ b/packages/CredentialManager/res/values-uk/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Диспетчер облікових даних"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Скасувати"</string>
     <string name="string_continue" msgid="1346732695941131882">"Продовжити"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Створити в іншому місці"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Зберегти в іншому місці"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Скористатись іншим пристроєм"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Зберегти на іншому пристрої"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Зручний спосіб для безпечного входу"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Користуйтеся відбитком пальця, фейсконтролем або іншим способом розблокування екрана, щоб входити в обліковий запис за допомогою унікального ключа доступу, який неможливо забути чи викрасти. Докладніше"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Виберіть, де <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"створювати ключі доступу"</string>
     <string name="save_your_password" msgid="6597736507991704307">"зберегти пароль"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"зберегти дані для входу"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Налаштуйте менеджер паролів за умовчанням, щоб зберігати свої паролі та ключі доступу й надалі входити в облікові записи швидше."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Створити ключ доступу в сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Зберегти ваш пароль у сервісі <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Зберегти ваші дані для входу в сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"ключ доступу"</string>
     <string name="password" msgid="6738570945182936667">"пароль"</string>
     <string name="sign_ins" msgid="4710739369149469208">"дані для входу"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Створити ключ доступу в"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Зберегти пароль в обліковому записі"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Зберегти дані для входу в обліковий запис"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Створити ключ доступу на іншому пристрої?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Використовувати сервіс <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> в усіх випадках входу?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Цей менеджер паролів зберігатиме ваші паролі та ключі доступу, щоб ви могли легко входити в облікові записи."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Вибрати за умовчанням"</string>
     <string name="use_once" msgid="9027366575315399714">"Скористатися раз"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>; кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Кількість паролів: <xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Кількість ключів доступу: <xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ключ доступу"</string>
     <string name="another_device" msgid="5147276802037801217">"Інший пристрій"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Інші менеджери паролів"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Закрити аркуш"</string>
diff --git a/packages/CredentialManager/res/values-ur/strings.xml b/packages/CredentialManager/res/values-ur/strings.xml
index 2d66079..50fbb8d 100644
--- a/packages/CredentialManager/res/values-ur/strings.xml
+++ b/packages/CredentialManager/res/values-ur/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"دوسرے مقام میں محفوظ کریں"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"کوئی دوسرا آلہ استعمال کریں"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"دوسرے آلے میں محفوظ کریں"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"محفوظ طریقے سے سائن ان کرنے کا آسان طریقہ"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"اپنے فنگر پرنٹ، چہرے یا اسکرین لاک کا استعمال کریں تاکہ ایک ایسی منفرد پاس کی سے سائن ان کیا جا سکے جسے بھولا یا چوری نہیں کیا جا سکتا۔ مزید جانیں"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> کی جگہ منتخب کریں"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> کی جگہ منتخب کریں"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"اپنی پاس کیز تخلیق کریں"</string>
     <string name="save_your_password" msgid="6597736507991704307">"اپنا پاس ورڈ محفوظ کریں"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"اپنے سائن ان کی معلومات محفوظ کریں"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"اپنے پاس ورڈز اور پاس کیز کو محفوظ کرنے اور اگلی بار تیزی سے سائن ان کرنے کے لیے ایک ڈیفالٹ پاس ورڈ مینیجر سیٹ کریں۔"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں پاس کی تخلیق کریں؟"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"اپنا پاس ورڈ <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"اپنے سائن ان کی معلومات کو <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> میں محفوظ کریں؟"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"پاس کی"</string>
     <string name="password" msgid="6738570945182936667">"پاس ورڈ"</string>
     <string name="sign_ins" msgid="4710739369149469208">"سائن انز"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"اس میں پاس کی تخلیق کریں"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"میں پاس ورڈ محفوظ کریں"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"میں سائن ان محفوظ کریں"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"کسی اور آلے میں پاس کی تخلیق کریں؟"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"اپنے سبھی سائن انز کے لیے <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> کا استعمال کریں؟"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"یہ پاس ورڈ مینیجر آپ کے پاس ورڈز اور پاس کیز کو آسانی سے سائن ان کرنے میں آپ کی مدد کرنے کے لیے اسٹور کرے گا۔"</string>
     <string name="set_as_default" msgid="4415328591568654603">"بطور ڈیفالٹ سیٹ کریں"</string>
     <string name="use_once" msgid="9027366575315399714">"ایک بار استعمال کریں"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> پاس ورڈز، <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> پاس کیز"</string>
diff --git a/packages/CredentialManager/res/values-uz/strings.xml b/packages/CredentialManager/res/values-uz/strings.xml
index 4ac35b2..b46979a 100644
--- a/packages/CredentialManager/res/values-uz/strings.xml
+++ b/packages/CredentialManager/res/values-uz/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Hisob maʼlumotlari menejeri"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Bekor qilish"</string>
     <string name="string_continue" msgid="1346732695941131882">"Davom etish"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Boshqa joyda yaratish"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Boshqa joyga saqlash"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Boshqa qurilmadan foydalaning"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Boshqa qurilmaga saqlash"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Xavfsiz kirishning oddiy usuli"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Esda qoladigan maxsus kalit bilan kirishda barmoq izi, yuz axboroti yoki ekran qulfidan foydalaning. Batafsil"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> joyini tanlang"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"<xliff:g id="CREATETYPES">%1$s</xliff:g> joyini tanlang"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"kodlar yaratish"</string>
     <string name="save_your_password" msgid="6597736507991704307">"Parolni saqlash"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"kirish axborotini saqlang"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Parol va kodlarni saqlash va keyingi safar tezroq kirish uchun standart parollar menejerini sozlang."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Kalit <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida yaratilsinmi?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Parol <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Hisob maʼlumotlari <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> xizmatida saqlansinmi?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"kalit"</string>
     <string name="password" msgid="6738570945182936667">"parol"</string>
     <string name="sign_ins" msgid="4710739369149469208">"kirishlar"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Kod yaratish vositasi"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Parolni bu hisobga saqlash"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Kirish axborotini saqlash"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Boshqa qurilmada kod yaratilsinmi?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Hamma kirishlarda <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> ishlatilsinmi?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Bu parollar menejerida hisobga oson kirishga yordam beruvchi parol va kalitlar saqlanadi."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Birlamchi deb belgilash"</string>
     <string name="use_once" msgid="9027366575315399714">"Bir marta ishlatish"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> ta kalit"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> ta parol"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> ta kalit"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Kod"</string>
     <string name="another_device" msgid="5147276802037801217">"Boshqa qurilma"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Boshqa parol menejerlari"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Varaqni yopish"</string>
diff --git a/packages/CredentialManager/res/values-vi/strings.xml b/packages/CredentialManager/res/values-vi/strings.xml
index fd5b986..6c3022e 100644
--- a/packages/CredentialManager/res/values-vi/strings.xml
+++ b/packages/CredentialManager/res/values-vi/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Trình quản lý thông tin xác thực"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Huỷ"</string>
     <string name="string_continue" msgid="1346732695941131882">"Tiếp tục"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Tạo ở vị trí khác"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Lưu vào vị trí khác"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Dùng thiết bị khác"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Lưu vào thiết bị khác"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Cách đơn giản để đăng nhập an toàn"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Dùng vân tay, khuôn mặt hoặc phương thức khoá màn hình để đăng nhập bằng một mã xác thực duy nhất mà bạn không lo sẽ quên hay bị đánh cắp. Tìm hiểu thêm"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"Chọn vị trí <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"Chọn vị trí <xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"tạo mã xác thực"</string>
     <string name="save_your_password" msgid="6597736507991704307">"lưu mật khẩu của bạn"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"lưu thông tin đăng nhập của bạn"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"Đặt một trình quản lý mật khẩu mặc định để lưu mật khẩu và mã xác thực của bạn, nhờ đó, bạn sẽ đăng nhập nhanh hơn vào lần sau."</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"Tạo một mã xác thực trong <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"Lưu mật khẩu của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"Lưu thông tin đăng nhập của bạn vào <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"mã xác thực"</string>
     <string name="password" msgid="6738570945182936667">"mật khẩu"</string>
     <string name="sign_ins" msgid="4710739369149469208">"thông tin đăng nhập"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"Tạo mã xác thực trong"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"Lưu mật khẩu vào"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"Lưu thông tin đăng nhập vào"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"Tạo mã xác thực trong một thiết bị khác?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"Dùng <xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g> cho mọi thông tin đăng nhập của bạn?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"Trình quản lý mật khẩu này sẽ lưu trữ mật khẩu và mã xác thực của bạn để bạn dễ dàng đăng nhập."</string>
     <string name="set_as_default" msgid="4415328591568654603">"Đặt làm mặc định"</string>
     <string name="use_once" msgid="9027366575315399714">"Dùng một lần"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu, <xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> mã xác thực"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> mật khẩu"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> mã xác thực"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Mã xác thực"</string>
     <string name="another_device" msgid="5147276802037801217">"Thiết bị khác"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Trình quản lý mật khẩu khác"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Đóng trang tính"</string>
diff --git a/packages/CredentialManager/res/values-zh-rCN/strings.xml b/packages/CredentialManager/res/values-zh-rCN/strings.xml
index a14dd2f..9e6c514 100644
--- a/packages/CredentialManager/res/values-zh-rCN/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rCN/strings.xml
@@ -1,23 +1,26 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Credential Manager"</string>
     <string name="string_cancel" msgid="6369133483981306063">"取消"</string>
     <string name="string_continue" msgid="1346732695941131882">"继续"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"在另一位置创建"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"保存到另一位置"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"使用另一台设备"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"保存到其他设备"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"简单又安全的登录方式"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"借助指纹、人脸识别或屏幕锁定功能,使用不会被忘记或被盗且具有唯一性的通行密钥登录。了解详情"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"选择<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"选择<xliff:g id="CREATETYPES">%1$s</xliff:g>的位置"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"创建通行密钥"</string>
     <string name="save_your_password" msgid="6597736507991704307">"保存您的密码"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"保存您的登录信息"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"设置默认密码管理工具,以便保存您的密码和通行密钥,从而提高下次的登录速度。"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"在“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”中创建通行密钥?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"将您的密码保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"将您的登录信息保存至“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”?"</string>
@@ -25,24 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"通行密钥"</string>
     <string name="password" msgid="6738570945182936667">"密码"</string>
     <string name="sign_ins" msgid="4710739369149469208">"登录"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"在以下位置创建通行密钥"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"将密码保存到"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"将登录信息保存到"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"在其他设备上创建通行密钥?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"将“<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>”用于您的所有登录信息?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"此密码管理工具将会存储您的密码和通行密钥,以帮助您轻松登录。"</string>
     <string name="set_as_default" msgid="4415328591568654603">"设为默认项"</string>
     <string name="use_once" msgid="9027366575315399714">"使用一次"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 个通行密钥"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 个密码"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 个通行密钥"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"通行密钥"</string>
     <string name="another_device" msgid="5147276802037801217">"另一台设备"</string>
     <string name="other_password_manager" msgid="565790221427004141">"其他密码管理工具"</string>
     <string name="close_sheet" msgid="1393792015338908262">"关闭工作表"</string>
diff --git a/packages/CredentialManager/res/values-zh-rHK/strings.xml b/packages/CredentialManager/res/values-zh-rHK/strings.xml
index 71dfa1a..cc596d7 100644
--- a/packages/CredentialManager/res/values-zh-rHK/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rHK/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡便的登入方式"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"使用指紋、面孔或螢幕鎖定配合密鑰登入。密鑰獨一無二,您不用擔心忘記密鑰或密鑰被盜。瞭解詳情"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"建立密鑰"</string>
     <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資料"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"設定預設的密碼管理工具以儲存密碼和密碼密鑰,下次就能更快速登入。"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密鑰嗎?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資料儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
@@ -24,23 +28,18 @@
     <string name="passkey" msgid="632353688396759522">"密鑰"</string>
     <string name="password" msgid="6738570945182936667">"密碼"</string>
     <string name="sign_ins" msgid="4710739369149469208">"登入資料"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"在此裝置建立密鑰:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"將密碼儲存至以下位置:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"將登入資料儲存至以下位置:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"要在另一部裝置上建立密鑰嗎?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資料嗎?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"此密碼管理工具將儲存您的密碼和密鑰,協助您輕鬆登入。"</string>
     <string name="set_as_default" msgid="4415328591568654603">"設定為預設"</string>
     <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密鑰"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g> 個密鑰"</string>
-    <string name="passkey_before_subtitle" msgid="2448119456208647444">"密碼金鑰"</string>
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"密鑰"</string>
     <string name="another_device" msgid="5147276802037801217">"其他裝置"</string>
     <string name="other_password_manager" msgid="565790221427004141">"其他密碼管理工具"</string>
     <string name="close_sheet" msgid="1393792015338908262">"閂工作表"</string>
diff --git a/packages/CredentialManager/res/values-zh-rTW/strings.xml b/packages/CredentialManager/res/values-zh-rTW/strings.xml
index 0d636c2..1fa1bd6 100644
--- a/packages/CredentialManager/res/values-zh-rTW/strings.xml
+++ b/packages/CredentialManager/res/values-zh-rTW/strings.xml
@@ -8,15 +8,19 @@
     <string name="string_save_to_another_place" msgid="7590325934591079193">"儲存至其他位置"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"改用其他裝置"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"儲存至其他裝置"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"安全又簡單的登入方式"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"登入帳戶時,你可以使用指紋、人臉或螢幕鎖定功能搭配不重複的密碼金鑰,不必擔心忘記密碼金鑰或遭人竊取。瞭解詳情"</string>
-    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
-    <!-- no translation found for create_your_passkeys (8901224153607590596) -->
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
     <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
+    <string name="choose_provider_title" msgid="7245243990139698508">"選擇「<xliff:g id="CREATETYPES">%1$s</xliff:g>」的位置"</string>
+    <string name="create_your_passkeys" msgid="8901224153607590596">"建立金鑰密碼"</string>
     <string name="save_your_password" msgid="6597736507991704307">"儲存密碼"</string>
     <string name="save_your_sign_in_info" msgid="7213978049817076882">"儲存登入資訊"</string>
-    <!-- no translation found for choose_provider_body (8045759834416308059) -->
-    <skip />
+    <string name="choose_provider_body" msgid="8045759834416308059">"你可以設定預設的密碼管理工具以儲存密碼和密碼金鑰,下次就能更快速登入。"</string>
     <string name="choose_create_option_passkey_title" msgid="4146408187146573131">"要在「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」建立密碼金鑰嗎?"</string>
     <string name="choose_create_option_password_title" msgid="8812546498357380545">"要將密碼儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
     <string name="choose_create_option_sign_in_title" msgid="6318246378475961834">"要將登入資訊儲存至「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」嗎?"</string>
@@ -24,17 +28,12 @@
     <string name="passkey" msgid="632353688396759522">"密碼金鑰"</string>
     <string name="password" msgid="6738570945182936667">"密碼"</string>
     <string name="sign_ins" msgid="4710739369149469208">"登入資訊"</string>
-    <!-- no translation found for create_passkey_in_title (2714306562710897785) -->
-    <skip />
-    <!-- no translation found for save_password_to_title (3450480045270186421) -->
-    <skip />
-    <!-- no translation found for save_sign_in_to_title (8328143607671760232) -->
-    <skip />
-    <!-- no translation found for create_passkey_in_other_device_title (6372952459932674632) -->
-    <skip />
+    <string name="create_passkey_in_title" msgid="2714306562710897785">"在以下位置建立密碼金鑰:"</string>
+    <string name="save_password_to_title" msgid="3450480045270186421">"將密碼儲存到以下位置:"</string>
+    <string name="save_sign_in_to_title" msgid="8328143607671760232">"將登入資訊儲存到以下位置:"</string>
+    <string name="create_passkey_in_other_device_title" msgid="6372952459932674632">"要在另一部裝置上建立密碼金鑰嗎?"</string>
     <string name="use_provider_for_all_title" msgid="4201020195058980757">"要將「<xliff:g id="PROVIDERINFODISPLAYNAME">%1$s</xliff:g>」用於所有的登入資訊嗎?"</string>
-    <!-- no translation found for use_provider_for_all_description (6560593199974037820) -->
-    <skip />
+    <string name="use_provider_for_all_description" msgid="6560593199974037820">"這個密碼管理工具會儲存你的密碼和密碼金鑰,協助你輕鬆登入。"</string>
     <string name="set_as_default" msgid="4415328591568654603">"設為預設"</string>
     <string name="use_once" msgid="9027366575315399714">"單次使用"</string>
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g> 個密碼,<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g> 個密碼金鑰"</string>
diff --git a/packages/CredentialManager/res/values-zu/strings.xml b/packages/CredentialManager/res/values-zu/strings.xml
index a35c6d2..772104d 100644
--- a/packages/CredentialManager/res/values-zu/strings.xml
+++ b/packages/CredentialManager/res/values-zu/strings.xml
@@ -1,16 +1,21 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- no translation found for app_name (4539824758261855508) -->
-    <skip />
+    <string name="app_name" msgid="4539824758261855508">"Umphathi Wezimfanelo"</string>
     <string name="string_cancel" msgid="6369133483981306063">"Khansela"</string>
     <string name="string_continue" msgid="1346732695941131882">"Qhubeka"</string>
     <string name="string_create_in_another_place" msgid="1033635365843437603">"Sungula kwenye indawo"</string>
     <string name="string_save_to_another_place" msgid="7590325934591079193">"Londoloza kwenye indawo"</string>
     <string name="string_use_another_device" msgid="8754514926121520445">"Sebenzisa enye idivayisi"</string>
     <string name="string_save_to_another_device" msgid="1959562542075194458">"Londoloza kwenye idivayisi"</string>
-    <string name="passkey_creation_intro_title" msgid="402553911484409884">"Indlela elula yokungena ngemvume ngokuphephile"</string>
-    <string name="passkey_creation_intro_body" msgid="7493320456005579290">"Sebenzisa isigxivizo somunwe, ubuso noma ukukhiya isikrini ukuze ungene ngemvume ngokhiye wokudlula oyingqayizivele ongenakulibaleka noma owebiwe. Funda kabanzi"</string>
+    <!-- no translation found for passkey_creation_intro_title (4251037543787718844) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_password (312712407571126228) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_fingerprint (691816235541508203) -->
+    <skip />
+    <!-- no translation found for passkey_creation_intro_body_device (477121861162321129) -->
+    <skip />
     <string name="choose_provider_title" msgid="7245243990139698508">"Khetha lapho onga-<xliff:g id="CREATETYPES">%1$s</xliff:g>"</string>
     <!-- no translation found for create_your_passkeys (8901224153607590596) -->
     <skip />
@@ -41,8 +46,7 @@
     <string name="more_options_usage_passwords_passkeys" msgid="4794903978126339473">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>, okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%2$s</xliff:g>"</string>
     <string name="more_options_usage_passwords" msgid="1632047277723187813">"Amaphasiwedi angu-<xliff:g id="PASSWORDSNUMBER">%1$s</xliff:g>"</string>
     <string name="more_options_usage_passkeys" msgid="5390320437243042237">"Okhiye bokudlula abangu-<xliff:g id="PASSKEYSNUMBER">%1$s</xliff:g>"</string>
-    <!-- no translation found for passkey_before_subtitle (2448119456208647444) -->
-    <skip />
+    <string name="passkey_before_subtitle" msgid="2448119456208647444">"Ukhiye wokudlula"</string>
     <string name="another_device" msgid="5147276802037801217">"Enye idivayisi"</string>
     <string name="other_password_manager" msgid="565790221427004141">"Abanye abaphathi bephasiwedi"</string>
     <string name="close_sheet" msgid="1393792015338908262">"Vala ishidi"</string>
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index 8c9023c..a3ebf1e 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -18,9 +18,13 @@
   <!-- This appears as a text button where users can click to save this credential to another device. [CHAR LIMIT=80] -->
   <string name="string_save_to_another_device">Save to another device</string>
   <!-- This appears as the title of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_title">A simple way to sign in safely</string>
-  <!-- This appears as the description body of the modal bottom sheet introducing what is passkey to users. [CHAR LIMIT=200] -->
-  <string name="passkey_creation_intro_body">Use your fingerprint, face or screen lock to sign in with a unique passkey that can’t be forgotten or stolen. Learn more</string>
+  <string name="passkey_creation_intro_title">Safer with passkeys</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the passwords side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_password">No need to create or remember complex passwords</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the safety side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_fingerprint">Use your fingerprint, face, or screen lock to create a unique passkey</string>
+  <!-- This appears as the description body of the modal bottom sheet introducing why passkey beneficial on the using other devices side. [CHAR LIMIT=200] -->
+  <string name="passkey_creation_intro_body_device">Passkeys are saved to a password manager, so you can sign in on other devices</string>
   <!-- This appears as the title of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
   <string name="choose_provider_title">Choose where to <xliff:g id="createTypes" example="create your passkeys">%1$s</xliff:g></string>
   <!-- Create types which are inserted as a placeholder for string choose_provider_title. [CHAR LIMIT=200] -->
@@ -76,6 +80,8 @@
   <string name="close_sheet">"Close sheet"</string>
   <!-- Spoken content description of the back arrow button. -->
   <string name="accessibility_back_arrow_button">"Go back to the previous page"</string>
+  <!-- Spoken content description of the close button. -->
+  <string name="accessibility_close_button">"Close the Credential Manager action suggestion appearing at the bottom of the screen"</string>
 
   <!-- Strings for the get flow. -->
   <!-- This appears as the title of the modal bottom sheet asking for user confirmation to use the single previously saved passkey to sign in to the app. [CHAR LIMIT=200] -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 1db1d1c..4faf00c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -40,14 +40,10 @@
 import android.os.Bundle
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
-import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreateCredentialUiState
-import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.createflow.EnabledProviderInfo
 import com.android.credentialmanager.createflow.RemoteInfo
-import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
-import com.android.credentialmanager.getflow.GetScreenState
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest.Companion.toBundle
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
@@ -67,7 +63,7 @@
     requestInfo = intent.extras?.getParcelable(
       RequestInfo.EXTRA_REQUEST_INFO,
       RequestInfo::class.java
-    ) ?: testCreatePasskeyRequestInfo()
+    ) ?: testGetRequestInfo()
 
     providerEnabledList = when (requestInfo.type) {
       RequestInfo.TYPE_CREATE ->
@@ -127,11 +123,9 @@
     val providerEnabledList = GetFlowUtils.toProviderList(
     // TODO: handle runtime cast error
       providerEnabledList as List<GetCredentialProviderData>, context)
-    // TODO: covert from real requestInfo
-    val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo("the app")
+    val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo)
     return GetCredentialUiState(
       providerEnabledList,
-      GetScreenState.PRIMARY_SELECTION,
       requestDisplayInfo,
     )
   }
@@ -146,20 +140,30 @@
       providerDisabledList, context)
     var defaultProvider: EnabledProviderInfo? = null
     var remoteEntry: RemoteInfo? = null
+    var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
       if (providerInfo.isDefault) {defaultProvider = providerInfo}
       if (providerInfo.remoteEntry != null) {
         remoteEntry = providerInfo.remoteEntry!!
       }
+      if (providerInfo.createOptions.isNotEmpty()) {
+        createOptionSize += providerInfo.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = providerInfo
+      }
     }
     return CreateCredentialUiState(
       enabledProviders = providerEnabledList,
       disabledProviders = providerDisabledList,
-      toCreateScreenState(requestDisplayInfo, defaultProvider, remoteEntry),
+      CreateFlowUtils.toCreateScreenState(
+        createOptionSize, false,
+        requestDisplayInfo, defaultProvider, remoteEntry),
       requestDisplayInfo,
       false,
-      toActiveEntry(defaultProvider, remoteEntry),
+      CreateFlowUtils.toActiveEntry(
+        /*defaultProvider=*/defaultProvider, createOptionSize,
+        lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
     )
   }
 
@@ -194,6 +198,7 @@
         .setRemoteEntry(
           newRemoteEntry("key2", "subkey-1")
         )
+        .setIsDefaultProvider(true)
         .build(),
       CreateCredentialProviderData
         .Builder("com.dashlane")
@@ -240,12 +245,10 @@
           listOf(
             newActionEntry(
               "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
-              Icon.createWithResource(context, R.drawable.ic_manage_accounts),
               "Open Google Password Manager", "elisa.beckett@gmail.com"
             ),
             newActionEntry(
               "key3", "subkey-2", TYPE_PASSWORD_CREDENTIAL,
-              Icon.createWithResource(context, R.drawable.ic_manage_accounts),
               "Open Google Password Manager", "beckett-family@gmail.com"
             ),
           )
@@ -270,7 +273,6 @@
           listOf(
             newActionEntry(
               "key3", "subkey-1", TYPE_PASSWORD_CREDENTIAL,
-              Icon.createWithResource(context, R.drawable.ic_face),
               "Open Enpass"
             ),
           )
@@ -282,7 +284,6 @@
     key: String,
     subkey: String,
     credentialType: String,
-    icon: Icon,
     text: String,
     subtext: String? = null,
   ): Entry {
@@ -290,7 +291,7 @@
       Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
     ).addText(
       text, null, listOf(Entry.HINT_ACTION_TITLE)
-    ).addIcon(icon, null, listOf(Entry.HINT_ACTION_ICON))
+    )
     if (subtext != null) {
       slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
     }
@@ -510,45 +511,11 @@
       GetCredentialRequest.Builder()
         .addGetCredentialOption(
           GetCredentialOption(
-            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), /*requireSystemProvider=*/ false)
+            TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
         )
         .build(),
       /*isFirstUsage=*/false,
       "tribank.us"
     )
   }
-
-  private fun toCreateScreenState(
-    requestDisplayInfo: RequestDisplayInfo,
-    defaultProvider: EnabledProviderInfo?,
-    remoteEntry: RemoteInfo?,
-  ): CreateScreenState {
-    return if (
-      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null
-    ){
-      CreateScreenState.EXTERNAL_ONLY_SELECTION
-    } else if (defaultProvider == null || defaultProvider.createOptions.isEmpty()) {
-      if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL) {
-        CreateScreenState.PASSKEY_INTRO
-      } else {
-        CreateScreenState.PROVIDER_SELECTION
-      }
-    } else {
-      CreateScreenState.CREATION_OPTION_SELECTION
-    }
-  }
-
-  private fun toActiveEntry(
-    defaultProvider: EnabledProviderInfo?,
-    remoteEntry: RemoteInfo?,
-  ): ActiveEntry? {
-    return if (
-      defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
-    ) {
-      ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
-    } else if (
-      defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
-      ActiveEntry(defaultProvider, remoteEntry)
-    } else null
-  }
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index 357c55d..56fbf66 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -28,6 +28,9 @@
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.createflow.RemoteInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.getflow.ActionEntryInfo
 import com.android.credentialmanager.getflow.AuthenticationEntryInfo
 import com.android.credentialmanager.getflow.CredentialEntryInfo
@@ -36,6 +39,7 @@
 import com.android.credentialmanager.jetpack.developer.CreateCredentialRequest
 import com.android.credentialmanager.jetpack.developer.CreatePasswordRequest
 import com.android.credentialmanager.jetpack.developer.CreatePublicKeyCredentialRequest
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 import com.android.credentialmanager.jetpack.provider.ActionUi
 import com.android.credentialmanager.jetpack.provider.CredentialEntryUi
 import com.android.credentialmanager.jetpack.provider.SaveEntryUi
@@ -63,7 +67,8 @@
           .getPackageInfo(packageName!!,
             PackageManager.PackageInfoFlags.of(0))
         val providerDisplayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString()
-        // TODO: decide what to do when failed to load a provider icon
+        // TODO: get the provider icon from the service
+        //  and decide what to do when failed to load a provider icon
         val providerIcon = pkgInfo.applicationInfo.loadIcon(packageManager)!!
         ProviderInfo(
           id = it.providerFlattenedComponentName,
@@ -79,11 +84,19 @@
             it.authenticationEntry),
           remoteEntry = getRemoteEntry(it.providerFlattenedComponentName, it.remoteEntry),
           actionEntryList = getActionEntryList(
-            it.providerFlattenedComponentName, it.actionChips, context),
+            it.providerFlattenedComponentName, it.actionChips, providerIcon),
         )
       }
     }
 
+    fun toRequestDisplayInfo(
+      requestInfo: RequestInfo,
+    ): com.android.credentialmanager.getflow.RequestDisplayInfo {
+      return com.android.credentialmanager.getflow.RequestDisplayInfo(
+        appDomainName = requestInfo.appPackageName
+      )
+    }
+
 
     /* From service data structure to UI credential entry list representation. */
     private fun getCredentialOptionInfoList(
@@ -107,7 +120,7 @@
           displayName = credentialEntryUi.userDisplayName?.toString(),
           // TODO: proper fallback
           icon = credentialEntryUi.entryIcon?.loadDrawable(context)
-            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+            ?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
           lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
         )
       }
@@ -152,7 +165,7 @@
     private fun getActionEntryList(
       providerId: String,
       actionEntries: List<Entry>,
-      context: Context,
+      providerIcon: Drawable,
     ): List<ActionEntryInfo> {
       return actionEntries.map {
         val actionEntryUi = ActionUi.fromSlice(it.slice)
@@ -165,7 +178,7 @@
           fillInIntent = it.frameworkExtrasIntent,
           title = actionEntryUi.text.toString(),
           // TODO: gracefully fail
-          icon = actionEntryUi.icon.loadDrawable(context)!!,
+          icon = providerIcon,
           subTitle = actionEntryUi.subtext?.toString(),
         )
       }
@@ -243,7 +256,8 @@
             createCredentialRequestJetpack.password,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_password)!!
+            context.getDrawable(R.drawable.ic_password)!!,
+            requestInfo.isFirstUsage
           )
         }
         is CreatePublicKeyCredentialRequest -> {
@@ -261,7 +275,8 @@
             displayName,
             createCredentialRequestJetpack.type,
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_passkey)!!)
+            context.getDrawable(R.drawable.ic_passkey)!!,
+            requestInfo.isFirstUsage)
         }
         // TODO: correctly parsing for other sign-ins
         else -> {
@@ -270,11 +285,58 @@
             "Elisa Beckett",
             "other-sign-ins",
             requestInfo.appPackageName,
-            context.getDrawable(R.drawable.ic_other_sign_in)!!)
+            context.getDrawable(R.drawable.ic_other_sign_in)!!,
+            requestInfo.isFirstUsage)
         }
       }
     }
 
+    fun toCreateScreenState(
+      createOptionSize: Int,
+      isOnPasskeyIntroStateAlready: Boolean,
+      requestDisplayInfo: RequestDisplayInfo,
+      defaultProvider: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): CreateScreenState {
+      return if (requestDisplayInfo.isFirstUsage && requestDisplayInfo
+          .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
+        CreateScreenState.PASSKEY_INTRO
+      } else if (
+        (defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize > 1) {
+        CreateScreenState.PROVIDER_SELECTION
+      } else if (
+        ((defaultProvider == null || defaultProvider.createOptions.isEmpty()
+                ) && createOptionSize == 1) || (
+                defaultProvider != null && defaultProvider.createOptions.isNotEmpty())) {
+        CreateScreenState.CREATION_OPTION_SELECTION
+      } else if (createOptionSize == 0 && remoteEntry != null) {
+        CreateScreenState.EXTERNAL_ONLY_SELECTION
+      } else {
+          // TODO: properly handle error and gracefully finish itself
+          throw java.lang.IllegalStateException("Empty provider list.")
+      }
+    }
+
+   fun toActiveEntry(
+      defaultProvider: EnabledProviderInfo?,
+      createOptionSize: Int,
+      lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo?,
+      remoteEntry: RemoteInfo?,
+    ): ActiveEntry? {
+      return if (
+        defaultProvider != null && defaultProvider.createOptions.isEmpty() && remoteEntry != null) {
+        ActiveEntry(defaultProvider, remoteEntry)
+      } else if (
+        defaultProvider != null && defaultProvider.createOptions.isNotEmpty()
+      ) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else if (createOptionSize == 1) {
+        ActiveEntry(lastSeenProviderWithNonEmptyCreateOptions!!,
+          lastSeenProviderWithNonEmptyCreateOptions.createOptions.first())
+      } else null
+    }
+
     private fun toCreationOptionInfoList(
       providerId: String,
       creationEntries: List<Entry>,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 57e20be..c26fac8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -118,7 +118,7 @@
                         onCancel = viewModel::onCancel,
                     )
                 }
-            } else if (uiState.hidden && uiState.selectedEntry != null) {
+            } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
                 viewModel.launchProviderUi(providerActivityLauncher)
             }
         },
@@ -140,12 +140,11 @@
 ) {
     ContainerCard() {
         Column() {
-            Icon(
-                painter = painterResource(R.drawable.ic_passkey),
+            Image(
+                painter = painterResource(R.drawable.ic_passkeys_onboarding),
                 contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
                 modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(top = 24.dp, bottom = 12.dp)
+                    .padding(top = 24.dp, bottom = 12.dp).size(316.dp, 168.dp)
             )
             TextOnSurface(
                 text = stringResource(R.string.passkey_creation_intro_title),
@@ -159,11 +158,62 @@
                 thickness = 16.dp,
                 color = Color.Transparent
             )
-            TextSecondary(
-                text = stringResource(R.string.passkey_creation_intro_body),
-                style = MaterialTheme.typography.bodyLarge,
-                modifier = Modifier.padding(horizontal = 28.dp),
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_password),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_password),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
             )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_fingerprint),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
+            Divider(
+                thickness = 16.dp,
+                color = Color.Transparent
+            )
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                verticalAlignment = Alignment.CenterVertically,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                Image(
+                    modifier = Modifier.size(24.dp),
+                    painter = painterResource(R.drawable.ic_passkeys_onboarding_device),
+                    contentDescription = null
+                )
+                TextSecondary(
+                    text = stringResource(R.string.passkey_creation_intro_body_device),
+                    style = MaterialTheme.typography.bodyMedium,
+                    modifier = Modifier.padding(start = 16.dp),
+                )
+            }
             Divider(
                 thickness = 32.dp,
                 color = Color.Transparent
@@ -263,7 +313,7 @@
                             }
                         }
                     }
-                    if (disabledProviderList != null) {
+                    if (disabledProviderList != null && disabledProviderList.isNotEmpty()) {
                         item {
                             MoreOptionsDisabledProvidersRow(
                                 disabledProviders = disabledProviderList,
@@ -381,14 +431,12 @@
                             }
                         }
                     }
-                    if (disabledProviderList != null) {
-                        item {
-                            MoreOptionsDisabledProvidersRow(
-                                disabledProviders = disabledProviderList,
-                                onDisabledPasswordManagerSelected =
-                                onDisabledPasswordManagerSelected,
-                            )
-                        }
+                    item {
+                        MoreOptionsDisabledProvidersRow(
+                            disabledProviders = disabledProviderList,
+                            onDisabledPasswordManagerSelected =
+                            onDisabledPasswordManagerSelected,
+                        )
                     }
                     // TODO: handle the error situation that if multiple remoteInfos exists
                     enabledProviderList.forEach {
@@ -698,7 +746,7 @@
                 },
                 contentDescription = null,
                 tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.padding(start = 18.dp).size(32.dp)
+                modifier = Modifier.padding(start = 10.dp).size(32.dp)
             )
         },
         label = {
@@ -709,7 +757,7 @@
                         TextOnSurfaceVariant(
                             text = requestDisplayInfo.title,
                             style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp),
+                            modifier = Modifier.padding(top = 16.dp, start = 5.dp),
                         )
                         TextSecondary(
                             text = if (requestDisplayInfo.subtitle != null) {
@@ -720,27 +768,27 @@
                                 stringResource(R.string.passkey_before_subtitle)
                             },
                             style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp),
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                         )
                     }
                     TYPE_PASSWORD_CREDENTIAL -> {
                         TextOnSurfaceVariant(
                             text = requestDisplayInfo.title,
                             style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp),
+                            modifier = Modifier.padding(top = 16.dp, start = 5.dp),
                         )
                         TextSecondary(
                             // This subtitle would never be null for create password
                             text = requestDisplayInfo.subtitle ?: "",
                             style = MaterialTheme.typography.bodyMedium,
-                            modifier = Modifier.padding(bottom = 16.dp),
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                         )
                     }
                     else -> {
                         TextOnSurfaceVariant(
                             text = requestDisplayInfo.title,
                             style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp),
+                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp, start = 5.dp),
                         )
                     }
                 }
@@ -760,7 +808,7 @@
         onClick = onOptionSelected,
         icon = {
             Image(
-                modifier = Modifier.size(32.dp).padding(start = 16.dp),
+                modifier = Modifier.padding(start = 10.dp).size(32.dp),
                 bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                 contentDescription = null
             )
@@ -770,13 +818,18 @@
                 TextOnSurfaceVariant(
                     text = providerInfo.displayName,
                     style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+                    modifier = Modifier.padding(top = 16.dp, start = 5.dp),
                 )
                 if (createOptionInfo.userProviderDisplayName != null) {
                     TextSecondary(
                         text = createOptionInfo.userProviderDisplayName,
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(start = 16.dp),
+                        // TODO: update the logic here for the case there is only total count
+                        modifier = if (
+                            createOptionInfo.passwordCount != null ||
+                            createOptionInfo.passkeyCount != null
+                        ) Modifier.padding(start = 5.dp) else Modifier
+                            .padding(bottom = 16.dp, start = 5.dp),
                     )
                 }
                 if (createOptionInfo.passwordCount != null &&
@@ -790,7 +843,7 @@
                             createOptionInfo.passkeyCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                     )
                 } else if (createOptionInfo.passwordCount != null) {
                     TextSecondary(
@@ -800,7 +853,7 @@
                             createOptionInfo.passwordCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                     )
                 } else if (createOptionInfo.passkeyCount != null) {
                     TextSecondary(
@@ -810,7 +863,7 @@
                             createOptionInfo.passkeyCount
                         ),
                         style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
+                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                     )
                 } else if (createOptionInfo.totalCredentialCount != null) {
                     // TODO: Handle the case when there is total count
@@ -824,34 +877,36 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsDisabledProvidersRow(
-    disabledProviders: List<ProviderInfo>,
+    disabledProviders: List<ProviderInfo>?,
     onDisabledPasswordManagerSelected: () -> Unit,
 ) {
-    Entry(
-        onClick = onDisabledPasswordManagerSelected,
-        icon = {
-            Icon(
-                Icons.Filled.Add,
-                contentDescription = null,
-                modifier = Modifier.padding(start = 16.dp)
-            )
-        },
-        label = {
-            Column() {
-                TextOnSurfaceVariant(
-                    text = stringResource(R.string.other_password_manager),
-                    style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp, start = 16.dp),
+    if (disabledProviders != null && disabledProviders.isNotEmpty()) {
+        Entry(
+            onClick = onDisabledPasswordManagerSelected,
+            icon = {
+                Icon(
+                    Icons.Filled.Add,
+                    contentDescription = null,
+                    modifier = Modifier.padding(start = 16.dp)
                 )
-                // TODO: Update the subtitle once design is confirmed
-                TextSecondary(
-                    text = disabledProviders.joinToString(separator = ", ") { it.displayName },
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(bottom = 16.dp, start = 16.dp),
-                )
+            },
+            label = {
+                Column() {
+                    TextOnSurfaceVariant(
+                        text = stringResource(R.string.other_password_manager),
+                        style = MaterialTheme.typography.titleLarge,
+                        modifier = Modifier.padding(top = 16.dp, start = 5.dp),
+                    )
+                    // TODO: Update the subtitle once design is confirmed
+                    TextSecondary(
+                        text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+                        style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                    )
+                }
             }
-        }
-    )
+        )
+    }
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 6f74998..518aaee 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -27,6 +27,7 @@
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
+import com.android.credentialmanager.CreateFlowUtils
 import com.android.credentialmanager.CredentialManagerRepo
 import com.android.credentialmanager.common.DialogResult
 import com.android.credentialmanager.common.ProviderActivityResult
@@ -41,6 +42,7 @@
   val activeEntry: ActiveEntry? = null,
   val selectedEntry: EntryInfo? = null,
   val hidden: Boolean = false,
+  val providerActivityPending: Boolean = false,
 )
 
 class CreateCredentialViewModel(
@@ -60,24 +62,26 @@
 
   fun onConfirmIntro() {
     var createOptionSize = 0
+    var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
+    var remoteEntry: RemoteInfo? = null
     uiState.enabledProviders.forEach {
-      enabledProvider -> createOptionSize += enabledProvider.createOptions.size}
-    uiState = if (createOptionSize > 1) {
-      uiState.copy(
-        currentScreenState = CreateScreenState.PROVIDER_SELECTION,
-        showActiveEntryOnly = true
-      )
-    } else if (createOptionSize == 1){
-      uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-        showActiveEntryOnly = false,
-        activeEntry = ActiveEntry(uiState.enabledProviders.first(),
-          uiState.enabledProviders.first().createOptions.first()
-        )
-      )
-    } else {
-      throw java.lang.IllegalStateException("Empty provider list.")
+      enabledProvider ->
+      if (enabledProvider.createOptions.isNotEmpty()) {
+        createOptionSize += enabledProvider.createOptions.size
+        lastSeenProviderWithNonEmptyCreateOptions = enabledProvider
+      }
+      if (enabledProvider.remoteEntry != null) {
+        remoteEntry = enabledProvider.remoteEntry!!
+      }
     }
+    uiState = uiState.copy(
+      currentScreenState = CreateFlowUtils.toCreateScreenState(
+        createOptionSize, true,
+        uiState.requestDisplayInfo, null, remoteEntry),
+      showActiveEntryOnly = createOptionSize > 1,
+      activeEntry = CreateFlowUtils.toActiveEntry(
+        null, createOptionSize, lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+    )
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -159,6 +163,9 @@
   ) {
     val entry = uiState.selectedEntry
     if (entry != null && entry.pendingIntent != null) {
+      uiState = uiState.copy(
+        providerActivityPending = true,
+      )
       val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
         .setFillInIntent(entry.fillInIntent).build()
       launcher.launch(intentSenderRequest)
@@ -189,6 +196,7 @@
       uiState = uiState.copy(
         selectedEntry = null,
         hidden = false,
+        providerActivityPending = false,
       )
     } else {
       if (entry != null) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index 9ac524a..21abe08 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -77,6 +77,7 @@
   val type: String,
   val appDomainName: String,
   val typeIcon: Drawable,
+  val isFirstUsage: Boolean,
 )
 
 /**
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 21342a1..619f5a3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -16,6 +16,7 @@
 
 package com.android.credentialmanager.getflow
 
+import android.credentials.Credential
 import android.text.TextUtils
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.result.ActivityResult
@@ -32,15 +33,20 @@
 import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.outlined.Lock
 import androidx.compose.material3.Divider
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Snackbar
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.Alignment
@@ -65,6 +71,7 @@
 import com.android.credentialmanager.common.ui.TransparentBackgroundEntry
 import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
 import com.android.credentialmanager.ui.theme.EntryShape
+import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme
 
 @Composable
 fun GetCredentialScreen(
@@ -75,39 +82,50 @@
         initialValue = ModalBottomSheetValue.Expanded,
         skipHalfExpanded = true
     )
-    ModalBottomSheetLayout(
-        sheetBackgroundColor = MaterialTheme.colorScheme.surface,
-        modifier = Modifier.background(Color.Transparent),
-        sheetState = state,
-        sheetContent = {
-            val uiState = viewModel.uiState
-            if (!uiState.hidden) {
-                when (uiState.currentScreenState) {
-                    GetScreenState.PRIMARY_SELECTION -> PrimarySelectionCard(
-                        requestDisplayInfo = uiState.requestDisplayInfo,
-                        providerDisplayInfo = uiState.providerDisplayInfo,
-                        onEntrySelected = viewModel::onEntrySelected,
-                        onCancel = viewModel::onCancel,
-                        onMoreOptionSelected = viewModel::onMoreOptionSelected,
-                    )
-                    GetScreenState.ALL_SIGN_IN_OPTIONS -> AllSignInOptionCard(
-                        providerInfoList = uiState.providerInfoList,
-                        providerDisplayInfo = uiState.providerDisplayInfo,
-                        onEntrySelected = viewModel::onEntrySelected,
-                        onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
-                    )
+    val uiState = viewModel.uiState
+    if (uiState.currentScreenState != GetScreenState.REMOTE_ONLY) {
+        ModalBottomSheetLayout(
+            sheetBackgroundColor = MaterialTheme.colorScheme.surface,
+            modifier = Modifier.background(Color.Transparent),
+            sheetState = state,
+            sheetContent = {
+                // TODO: hide UI at top level
+                if (!uiState.hidden) {
+                    if (uiState.currentScreenState == GetScreenState.PRIMARY_SELECTION) {
+                        PrimarySelectionCard(
+                            requestDisplayInfo = uiState.requestDisplayInfo,
+                            providerDisplayInfo = uiState.providerDisplayInfo,
+                            onEntrySelected = viewModel::onEntrySelected,
+                            onCancel = viewModel::onCancel,
+                            onMoreOptionSelected = viewModel::onMoreOptionSelected,
+                        )
+                    } else {
+                        AllSignInOptionCard(
+                            providerInfoList = uiState.providerInfoList,
+                            providerDisplayInfo = uiState.providerDisplayInfo,
+                            onEntrySelected = viewModel::onEntrySelected,
+                            onBackButtonClicked = viewModel::onBackToPrimarySelectionScreen,
+                            onCancel = viewModel::onCancel,
+                            isNoAccount = uiState.isNoAccount,
+                        )
+                    }
+                } else if (uiState.selectedEntry != null && !uiState.providerActivityPending) {
+                    viewModel.launchProviderUi(providerActivityLauncher)
                 }
-            } else if (uiState.hidden && uiState.selectedEntry != null) {
-                viewModel.launchProviderUi(providerActivityLauncher)
+            },
+            scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
+            sheetShape = EntryShape.TopRoundedCorner,
+        ) {}
+        LaunchedEffect(state.currentValue) {
+            if (state.currentValue == ModalBottomSheetValue.Hidden) {
+                viewModel.onCancel()
             }
-        },
-        scrimColor = MaterialTheme.colorScheme.scrim.copy(alpha = 0.8f),
-        sheetShape = EntryShape.TopRoundedCorner,
-    ) {}
-    LaunchedEffect(state.currentValue) {
-        if (state.currentValue == ModalBottomSheetValue.Hidden) {
-            viewModel.onCancel()
         }
+    } else {
+        SnackBarScreen(
+            onClick = viewModel::onMoreOptionOnSnackBarSelected,
+            onCancel = viewModel::onCancel,
+        )
     }
 }
 
@@ -173,7 +191,7 @@
                 color = Color.Transparent
             )
             Row(
-                horizontalArrangement = Arrangement.Start,
+                horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
                 CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
@@ -195,6 +213,8 @@
     providerDisplayInfo: ProviderDisplayInfo,
     onEntrySelected: (EntryInfo) -> Unit,
     onBackButtonClicked: () -> Unit,
+    onCancel: () -> Unit,
+    isNoAccount: Boolean,
 ) {
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -212,7 +232,7 @@
                     )
                 },
                 navigationIcon = {
-                    IconButton(onClick = onBackButtonClicked) {
+                    IconButton(onClick = if (isNoAccount) onCancel else onBackButtonClicked) {
                         Icon(
                             Icons.Filled.ArrowBack,
                             contentDescription = stringResource(
@@ -405,11 +425,12 @@
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         icon = {
-            Image(
+            Icon(
                 modifier = Modifier.padding(start = 10.dp).size(32.dp),
                 bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
                 // TODO: add description.
-                contentDescription = ""
+                contentDescription = "",
+                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
             )
         },
         label = {
@@ -418,19 +439,24 @@
                 TextOnSurfaceVariant(
                     text = credentialEntryInfo.userName,
                     style = MaterialTheme.typography.titleLarge,
-                    modifier = Modifier.padding(top = 16.dp)
+                    modifier = Modifier.padding(top = 16.dp, start = 5.dp)
                 )
                 TextSecondary(
-                    text =
-                    if (TextUtils.isEmpty(credentialEntryInfo.displayName))
-                        credentialEntryInfo.credentialTypeDisplayName
-                    else
-                        credentialEntryInfo.credentialTypeDisplayName +
-                                stringResource(
-                                    R.string.get_dialog_sign_in_type_username_separator) +
-                                credentialEntryInfo.displayName,
+                    text = if (
+                        credentialEntryInfo.credentialType == Credential.TYPE_PASSWORD_CREDENTIAL) {
+                        "••••••••••••"
+                    } else {
+                        if (TextUtils.isEmpty(credentialEntryInfo.displayName))
+                            credentialEntryInfo.credentialTypeDisplayName
+                        else
+                            credentialEntryInfo.credentialTypeDisplayName +
+                                    stringResource(
+                                        R.string.get_dialog_sign_in_type_username_separator
+                                    ) +
+                                    credentialEntryInfo.displayName
+                    },
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(bottom = 16.dp)
+                    modifier = Modifier.padding(bottom = 16.dp, start = 5.dp)
                 )
             }
         }
@@ -454,17 +480,27 @@
             )
         },
         label = {
-            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),
-                    style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(bottom = 16.dp)
+            Row(
+                horizontalArrangement = Arrangement.SpaceBetween,
+                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),
+                        style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(bottom = 16.dp)
+                    )
+                }
+                Icon(
+                    Icons.Outlined.Lock,
+                    null,
+                    Modifier.align(alignment = Alignment.CenterVertically).padding(end = 10.dp),
                 )
             }
         }
@@ -491,11 +527,13 @@
                 TextOnSurfaceVariant(
                     text = actionEntryInfo.title,
                     style = MaterialTheme.typography.titleLarge,
+                    modifier = Modifier.padding(start = 5.dp),
                 )
                 if (actionEntryInfo.subTitle != null) {
                     TextSecondary(
                         text = actionEntryInfo.subTitle,
                         style = MaterialTheme.typography.bodyMedium,
+                        modifier = Modifier.padding(start = 5.dp),
                     )
                 }
             }
@@ -518,3 +556,37 @@
         }
     )
 }
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SnackBarScreen(
+    onClick: (Boolean) -> Unit,
+    onCancel: () -> Unit,
+) {
+    // TODO: Change the height, width and position according to the design
+    Snackbar (
+        modifier = Modifier.padding(horizontal = 80.dp).padding(top = 700.dp),
+        shape = EntryShape.FullMediumRoundedCorner,
+        containerColor = LocalAndroidColorScheme.current.colorBackground,
+        contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+    ) {
+        Row(
+            horizontalArrangement = Arrangement.SpaceBetween,
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            TextButton(
+                onClick = {onClick(true)},
+            ) {
+                Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
+            }
+            IconButton(onClick = onCancel) {
+                Icon(
+                    Icons.Filled.Close,
+                    contentDescription = stringResource(
+                        R.string.accessibility_close_button
+                    )
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
index 33e7021..c182397 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -36,11 +36,13 @@
 
 data class GetCredentialUiState(
   val providerInfoList: List<ProviderInfo>,
-  val currentScreenState: GetScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
+  val currentScreenState: GetScreenState = toGetScreenState(providerInfoList),
   val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
   val selectedEntry: EntryInfo? = null,
   val hidden: Boolean = false,
+  val providerActivityPending: Boolean = false,
+  val isNoAccount: Boolean = false,
 )
 
 class GetCredentialViewModel(
@@ -79,6 +81,9 @@
   ) {
     val entry = uiState.selectedEntry
     if (entry != null && entry.pendingIntent != null) {
+      uiState = uiState.copy(
+        providerActivityPending = true,
+      )
       val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
         .setFillInIntent(entry.fillInIntent).build()
       launcher.launch(intentSenderRequest)
@@ -96,6 +101,7 @@
       uiState = uiState.copy(
         selectedEntry = null,
         hidden = false,
+        providerActivityPending = false,
       )
     } else {
       if (entry != null) {
@@ -122,6 +128,14 @@
     )
   }
 
+  fun onMoreOptionOnSnackBarSelected(isNoAccount: Boolean) {
+    Log.d("Account Selector", "More Option on snackBar selected")
+    uiState = uiState.copy(
+      currentScreenState = GetScreenState.ALL_SIGN_IN_OPTIONS,
+      isNoAccount = isNoAccount,
+    )
+  }
+
   fun onBackToPrimarySelectionScreen() {
     uiState = uiState.copy(
       currentScreenState = GetScreenState.PRIMARY_SELECTION
@@ -187,9 +201,36 @@
   )
 }
 
+private fun toGetScreenState(
+  providerInfoList: List<ProviderInfo>
+): GetScreenState {
+  var noLocalAccount = true
+  var remoteInfo: RemoteEntryInfo? = null
+  providerInfoList.forEach{providerInfo -> if (
+    providerInfo.credentialEntryList.isNotEmpty() || providerInfo.authenticationEntry != null
+  ) { noLocalAccount = false }
+    // TODO: handle the error situation that if multiple remoteInfos exists
+    if (providerInfo.remoteEntry != null) {
+      remoteInfo = providerInfo.remoteEntry
+    }
+  }
+
+  return if (noLocalAccount && remoteInfo != null)
+    GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
+}
+
 internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
   override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
-    // First order by last used timestamp
+    // First prefer passkey type for its security benefits
+    if (p0.credentialType != p1.credentialType) {
+      if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
+        return -1
+      } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
+        return 1
+      }
+    }
+
+    // Then order by last used timestamp
     if (p0.lastUsedTimeMillis != null && p1.lastUsedTimeMillis != null) {
       if (p0.lastUsedTimeMillis < p1.lastUsedTimeMillis) {
         return 1
@@ -201,15 +242,6 @@
     } else if (p1.lastUsedTimeMillis != null && p1.lastUsedTimeMillis > 0) {
       return 1
     }
-
-    // Then prefer passkey type for its security benefits
-    if (p0.credentialType != p1.credentialType) {
-      if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p0.credentialType) {
-        return -1
-      } else if (PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL == p1.credentialType) {
-        return 1
-      }
-    }
     return 0
   }
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 0c3baff..3a2a738 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -120,4 +120,6 @@
   PRIMARY_SELECTION,
   /** The secondary credential selection page, where all sign-in options are listed. */
   ALL_SIGN_IN_OPTIONS,
+  /** The snackbar only page when there's no account but only a remoteEntry. */
+  REMOTE_ONLY,
 }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
index eb65241..ef48a77 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
@@ -28,9 +28,9 @@
  *                              otherwise
  */
 open class GetCredentialOption(
-        val type: String,
-        val data: Bundle,
-        val requireSystemProvider: Boolean,
+    val type: String,
+    val data: Bundle,
+    val requireSystemProvider: Boolean,
 ) {
     companion object {
         @JvmStatic
@@ -38,14 +38,20 @@
             return try {
                 when (from.type) {
                     Credential.TYPE_PASSWORD_CREDENTIAL ->
-                        GetPasswordOption.createFrom(from.data)
+                        GetPasswordOption.createFrom(from.credentialRetrievalData)
                     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
-                        GetPublicKeyCredentialBaseOption.createFrom(from.data)
+                        GetPublicKeyCredentialBaseOption.createFrom(from.credentialRetrievalData)
                     else ->
-                        GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+                        GetCredentialOption(
+                            from.type, from.credentialRetrievalData, from.requireSystemProvider()
+                        )
                 }
             } catch (e: FrameworkClassParsingException) {
-                GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+                GetCredentialOption(
+                    from.type,
+                    from.credentialRetrievalData,
+                    from.requireSystemProvider()
+                )
             }
         }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
index 1e639fe..19c5c2d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
@@ -18,7 +18,6 @@
 
 import android.app.slice.Slice
 import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
 
 /**
  * UI representation for a credential entry used during the get credential flow.
@@ -26,28 +25,24 @@
  * TODO: move to jetpack.
  */
 class ActionUi(
-  val icon: Icon,
   val text: CharSequence,
   val subtext: CharSequence?,
 ) {
   companion object {
     fun fromSlice(slice: Slice): ActionUi {
-      var icon: Icon? = null
       var text: CharSequence? = null
       var subtext: CharSequence? = null
 
       val items = slice.items
       items.forEach {
-        if (it.hasHint(Entry.HINT_ACTION_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
+        if (it.hasHint(Entry.HINT_ACTION_TITLE)) {
           text = it.text
         } else if (it.hasHint(Entry.HINT_ACTION_SUBTEXT)) {
           subtext = it.text
         }
       }
       // TODO: fail NPE more elegantly.
-      return ActionUi(icon!!, text!!, subtext)
+      return ActionUi(text!!, subtext)
     }
   }
 }
diff --git a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
index 66c1c98..aa31493 100644
--- a/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
+++ b/packages/InputDevices/res/raw/keyboard_layout_english_us_intl.kcm
@@ -292,10 +292,10 @@
 
 key APOSTROPHE {
     label:                              '\''
-    base:                               '\''
-    shift:                              '"'
-    ralt:                               '\u0301'
-    shift+ralt:                         '\u0308'
+    base:                               '\u030D'
+    shift:                              '\u030E'
+    ralt:                               '\u00B4'
+    shift+ralt:                         '\u00A8'
 }
 
 ### ROW 4
diff --git a/packages/PrintSpooler/res/values-ca/strings.xml b/packages/PrintSpooler/res/values-ca/strings.xml
index a346cb2..4ee4323 100644
--- a/packages/PrintSpooler/res/values-ca/strings.xml
+++ b/packages/PrintSpooler/res/values-ca/strings.xml
@@ -56,6 +56,7 @@
     <string name="print_select_printer" msgid="7388760939873368698">"Selecciona una impressora"</string>
     <string name="print_forget_printer" msgid="5035287497291910766">"Oblida la impressora"</string>
     <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
+      <item quantity="many"><xliff:g id="COUNT_1">%1$s</xliff:g> printers found</item>
       <item quantity="other">S\'han trobat <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
       <item quantity="one">S\'ha trobat <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
     </plurals>
@@ -76,6 +77,7 @@
     <string name="disabled_services_title" msgid="7313253167968363211">"Serveis desactivats"</string>
     <string name="all_services_title" msgid="5578662754874906455">"Tots els serveis"</string>
     <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
+      <item quantity="many">Install to discover <xliff:g id="COUNT_1">%1$s</xliff:g> printers</item>
       <item quantity="other">Instal·la\'l per detectar <xliff:g id="COUNT_1">%1$s</xliff:g> impressores</item>
       <item quantity="one">Instal·la\'l per detectar <xliff:g id="COUNT_0">%1$s</xliff:g> impressora</item>
     </plurals>
diff --git a/packages/PrintSpooler/res/values-iw/strings.xml b/packages/PrintSpooler/res/values-iw/strings.xml
index 2ed8b7f..4c93df7 100644
--- a/packages/PrintSpooler/res/values-iw/strings.xml
+++ b/packages/PrintSpooler/res/values-iw/strings.xml
@@ -56,10 +56,9 @@
     <string name="print_select_printer" msgid="7388760939873368698">"בחירת מדפסת"</string>
     <string name="print_forget_printer" msgid="5035287497291910766">"לשכוח את המדפסת"</string>
     <plurals name="print_search_result_count_utterance" formatted="false" msgid="6997663738361080868">
+      <item quantity="one">נמצאו <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
       <item quantity="two">נמצאו <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
-      <item quantity="many">נמצאו <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
       <item quantity="other">נמצאו <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
-      <item quantity="one">נמצאה מדפסת <xliff:g id="COUNT_0">%1$s</xliff:g></item>
     </plurals>
     <string name="printer_extended_description_template" msgid="1366699227703381874">"<xliff:g id="PRINT_SERVICE_LABEL">%1$s</xliff:g> - <xliff:g id="PRINTER_DESCRIPTION">%2$s</xliff:g>"</string>
     <string name="printer_info_desc" msgid="7181988788991581654">"מידע נוסף על המדפסת הזו"</string>
@@ -78,10 +77,9 @@
     <string name="disabled_services_title" msgid="7313253167968363211">"שירותים מושבתים"</string>
     <string name="all_services_title" msgid="5578662754874906455">"כל השירותים"</string>
     <plurals name="print_services_recommendation_subtitle" formatted="false" msgid="5678487708807185138">
+      <item quantity="one">יש להתקין כדי לגלות <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
       <item quantity="two">יש להתקין כדי לגלות <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
-      <item quantity="many">יש להתקין כדי לגלות <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
       <item quantity="other">יש להתקין כדי לגלות <xliff:g id="COUNT_1">%1$s</xliff:g> מדפסות</item>
-      <item quantity="one">יש להתקין כדי לגלות מדפסת אחת (<xliff:g id="COUNT_0">%1$s</xliff:g>)‏</item>
     </plurals>
     <string name="printing_notification_title_template" msgid="295903957762447362">"בתהליך הדפסה של <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
     <string name="cancelling_notification_title_template" msgid="1821759594704703197">"המערכת מבטלת את <xliff:g id="PRINT_JOB_NAME">%1$s</xliff:g>"</string>
diff --git a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
index 37d6b42..d32d659 100644
--- a/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/gallery/AndroidManifest.xml
@@ -38,7 +38,6 @@
         <provider
             android:name="com.android.settingslib.spa.search.SpaSearchProvider"
             android:authorities="com.android.spa.gallery.search.provider"
-            android:enabled="true"
             android:exported="false">
         </provider>
 
@@ -67,7 +66,6 @@
         <provider
             android:name="com.android.settingslib.spa.debug.DebugProvider"
             android:authorities="com.android.spa.gallery.debug.provider"
-            android:enabled="true"
             android:exported="false">
         </provider>
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index db49909..e54e276 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -17,7 +17,7 @@
 package com.android.settingslib.spa.gallery
 
 import android.content.Context
-import com.android.settingslib.spa.framework.common.LocalLogger
+import com.android.settingslib.spa.debug.DebugLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.common.createSettingsPage
@@ -83,7 +83,7 @@
         )
     }
 
-    override val logger = LocalLogger()
+    override val logger = DebugLogger()
 
     override val browseActivityClass = GalleryMainActivity::class.java
     override val sliceBroadcastReceiverClass = SpaSliceBroadcastReceiver::class.java
diff --git a/packages/SettingsLib/Spa/screenshot/Android.bp b/packages/SettingsLib/Spa/screenshot/Android.bp
new file mode 100644
index 0000000..4e6b646
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/Android.bp
@@ -0,0 +1,41 @@
+//
+// 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 {
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "SpaScreenshotTests",
+    test_suites: ["device-tests"],
+
+    asset_dirs: ["assets"],
+
+    srcs: ["src/**/*.kt"],
+
+    certificate: "platform",
+
+    static_libs: [
+        "SpaLib",
+        "SpaLibTestUtils",
+        "androidx.compose.runtime_runtime",
+        "androidx.test.ext.junit",
+        "androidx.test.runner",
+        "mockito-target-minus-junit4",
+        "platform-screenshot-diff-core",
+    ],
+    kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
new file mode 100644
index 0000000..d59a154
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.settingslib.spa.screenshot">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".DebugActivity" android:exported="true" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="Screenshot tests for SpaLib"
+        android:targetPackage="com.android.settingslib.spa.screenshot">
+    </instrumentation>
+</manifest>
diff --git a/packages/SettingsLib/Spa/screenshot/AndroidTest.xml b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
new file mode 100644
index 0000000..e0c08e8
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/AndroidTest.xml
@@ -0,0 +1,36 @@
+<!--
+  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.
+  -->
+
+<configuration description="Runs screendiff tests.">
+    <option name="test-suite-tag" value="apct-instrumentation" />
+    <option name="test-suite-tag" value="apct" />
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="optimized-property-setting" value="true" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="SpaScreenshotTests.apk" />
+    </target_preparer>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys"
+            value="/data/user/0/com.android.settingslib.spa.screenshot/files/settings_screenshots" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.settingslib.spa.screenshot" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
new file mode 100644
index 0000000..6086e2d
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
new file mode 100644
index 0000000..aa6c5b7
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
new file mode 100644
index 0000000..cac990c
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
new file mode 100644
index 0000000..f6298c0
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/phone/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
new file mode 100644
index 0000000..9391eeb
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
new file mode 100644
index 0000000..94e2843
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/dark_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
new file mode 100644
index 0000000..b1d03c3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_landscape_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
new file mode 100644
index 0000000..95f19da
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/assets/tablet/light_portrait_preference.png
Binary files differ
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
new file mode 100644
index 0000000..814d4a1
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+    val bitmap =
+        Bitmap.createBitmap(
+            measuredWidth,
+            measuredHeight,
+            Bitmap.Config.ARGB_8888,
+        )
+    val canvas = Canvas(bitmap)
+    draw(canvas)
+    return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+    if (Build.CPU_ABI == "x86_64") {
+        // Different CPU architectures can sometimes end up rendering differently, so we can't do
+        // pixel-perfect matching on different architectures using the same golden. Given that our
+        // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+        // x86_64 architecture and use the Structural Similarity Index on others.
+        // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+        // do pixel perfect matching both at presubmit time and at development time with actual
+        // devices.
+        PixelPerfectMatcher()
+    } else {
+        MSSIMMatcher()
+    }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
new file mode 100644
index 0000000..d7f42b3
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/DefaultDeviceEmulationSpec.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.DisplaySpec
+
+/**
+ * The emulations specs for all 8 permutations of:
+ * - phone or tablet.
+ * - dark of light mode.
+ * - portrait or landscape.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletFull
+    get() = PhoneAndTabletFullSpec
+
+private val PhoneAndTabletFullSpec =
+    DeviceEmulationSpec.forDisplays(Displays.Phone, Displays.Tablet)
+
+/**
+ * The emulations specs of:
+ * - phone + light mode + portrait.
+ * - phone + light mode + landscape.
+ * - tablet + dark mode + portrait.
+ *
+ * This allows to test the most important permutations of a screen/layout with only 3
+ * configurations.
+ */
+val DeviceEmulationSpec.Companion.PhoneAndTabletMinimal
+    get() = PhoneAndTabletMinimalSpec
+
+private val PhoneAndTabletMinimalSpec =
+    DeviceEmulationSpec.forDisplays(Displays.Phone, isDarkTheme = false) +
+        DeviceEmulationSpec.forDisplays(Displays.Tablet, isDarkTheme = true, isLandscape = false)
+
+object Displays {
+    val Phone =
+        DisplaySpec(
+            "phone",
+            width = 1440,
+            height = 3120,
+            densityDpi = 560,
+        )
+
+    val Tablet =
+        DisplaySpec(
+            "tablet",
+            width = 2560,
+            height = 1600,
+            densityDpi = 320,
+        )
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
new file mode 100644
index 0000000..25bc098
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsGoldenImagePathManager.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all Settings screenshot tests. */
+class SettingsGoldenImagePathManager(
+    pathConfig: PathConfig,
+    assetsPathRelativeToBuildRoot: String
+) :
+    GoldenImagePathManager(
+        appContext = InstrumentationRegistry.getInstrumentation().context,
+        assetsPathRelativeToBuildRoot = assetsPathRelativeToBuildRoot,
+        deviceLocalPath =
+        InstrumentationRegistry.getInstrumentation()
+            .targetContext
+            .filesDir
+            .absolutePath
+            .toString() + "/settings_screenshots",
+        pathConfig = pathConfig,
+    ) {
+    override fun toString(): String {
+        // This string is appended to all actual/expected screenshots on the device, so make sure
+        // it is a static value.
+        return "SettingsGoldenImagePathManager"
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
new file mode 100644
index 0000000..7a7cf31
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/util/SettingsScreenshotTestRule.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.activity.ComponentActivity
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.ViewRootForTest
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onRoot
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for Settings screenshot diff tests. */
+class SettingsScreenshotTestRule(
+    emulationSpec: DeviceEmulationSpec,
+    assetsPathRelativeToBuildRoot: String
+) : TestRule {
+    private val colorsRule = MaterialYouColorsRule()
+    private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+    private val screenshotRule =
+        ScreenshotTestRule(
+            SettingsGoldenImagePathManager(
+                getEmulatedDevicePathConfig(emulationSpec),
+                assetsPathRelativeToBuildRoot
+            )
+        )
+    private val composeRule = createAndroidComposeRule<ComponentActivity>()
+    private val delegateRule =
+        RuleChain.outerRule(colorsRule)
+            .around(deviceEmulationRule)
+            .around(screenshotRule)
+            .around(composeRule)
+    private val matcher = UnitTestBitmapMatcher
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return delegateRule.apply(base, description)
+    }
+
+    /**
+     * Compare [content] with the golden image identified by [goldenIdentifier] in the context of
+     * [testSpec].
+     */
+    fun screenshotTest(
+        goldenIdentifier: String,
+        content: @Composable () -> Unit,
+    ) {
+        // Make sure that the activity draws full screen and fits the whole display.
+        val activity = composeRule.activity
+        activity.mainExecutor.execute { activity.window.setDecorFitsSystemWindows(false) }
+
+        // Set the content using the AndroidComposeRule to make sure that the Activity is set up
+        // correctly.
+        composeRule.setContent {
+            SettingsTheme {
+                Surface(
+                    color = MaterialTheme.colorScheme.background,
+                ) {
+                    content()
+                }
+            }
+        }
+        composeRule.waitForIdle()
+
+        val view = (composeRule.onRoot().fetchSemanticsNode().root as ViewRootForTest).view
+        screenshotRule.assertBitmapAgainstGolden(view.drawIntoBitmap(), goldenIdentifier, matcher)
+    }
+}
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
new file mode 100644
index 0000000..9631826
--- /dev/null
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/PreferenceScreenshotTest.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.settingslib.spa.screenshot
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Autorenew
+import androidx.compose.material.icons.outlined.DisabledByDefault
+import androidx.compose.runtime.Composable
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.SettingsIcon
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import platform.test.screenshot.DeviceEmulationSpec
+
+/** A screenshot test for ExampleFeature. */
+@RunWith(Parameterized::class)
+class PreferenceScreenshotTest(emulationSpec: DeviceEmulationSpec) {
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getTestSpecs() = DeviceEmulationSpec.PhoneAndTabletFull
+        private const val TITLE = "Title"
+        private const val SUMMARY = "Summary"
+        private const val LONG_SUMMARY =
+            "Long long long long long long long long long long long long long long long summary"
+    }
+
+    @get:Rule
+    val screenshotRule =
+        SettingsScreenshotTestRule(
+            emulationSpec,
+            "frameworks/base/packages/SettingsLib/Spa/screenshot/assets"
+        )
+
+    @Test
+    fun testPreference() {
+        screenshotRule.screenshotTest("preference") {
+            RegularScaffold(title = "Preference") {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                })
+
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val summary = SUMMARY.toState()
+                })
+
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val summary = LONG_SUMMARY.toState()
+                })
+
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val summary = SUMMARY.toState()
+                    override val enabled = false.toState()
+                    override val icon = @Composable {
+                        SettingsIcon(imageVector = Icons.Outlined.DisabledByDefault)
+                    }
+                })
+
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val summary = SUMMARY.toState()
+                    override val icon = @Composable {
+                        SettingsIcon(imageVector = Icons.Outlined.Autorenew)
+                    }
+                })
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index b627a70..1c5a1ce 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -33,4 +33,3 @@
 include ':spa'
 include ':gallery'
 include ':testutils'
-include ':tests'
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index b1d8d0d..6ddedd4 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -27,6 +27,8 @@
     defaultConfig {
         minSdk MIN_SDK
         targetSdk TARGET_SDK
+
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
     }
 
     sourceSets {
@@ -37,6 +39,13 @@
             res.srcDirs = ["res"]
             manifest.srcFile "AndroidManifest.xml"
         }
+        androidTest {
+            kotlin {
+                srcDir "../tests/src"
+            }
+            res.srcDirs = ["../tests/res"]
+            manifest.srcFile "../tests/AndroidManifest.xml"
+        }
     }
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
@@ -52,6 +61,11 @@
     composeOptions {
         kotlinCompilerExtensionVersion jetpack_compose_compiler_version
     }
+    buildTypes {
+        debug {
+            testCoverageEnabled = true
+        }
+    }
 }
 
 dependencies {
@@ -72,4 +86,33 @@
     api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
     implementation "com.airbnb.android:lottie-compose:5.2.0"
+
+    androidTestImplementation project(":testutils")
+    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
+}
+
+task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
+    group = "Reporting"
+    description = "Generate Jacoco coverage reports after running tests."
+
+    sourceDirectories.from = files("src")
+    classDirectories.from = fileTree(
+            dir: "$buildDir/tmp/kotlin-classes/debug",
+            excludes: [
+                    "com/android/settingslib/spa/debug/**",
+
+                    // Excludes inline functions, which is not covered in Jacoco reports.
+                    "com/android/settingslib/spa/framework/util/Collections*",
+                    "com/android/settingslib/spa/framework/util/Flows*",
+
+                    // Excludes files forked from AndroidX.
+                    "com/android/settingslib/spa/widget/scaffold/CustomizedAppBar*",
+                    "com/android/settingslib/spa/widget/scaffold/TopAppBarColors*",
+
+                    // Excludes files forked from Accompanist.
+                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
+                    "com/android/settingslib/spa/framework/compose/Pager*",
+            ],
+    )
+    executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
index 5873635..6ecf9c3 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugFormat.kt
@@ -40,7 +40,7 @@
 }
 
 fun SettingsPage.debugArguments(): String {
-    val normArguments = parameter.normalize(arguments)
+    val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
     if (normArguments == null || normArguments.isEmpty) return "[No arguments]"
     return normArguments.toString().removeRange(0, 6)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugLogger.kt
new file mode 100644
index 0000000..7d48336
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugLogger.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.settingslib.spa.debug
+
+import android.os.Bundle
+import android.util.Log
+import com.android.settingslib.spa.framework.common.LogCategory
+import com.android.settingslib.spa.framework.common.LogEvent
+import com.android.settingslib.spa.framework.common.SpaLogger
+
+class DebugLogger : SpaLogger {
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        Log.d("SpaMsg-$category", "[$tag] $msg")
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
+        val extraMsg = extraData.toString().removeRange(0, 6)
+        Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
index 59ec985..838c0cf 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/DebugProvider.kt
@@ -27,11 +27,7 @@
 import android.database.MatrixCursor
 import android.net.Uri
 import android.util.Log
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
 import com.android.settingslib.spa.framework.util.KEY_DESTINATION
 import com.android.settingslib.spa.framework.util.KEY_HIGHLIGHT_ENTRY
 import com.android.settingslib.spa.framework.util.KEY_SESSION_SOURCE_NAME
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
similarity index 60%
rename from packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
rename to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
index 61b46be..bb9a134 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/ProviderColumn.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/debug/ProviderColumn.kt
@@ -14,10 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.spa.framework.common
+package com.android.settingslib.spa.debug
 
 import android.content.UriMatcher
-import androidx.annotation.VisibleForTesting
 
 /**
  * Enum to define all column names in provider.
@@ -39,12 +38,6 @@
     ENTRY_INTENT_URI("entryIntent"),
     ENTRY_HIERARCHY_PATH("entryPath"),
     ENTRY_START_ADB("entryStartAdb"),
-
-    // Columns related to search
-    SEARCH_TITLE("searchTitle"),
-    SEARCH_KEYWORD("searchKw"),
-    SEARCH_PATH("searchPath"),
-    SEARCH_STATUS_DISABLED("searchDisabled"),
 }
 
 /**
@@ -89,54 +82,16 @@
             ColumnEnum.ENTRY_HIERARCHY_PATH,
         )
     ),
-
-    SEARCH_STATIC_DATA_QUERY(
-        "search_static", 301,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.ENTRY_INTENT_URI,
-            ColumnEnum.SEARCH_TITLE,
-            ColumnEnum.SEARCH_KEYWORD,
-            ColumnEnum.SEARCH_PATH,
-        )
-    ),
-    SEARCH_DYNAMIC_DATA_QUERY(
-        "search_dynamic", 302,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.ENTRY_INTENT_URI,
-            ColumnEnum.SEARCH_TITLE,
-            ColumnEnum.SEARCH_KEYWORD,
-            ColumnEnum.SEARCH_PATH,
-        )
-    ),
-    SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
-        "search_immutable_status", 303,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.SEARCH_STATUS_DISABLED,
-        )
-    ),
-    SEARCH_MUTABLE_STATUS_DATA_QUERY(
-        "search_mutable_status", 304,
-        listOf(
-            ColumnEnum.ENTRY_ID,
-            ColumnEnum.SEARCH_STATUS_DISABLED,
-        )
-    ),
 }
 
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getColumns(): Array<String> {
+internal fun QueryEnum.getColumns(): Array<String> {
     return columnNames.map { it.id }.toTypedArray()
 }
 
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.getIndex(name: ColumnEnum): Int {
+internal fun QueryEnum.getIndex(name: ColumnEnum): Int {
     return columnNames.indexOf(name)
 }
 
-@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
+internal fun QueryEnum.addUri(uriMatcher: UriMatcher, authority: String) {
     uriMatcher.addURI(authority, queryPath, queryMatchCode)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
index 702c075..bb968eb 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntry.kt
@@ -35,6 +35,8 @@
         get() = null
     val isHighlighted: Boolean
         get() = false
+    val arguments: Bundle?
+        get() = null
 }
 
 val LocalEntryDataProvider =
@@ -121,11 +123,11 @@
     }
 
     private fun fullArgument(runtimeArguments: Bundle? = null): Bundle {
-        val arguments = Bundle()
-        if (owner.arguments != null) arguments.putAll(owner.arguments)
-        // Put runtime args later, which can override page args.
-        if (runtimeArguments != null) arguments.putAll(runtimeArguments)
-        return arguments
+        return Bundle().apply {
+            if (owner.arguments != null) putAll(owner.arguments)
+            // Put runtime args later, which can override page args.
+            if (runtimeArguments != null) putAll(runtimeArguments)
+        }
     }
 
     fun getStatusData(runtimeArguments: Bundle? = null): EntryStatusData? {
@@ -142,19 +144,21 @@
 
     @Composable
     fun UiLayout(runtimeArguments: Bundle? = null) {
-        CompositionLocalProvider(provideLocalEntryData()) {
-            uiLayoutImpl(fullArgument(runtimeArguments))
+        val arguments = remember { fullArgument(runtimeArguments) }
+        CompositionLocalProvider(provideLocalEntryData(arguments)) {
+            uiLayoutImpl(arguments)
         }
     }
 
     @Composable
-    fun provideLocalEntryData(): ProvidedValue<EntryData> {
+    private fun provideLocalEntryData(arguments: Bundle): ProvidedValue<EntryData> {
         val controller = LocalNavController.current
         return LocalEntryDataProvider provides remember {
             object : EntryData {
                 override val pageId = containerPage().id
                 override val entryId = id
                 override val isHighlighted = controller.highlightEntryId == id
+                override val arguments = arguments
             }
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 7a39b73..2bfa2a4 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -69,7 +69,7 @@
             parameter: List<NamedNavArgument> = emptyList(),
             arguments: Bundle? = null
         ): String {
-            val normArguments = parameter.normalize(arguments)
+            val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
             return "$name:${normArguments?.toString()}".toHashId()
         }
     }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
index 6ecb7fa..78df0f2 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaLogger.kt
@@ -17,7 +17,6 @@
 package com.android.settingslib.spa.framework.common
 
 import android.os.Bundle
-import android.util.Log
 
 // Defines the category of the log, for quick filter
 enum class LogCategory {
@@ -62,14 +61,3 @@
     ) {
     }
 }
-
-class LocalLogger : SpaLogger {
-    override fun message(tag: String, msg: String, category: LogCategory) {
-        Log.d("SpaMsg-$category", "[$tag] $msg")
-    }
-
-    override fun event(id: String, event: LogEvent, category: LogCategory, extraData: Bundle) {
-        val extraMsg = extraData.toString().removeRange(0, 6)
-        Log.d("SpaEvent-$category", "[$id] $event $extraMsg")
-    }
-}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
index 1c88187..8ff4368 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/EntryLogger.kt
@@ -28,9 +28,12 @@
 @Composable
 fun logEntryEvent(): (event: LogEvent, extraData: Bundle) -> Unit {
     val entryId = LocalEntryDataProvider.current.entryId ?: return { _, _ -> }
+    val arguments = LocalEntryDataProvider.current.arguments
     return { event, extraData ->
         SpaEnvironmentFactory.instance.logger.event(
-            entryId, event, category = LogCategory.VIEW, extraData = extraData
+            entryId, event, category = LogCategory.VIEW, extraData = extraData.apply {
+                if (arguments != null) putAll(arguments)
+            }
         )
     }
 }
@@ -40,7 +43,7 @@
     if (onClick == null) return null
     val logEvent = logEntryEvent()
     return {
-        logEvent(LogEvent.ENTRY_CLICK, Bundle.EMPTY)
+        logEvent(LogEvent.ENTRY_CLICK, bundleOf())
         onClick()
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
new file mode 100644
index 0000000..2adfcca
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/MessageFormats.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.Context
+import android.content.res.Resources
+import android.icu.text.MessageFormat
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.StringRes
+import java.util.Locale
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Context.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+    resources.formatString(resId, *arguments)
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Resources.formatString(@StringRes resId: Int, vararg arguments: Pair<String, Any>): String =
+    MessageFormat(getString(resId), Locale.getDefault(Locale.Category.FORMAT))
+        .format(mapOf(*arguments))
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
index a881254..271443e 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/PageLogger.kt
@@ -48,7 +48,10 @@
                     extraData = bundleOf(
                         LOG_DATA_DISPLAY_NAME to page.displayName,
                         LOG_DATA_SESSION_NAME to navController.sessionSourceName,
-                    )
+                    ).apply {
+                        val normArguments = parameter.normalize(arguments)
+                        if (normArguments != null) putAll(normArguments)
+                    }
                 )
             }
             if (event == Lifecycle.Event.ON_START) {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
index f10d3b0..be303f0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/Parameter.kt
@@ -47,12 +47,15 @@
     return argsArray.joinToString("") { arg -> "/$arg" }
 }
 
-fun List<NamedNavArgument>.normalize(arguments: Bundle? = null): Bundle? {
+fun List<NamedNavArgument>.normalize(
+    arguments: Bundle? = null,
+    eraseRuntimeValues: Boolean = false
+): Bundle? {
     if (this.isEmpty()) return null
     val normArgs = Bundle()
     for (navArg in this) {
         // Erase value of runtime parameters.
-        if (navArg.isRuntimeParam()) {
+        if (navArg.isRuntimeParam() && eraseRuntimeValues) {
             normArgs.putString(navArg.name, null)
             continue
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
new file mode 100644
index 0000000..2301f04
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchContract.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.settingslib.spa.search
+
+/**
+ * Intent action used to identify SpaSearchProvider instances. This is used in the {@code
+ * <intent-filter>} of a {@code <provider>}.
+ */
+const val PROVIDER_INTERFACE = "android.content.action.SPA_SEARCH_PROVIDER"
+
+/** ContentProvider path for search static data */
+const val SEARCH_STATIC_DATA = "search_static_data"
+
+/** ContentProvider path for search dynamic data */
+const val SEARCH_DYNAMIC_DATA = "search_dynamic_data"
+
+/** ContentProvider path for search immutable status */
+const val SEARCH_IMMUTABLE_STATUS = "search_immutable_status"
+
+/** ContentProvider path for search mutable status */
+const val SEARCH_MUTABLE_STATUS = "search_mutable_status"
+
+/** Enum to define all column names in provider. */
+enum class ColumnEnum(val id: String) {
+    ENTRY_ID("entryId"),
+    SEARCH_TITLE("searchTitle"),
+    SEARCH_KEYWORD("searchKw"),
+    SEARCH_PATH("searchPath"),
+    INTENT_TARGET_PACKAGE("intentTargetPackage"),
+    INTENT_TARGET_CLASS("intentTargetClass"),
+    INTENT_EXTRAS("intentExtras"),
+    SLICE_URI("sliceUri"),
+    LEGACY_KEY("legacyKey"),
+    ENTRY_DISABLED("entryDisabled"),
+}
+
+/** Enum to define all queries supported in the provider. */
+@SuppressWarnings("Immutable")
+enum class QueryEnum(
+    val queryPath: String,
+    val columnNames: List<ColumnEnum>
+) {
+    SEARCH_STATIC_DATA_QUERY(
+        SEARCH_STATIC_DATA,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.INTENT_EXTRAS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY
+        )
+    ),
+    SEARCH_DYNAMIC_DATA_QUERY(
+        SEARCH_DYNAMIC_DATA,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.SEARCH_TITLE,
+            ColumnEnum.SEARCH_KEYWORD,
+            ColumnEnum.SEARCH_PATH,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            ColumnEnum.SLICE_URI,
+            ColumnEnum.LEGACY_KEY
+        )
+    ),
+    SEARCH_IMMUTABLE_STATUS_DATA_QUERY(
+        SEARCH_IMMUTABLE_STATUS,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
+    SEARCH_MUTABLE_STATUS_DATA_QUERY(
+        SEARCH_MUTABLE_STATUS,
+        listOf(
+            ColumnEnum.ENTRY_ID,
+            ColumnEnum.ENTRY_DISABLED,
+        )
+    ),
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
index 02aed1c..21bc75a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/search/SpaSearchProvider.kt
@@ -19,22 +19,22 @@
 import android.content.ContentProvider
 import android.content.ContentValues
 import android.content.Context
-import android.content.Intent
 import android.content.UriMatcher
 import android.content.pm.ProviderInfo
 import android.database.Cursor
 import android.database.MatrixCursor
 import android.net.Uri
+import android.os.Parcel
+import android.os.Parcelable
 import android.util.Log
 import androidx.annotation.VisibleForTesting
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
+import com.android.settingslib.spa.framework.common.EntryStatusData
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
-import com.android.settingslib.spa.framework.common.addUri
-import com.android.settingslib.spa.framework.common.getColumns
 import com.android.settingslib.spa.framework.util.SESSION_SEARCH
 import com.android.settingslib.spa.framework.util.createIntent
+import com.android.settingslib.spa.slice.fromEntry
+
 
 private const val TAG = "SpaSearchProvider"
 
@@ -42,18 +42,25 @@
  * The content provider to return entry related data, which can be used for search and hierarchy.
  * One can query the provider result by:
  *   $ adb shell content query --uri content://<AuthorityPath>/<QueryPath>
- * For gallery, AuthorityPath = com.android.spa.gallery.provider
- * For Settings, AuthorityPath = com.android.settings.spa.provider
+ * For gallery, AuthorityPath = com.android.spa.gallery.search.provider
+ * For Settings, AuthorityPath = com.android.settings.spa.search.provider"
  * Some examples:
- *   $ adb shell content query --uri content://<AuthorityPath>/search_static
- *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic
- *   $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_static_data
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_dynamic_data
  *   $ adb shell content query --uri content://<AuthorityPath>/search_immutable_status
+ *   $ adb shell content query --uri content://<AuthorityPath>/search_mutable_status
  */
 class SpaSearchProvider : ContentProvider() {
     private val spaEnvironment get() = SpaEnvironmentFactory.instance
     private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)
 
+    private val queryMatchCode = mapOf(
+        SEARCH_STATIC_DATA to 301,
+        SEARCH_DYNAMIC_DATA to 302,
+        SEARCH_MUTABLE_STATUS to 303,
+        SEARCH_IMMUTABLE_STATUS to 304
+    )
+
     override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
         TODO("Implement this to handle requests to delete one or more rows")
     }
@@ -85,10 +92,9 @@
 
     override fun attachInfo(context: Context?, info: ProviderInfo?) {
         if (info != null) {
-            QueryEnum.SEARCH_STATIC_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.addUri(uriMatcher, info.authority)
+            for (entry in queryMatchCode) {
+                uriMatcher.addURI(info.authority, entry.key, entry.value)
+            }
         }
         super.attachInfo(context, info)
     }
@@ -102,11 +108,11 @@
     ): Cursor? {
         return try {
             when (uriMatcher.match(uri)) {
-                QueryEnum.SEARCH_STATIC_DATA_QUERY.queryMatchCode -> querySearchStaticData()
-                QueryEnum.SEARCH_DYNAMIC_DATA_QUERY.queryMatchCode -> querySearchDynamicData()
-                QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+                queryMatchCode[SEARCH_STATIC_DATA] -> querySearchStaticData()
+                queryMatchCode[SEARCH_DYNAMIC_DATA] -> querySearchDynamicData()
+                queryMatchCode[SEARCH_MUTABLE_STATUS] ->
                     querySearchMutableStatusData()
-                QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY.queryMatchCode ->
+                queryMatchCode[SEARCH_IMMUTABLE_STATUS] ->
                     querySearchImmutableStatusData()
                 else -> throw UnsupportedOperationException("Unknown Uri $uri")
             }
@@ -167,23 +173,45 @@
 
         // Fetch search data. We can add runtime arguments later if necessary
         val searchData = entry.getSearchData() ?: return
-        val intent = entry.createIntent(SESSION_SEARCH) ?: Intent()
-        cursor.newRow()
-            .add(ColumnEnum.ENTRY_ID.id, entry.id)
-            .add(ColumnEnum.ENTRY_INTENT_URI.id, intent.toUri(Intent.URI_INTENT_SCHEME))
+        val intent = entry.createIntent(SESSION_SEARCH)
+        val row = cursor.newRow().add(ColumnEnum.ENTRY_ID.id, entry.id)
             .add(ColumnEnum.SEARCH_TITLE.id, searchData.title)
             .add(ColumnEnum.SEARCH_KEYWORD.id, searchData.keyword)
             .add(
                 ColumnEnum.SEARCH_PATH.id,
                 entryRepository.getEntryPathWithTitle(entry.id, searchData.title)
             )
+        intent?.let {
+            row.add(ColumnEnum.INTENT_TARGET_PACKAGE.id, spaEnvironment.appContext.packageName)
+                .add(ColumnEnum.INTENT_TARGET_CLASS.id, spaEnvironment.browseActivityClass?.name)
+                .add(ColumnEnum.INTENT_EXTRAS.id, marshall(intent.extras))
+        }
+        if (entry.hasSliceSupport)
+            row.add(
+                ColumnEnum.SLICE_URI.id, Uri.Builder()
+                    .fromEntry(entry, spaEnvironment.sliceProviderAuthorities)
+            )
+        // TODO: support legacy key
     }
 
     private fun fetchStatusData(entry: SettingsEntry, cursor: MatrixCursor) {
         // Fetch status data. We can add runtime arguments later if necessary
-        val statusData = entry.getStatusData() ?: return
+        val statusData = entry.getStatusData() ?: EntryStatusData()
         cursor.newRow()
             .add(ColumnEnum.ENTRY_ID.id, entry.id)
-            .add(ColumnEnum.SEARCH_STATUS_DISABLED.id, statusData.isDisabled)
+            .add(ColumnEnum.ENTRY_DISABLED.id, statusData.isDisabled)
+    }
+
+    private fun QueryEnum.getColumns(): Array<String> {
+        return columnNames.map { it.id }.toTypedArray()
+    }
+
+    private fun marshall(parcelable: Parcelable?): ByteArray? {
+        if (parcelable == null) return null
+        val parcel = Parcel.obtain()
+        parcelable.writeToParcel(parcel, 0)
+        val bytes = parcel.marshall()
+        parcel.recycle()
+        return bytes
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
index b65b91f..e4a7386 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/provider/Demo.kt
@@ -19,6 +19,7 @@
 import android.app.PendingIntent
 import android.content.Context
 import android.net.Uri
+import androidx.core.R
 import androidx.core.graphics.drawable.IconCompat
 import androidx.slice.Slice
 import androidx.slice.SliceManager
@@ -52,10 +53,7 @@
 private fun createSliceAction(context: Context, intent: PendingIntent): SliceAction {
     return SliceAction.create(
         intent,
-        IconCompat.createWithResource(
-            context,
-            com.google.android.material.R.drawable.navigation_empty_icon
-        ),
+        IconCompat.createWithResource(context, R.drawable.notification_action_background),
         ListBuilder.ICON_IMAGE,
         "Enter app"
     )
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 7db1ca1..ae88ed7 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
@@ -132,7 +132,10 @@
                     .asPaddingValues()
                     .horizontalValues()
             )
-            .padding(horizontal = SettingsDimension.itemPaddingAround),
+            .padding(
+                start = SettingsDimension.itemPaddingAround,
+                end = SettingsDimension.itemPaddingEnd,
+            ),
         overflow = TextOverflow.Ellipsis,
         maxLines = maxLines,
     )
diff --git a/packages/SettingsLib/Spa/tests/AndroidManifest.xml b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
index e2db594..1fda4e0 100644
--- a/packages/SettingsLib/Spa/tests/AndroidManifest.xml
+++ b/packages/SettingsLib/Spa/tests/AndroidManifest.xml
@@ -15,7 +15,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.settingslib.spa.tests">
+    package="com.android.settingslib.spa.test">
 
     <uses-sdk android:minSdkVersion="21"/>
 
@@ -26,6 +26,6 @@
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:label="Tests for SpaLib"
-        android:targetPackage="com.android.settingslib.spa.tests">
+        android:targetPackage="com.android.settingslib.spa.test">
     </instrumentation>
 </manifest>
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
deleted file mode 100644
index 45f9b23..0000000
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-plugins {
-    id 'com.android.library'
-    id 'kotlin-android'
-}
-
-android {
-    namespace 'com.android.settingslib.spa.tests'
-    compileSdk TARGET_SDK
-    buildToolsVersion = BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdk MIN_SDK
-        targetSdk TARGET_SDK
-
-        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
-    }
-
-    sourceSets {
-        main {
-            res.srcDirs = ["res"]
-        }
-        androidTest {
-            kotlin {
-                srcDir "src"
-            }
-            manifest.srcFile "AndroidManifest.xml"
-        }
-    }
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
-    kotlinOptions {
-        jvmTarget = '1.8'
-        freeCompilerArgs = ["-Xjvm-default=all"]
-    }
-    buildFeatures {
-        compose true
-    }
-    composeOptions {
-        kotlinCompilerExtensionVersion jetpack_compose_compiler_version
-    }
-    buildTypes {
-        debug {
-            testCoverageEnabled = true
-        }
-    }
-}
-
-dependencies {
-    androidTestImplementation project(":spa")
-    androidTestImplementation project(":testutils")
-    androidTestImplementation "com.linkedin.dexmaker:dexmaker-mockito:2.28.1"
-}
-
-task coverageReport(type: JacocoReport, dependsOn: "connectedDebugAndroidTest") {
-    group = "Reporting"
-    description = "Generate Jacoco coverage reports after running tests."
-
-    sourceDirectories.from = files("../spa/src")
-    classDirectories.from = fileTree(
-            dir: "../spa/build/tmp/kotlin-classes/debug",
-            excludes: [
-                    "com/android/settingslib/spa/debug/**",
-
-                    // Excludes files forked from Accompanist.
-                    "com/android/settingslib/spa/framework/compose/DrawablePainter*",
-                    "com/android/settingslib/spa/framework/compose/Pager*",
-            ],
-    )
-    executionData.from = fileTree(dir: "$buildDir/outputs/code_coverage/debugAndroidTest/connected")
-}
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
new file mode 100644
index 0000000..1ca425c
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources>
+    <string name="test_quantity_strings">{count, plural,
+        =1    {There is one song found.}
+        other {There are # songs found.}
+    }</string>
+
+    <string name="test_quantity_strings_with_param">{count, plural,
+        =1    {There is one song found in {place}.}
+        other {There are # songs found in {place}.}
+    }</string>
+</resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
index f98963c..5bf7c28 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -16,10 +16,13 @@
 
 package com.android.settingslib.spa.framework.common
 
+import android.net.Uri
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.core.os.bundleOf
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.slice.appendSpaParams
+import com.android.settingslib.spa.slice.getEntryId
 import com.android.settingslib.spa.tests.testutils.getUniqueEntryId
 import com.android.settingslib.spa.tests.testutils.getUniquePageId
 import com.google.common.truth.Truth.assertThat
@@ -169,4 +172,25 @@
         assertThat(statusData?.isDisabled).isTrue()
         assertThat(statusData?.isSwitchOff).isTrue()
     }
+
+    @Test
+    fun testSetSliceDataFn() {
+        val owner = SettingsPage.create("mySpp")
+        val entryId = getUniqueEntryId("myEntry", owner)
+        val emptySliceData = EntrySliceData()
+
+        val entryBuilder = SettingsEntryBuilder.create(owner, "myEntry")
+            .setSliceDataFn { uri, _ ->
+                return@setSliceDataFn if (uri.getEntryId() == entryId) emptySliceData else null
+            }
+        val entry = entryBuilder.build()
+        assertThat(entry.id).isEqualTo(entryId)
+        assertThat(entry.hasSliceSupport).isTrue()
+        assertThat(entry.getSliceData(Uri.EMPTY)).isNull()
+        assertThat(
+            entry.getSliceData(
+                Uri.Builder().scheme("content").appendSpaParams(entryId = entryId).build()
+            )
+        ).isEqualTo(emptySliceData)
+    }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
new file mode 100644
index 0000000..62f4707
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
@@ -0,0 +1,47 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+class CollectionsTest {
+    @Test
+    fun testAsyncForEach() = runTest {
+        var sum = 0
+        listOf(1, 2, 3).asyncForEach { sum += it }
+        Truth.assertThat(sum).isEqualTo(6)
+    }
+
+    @Test
+    fun testAsyncFilter() = runTest {
+        val res = listOf(1, 2, 3).asyncFilter { it >= 2 }
+        Truth.assertThat(res).containsExactly(2, 3).inOrder()
+    }
+
+    @Test
+    fun testAsyncMap() = runTest {
+        val res = listOf(1, 2, 3).asyncMap { it + 1 }
+        Truth.assertThat(res).containsExactly(2, 3, 4).inOrder()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
new file mode 100644
index 0000000..2017ad1
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/MessageFormatsTest.kt
@@ -0,0 +1,66 @@
+/*
+ * 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.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MessageFormatsTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun formatString_one() {
+        val message = context.formatString(R.string.test_quantity_strings, "count" to 1)
+
+        assertThat(message).isEqualTo("There is one song found.")
+    }
+
+    @Test
+    fun formatString_other() {
+        val message = context.formatString(R.string.test_quantity_strings, "count" to 2)
+
+        assertThat(message).isEqualTo("There are 2 songs found.")
+    }
+
+    @Test
+    fun formatString_withParam_one() {
+        val message = context.formatString(
+            R.string.test_quantity_strings_with_param,
+            "count" to 1,
+            "place" to "phone",
+        )
+
+        assertThat(message).isEqualTo("There is one song found in phone.")
+    }
+
+    @Test
+    fun formatString_withParam_other() {
+        val message = context.formatString(
+            R.string.test_quantity_strings_with_param,
+            "count" to 2,
+            "place" to "phone",
+        )
+
+        assertThat(message).isEqualTo("There are 2 songs found in phone.")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
index 48ebd8d..0aa846c 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -88,7 +88,7 @@
         val emptyParam = navArguments.normalize()
         assertThat(emptyParam).isNotNull()
         assertThat(emptyParam.toString()).isEqualTo(
-            "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
+            "Bundle[{unset_rt_param=null, unset_string_param=null, unset_int_param=null}]"
         )
 
         val setPartialParam = navArguments.normalize(
@@ -99,7 +99,7 @@
         )
         assertThat(setPartialParam).isNotNull()
         assertThat(setPartialParam.toString()).isEqualTo(
-            "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
+            "Bundle[{rt_param=rtStr, string_param=myStr, unset_int_param=null}]"
         )
 
         val setAllParam = navArguments.normalize(
@@ -107,7 +107,8 @@
                 "string_param" to "myStr",
                 "int_param" to 10,
                 "rt_param" to "rtStr",
-            )
+            ),
+            eraseRuntimeValues = true
         )
         assertThat(setAllParam).isNotNull()
         assertThat(setAllParam.toString()).isEqualTo(
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
index cdb0f3a..831aded 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/search/SpaSearchProviderTest.kt
@@ -18,15 +18,14 @@
 
 import android.content.Context
 import android.database.Cursor
+import android.os.Bundle
+import android.os.Parcel
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.common.ColumnEnum
-import com.android.settingslib.spa.framework.common.QueryEnum
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
-import com.android.settingslib.spa.framework.common.getIndex
 import com.android.settingslib.spa.tests.testutils.SpaEnvironmentForTest
 import com.android.settingslib.spa.tests.testutils.SppForSearch
 import com.google.common.truth.Truth
@@ -39,51 +38,145 @@
     private val spaEnvironment =
         SpaEnvironmentForTest(context, listOf(SppForSearch.createSettingsPage()))
     private val searchProvider = SpaSearchProvider()
+    private val pageOwner = spaEnvironment.createPage("SppForSearch")
 
     @Test
     fun testQuerySearchStatusData() {
         SpaEnvironmentFactory.reset(spaEnvironment)
-        val pageOwner = spaEnvironment.createPage("SppForSearch")
 
         val immutableStatus = searchProvider.querySearchImmutableStatusData()
-        Truth.assertThat(immutableStatus.count).isEqualTo(1)
+        Truth.assertThat(immutableStatus.count).isEqualTo(2)
         immutableStatus.moveToFirst()
         immutableStatus.checkValue(
             QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
             ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithNoStatus")
+        )
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_DISABLED,
+            false.toString()
+        )
+
+        immutableStatus.moveToNext()
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
             pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
         )
+        immutableStatus.checkValue(
+            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
 
         val mutableStatus = searchProvider.querySearchMutableStatusData()
         Truth.assertThat(mutableStatus.count).isEqualTo(2)
         mutableStatus.moveToFirst()
         mutableStatus.checkValue(
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
             ColumnEnum.ENTRY_ID,
             pageOwner.getEntryId("SearchStaticWithMutableStatus")
         )
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, false.toString()
+        )
 
         mutableStatus.moveToNext()
         mutableStatus.checkValue(
-            QueryEnum.SEARCH_IMMUTABLE_STATUS_DATA_QUERY,
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY,
             ColumnEnum.ENTRY_ID,
             pageOwner.getEntryId("SearchDynamicWithMutableStatus")
         )
+        mutableStatus.checkValue(
+            QueryEnum.SEARCH_MUTABLE_STATUS_DATA_QUERY, ColumnEnum.ENTRY_DISABLED, true.toString()
+        )
     }
 
     @Test
     fun testQuerySearchIndexData() {
         SpaEnvironmentFactory.reset(spaEnvironment)
+
         val staticData = searchProvider.querySearchStaticData()
         Truth.assertThat(staticData.count).isEqualTo(2)
+        staticData.moveToFirst()
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithNoStatus")
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_TITLE, "SearchStaticWithNoStatus"
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.SEARCH_KEYWORD, listOf("").toString()
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.SEARCH_PATH,
+            listOf("SearchStaticWithNoStatus", "SppForSearch").toString()
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.INTENT_TARGET_PACKAGE,
+            spaEnvironment.appContext.packageName
+        )
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.INTENT_TARGET_CLASS,
+            "com.android.settingslib.spa.tests.testutils.BlankActivity"
+        )
+
+        // Check extras in intent
+        val bundle =
+            staticData.getExtras(QueryEnum.SEARCH_STATIC_DATA_QUERY, ColumnEnum.INTENT_EXTRAS)
+        Truth.assertThat(bundle).isNotNull()
+        Truth.assertThat(bundle!!.size()).isEqualTo(3)
+        Truth.assertThat(bundle.getString("spaActivityDestination")).isEqualTo("SppForSearch")
+        Truth.assertThat(bundle.getString("highlightEntry"))
+            .isEqualTo(pageOwner.getEntryId("SearchStaticWithNoStatus"))
+        Truth.assertThat(bundle.getString("sessionSource")).isEqualTo("search")
+
+        staticData.moveToNext()
+        staticData.checkValue(
+            QueryEnum.SEARCH_STATIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchStaticWithMutableStatus")
+        )
 
         val dynamicData = searchProvider.querySearchDynamicData()
         Truth.assertThat(dynamicData.count).isEqualTo(2)
+        dynamicData.moveToFirst()
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithMutableStatus")
+        )
+
+        dynamicData.moveToNext()
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.ENTRY_ID,
+            pageOwner.getEntryId("SearchDynamicWithImmutableStatus")
+        )
+        dynamicData.checkValue(
+            QueryEnum.SEARCH_DYNAMIC_DATA_QUERY,
+            ColumnEnum.SEARCH_KEYWORD,
+            listOf("kw1", "kw2").toString()
+        )
     }
 }
 
 private fun Cursor.checkValue(query: QueryEnum, column: ColumnEnum, value: String) {
-    Truth.assertThat(getString(query.getIndex(column))).isEqualTo(value)
+    Truth.assertThat(getString(query.columnNames.indexOf(column))).isEqualTo(value)
+}
+
+private fun Cursor.getExtras(query: QueryEnum, column: ColumnEnum): Bundle? {
+    val extrasByte = getBlob(query.columnNames.indexOf(column)) ?: return null
+    val parcel = Parcel.obtain()
+    parcel.unmarshall(extrasByte, 0, extrasByte.size)
+    parcel.setDataPosition(0)
+    val bundle = Bundle()
+    bundle.readFromParcel(parcel)
+    return bundle
 }
 
 private fun SettingsPage.getEntryId(name: String): String {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
index 7e51fea..ce9b791 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/tests/testutils/UniqueIdHelper.kt
@@ -27,7 +27,7 @@
     parameter: List<NamedNavArgument> = emptyList(),
     arguments: Bundle? = null
 ): String {
-    val normArguments = parameter.normalize(arguments)
+    val normArguments = parameter.normalize(arguments, eraseRuntimeValues = true)
     return "$name:${normArguments?.toString()}".toHashId()
 }
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
index 8101a94..f59b0de 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/button/ActionButtonsTest.kt
@@ -17,11 +17,13 @@
 package com.android.settingslib.spa.widget.button
 
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.Launch
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.getBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
@@ -66,4 +68,19 @@
 
         assertThat(clicked).isTrue()
     }
+
+    @Test
+    fun twoButtons_positionIsAligned() {
+        composeTestRule.setContent {
+            ActionButtons(
+                listOf(
+                    ActionButton(text = "Open", imageVector = Icons.Outlined.Launch) {},
+                    ActionButton(text = "Close", imageVector = Icons.Outlined.Close) {},
+                )
+            )
+        }
+
+        assertThat(composeTestRule.onNodeWithText("Open").getBoundsInRoot().top)
+            .isEqualTo(composeTestRule.onNodeWithText("Close").getBoundsInRoot().top)
+    }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
index 77c505d..105fdc8 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/illustration/IllustrationTest.kt
@@ -29,7 +29,7 @@
 import androidx.compose.ui.test.hasAnyAncestor
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.tests.R
+import com.android.settingslib.spa.test.R
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
new file mode 100644
index 0000000..9d0501f
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/FooterTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.settingslib.spa.widget.ui
+
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onRoot
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class FooterTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun footer_isEmpty() {
+        composeTestRule.setContent {
+            Footer(footerText = "")
+        }
+
+        composeTestRule.onRoot().assertIsNotDisplayed()
+    }
+
+    @Test
+    fun footer_notEmpty_displayed() {
+        composeTestRule.setContent {
+            Footer(footerText = FOOTER_TEXT)
+        }
+
+        composeTestRule.onNodeWithText(FOOTER_TEXT).assertIsDisplayed()
+    }
+
+    private companion object {
+        const val FOOTER_TEXT = "Footer text"
+    }
+}
diff --git a/packages/SettingsLib/Spa/testutils/build.gradle b/packages/SettingsLib/Spa/testutils/build.gradle
index e31eb02..dd7058d 100644
--- a/packages/SettingsLib/Spa/testutils/build.gradle
+++ b/packages/SettingsLib/Spa/testutils/build.gradle
@@ -53,7 +53,7 @@
 }
 
 dependencies {
-    api project(":SpaLib")
+    api project(":spa")
 
     api "androidx.arch.core:core-testing:2.1.0"
     api "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
new file mode 100644
index 0000000..dddda55
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/LiveDataTestUtil.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.settingslib.spa.testutils
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
+
+fun <T> LiveData<T>.getOrAwaitValue(
+    timeout: Long = 1,
+    timeUnit: TimeUnit = TimeUnit.SECONDS,
+    afterObserve: () -> Unit = {},
+): T? {
+    var data: T? = null
+    val latch = CountDownLatch(1)
+    val observer = Observer<T> { newData ->
+        data = newData
+        latch.countDown()
+    }
+    this.observeForever(observer)
+
+    afterObserve()
+
+    try {
+        // Don't wait indefinitely if the LiveData is not set.
+        if (!latch.await(timeout, timeUnit)) {
+            throw TimeoutException("LiveData value was never set.")
+        }
+    } finally {
+        this.removeObserver(observer)
+    }
+
+    return data
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
new file mode 100644
index 0000000..01f4cc6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/compose/DisposableBroadcastReceiverAsUserTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.settingslib.spaprivileged.framework.compose
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.UserHandle
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class DisposableBroadcastReceiverAsUserTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var context: Context
+
+    private var registeredBroadcastReceiver: BroadcastReceiver? = null
+
+    @Before
+    fun setUp() {
+        whenever(context.registerReceiverAsUser(any(), any(), any(), any(), any()))
+            .thenAnswer {
+                registeredBroadcastReceiver = it.arguments[0] as BroadcastReceiver
+                null
+            }
+    }
+
+    @Test
+    fun broadcastReceiver_registered() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {}
+            }
+        }
+
+        assertThat(registeredBroadcastReceiver).isNotNull()
+    }
+
+    @Test
+    fun broadcastReceiver_isCalledOnReceive() {
+        var onReceiveIsCalled = false
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(IntentFilter(), USER_HANDLE) {
+                    onReceiveIsCalled = true
+                }
+            }
+        }
+
+        registeredBroadcastReceiver!!.onReceive(context, Intent())
+
+        assertThat(onReceiveIsCalled).isTrue()
+    }
+
+    @Test
+    fun broadcastReceiver_onStartIsCalled() {
+        var onStartIsCalled = false
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                DisposableBroadcastReceiverAsUser(
+                    intentFilter = IntentFilter(),
+                    userHandle = USER_HANDLE,
+                    onStart = { onStartIsCalled = true },
+                    onReceive = {},
+                )
+            }
+        }
+
+        assertThat(onStartIsCalled).isTrue()
+    }
+
+    private companion object {
+        val USER_HANDLE: UserHandle = UserHandle.of(0)
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 61c7fb9..2951001 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -163,6 +163,11 @@
         mUnpairing = false;
     }
 
+    /** Clears any pending messages in the message queue. */
+    public void release() {
+        mHandler.removeCallbacksAndMessages(null);
+    }
+
     private void initDrawableCache() {
         int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
         int cacheSize = maxMemory / 8;
@@ -1441,11 +1446,13 @@
         final boolean tmpJustDiscovered = mJustDiscovered;
         final HearingAidInfo tmpHearingAidInfo = mHearingAidInfo;
         // Set main device from sub device
+        release();
         mDevice = mSubDevice.mDevice;
         mRssi = mSubDevice.mRssi;
         mJustDiscovered = mSubDevice.mJustDiscovered;
         mHearingAidInfo = mSubDevice.mHearingAidInfo;
         // Set sub device from backup
+        mSubDevice.release();
         mSubDevice.mDevice = tmpDevice;
         mSubDevice.mRssi = tmpRssi;
         mSubDevice.mJustDiscovered = tmpJustDiscovered;
@@ -1471,6 +1478,7 @@
      * Remove a device from the member device sets.
      */
     public void removeMemberDevice(CachedBluetoothDevice memberDevice) {
+        memberDevice.release();
         mMemberDevices.remove(memberDevice);
     }
 
@@ -1488,11 +1496,13 @@
         final short tmpRssi = mRssi;
         final boolean tmpJustDiscovered = mJustDiscovered;
         // Set main device from sub device
+        release();
         mDevice = newMainDevice.mDevice;
         mRssi = newMainDevice.mRssi;
         mJustDiscovered = newMainDevice.mJustDiscovered;
 
         // Set sub device from backup
+        newMainDevice.release();
         newMainDevice.mDevice = tmpDevice;
         newMainDevice.mRssi = tmpRssi;
         newMainDevice.mJustDiscovered = tmpJustDiscovered;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index dd56bde..221836b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -223,8 +223,14 @@
 
     public synchronized void clearNonBondedDevices() {
         clearNonBondedSubDevices();
-        mCachedDevices.removeIf(cachedDevice
-            -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE);
+        final List<CachedBluetoothDevice> removedCachedDevice = new ArrayList<>();
+        mCachedDevices.stream()
+                .filter(cachedDevice -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE)
+                .forEach(cachedDevice -> {
+                    cachedDevice.release();
+                    removedCachedDevice.add(cachedDevice);
+                });
+        mCachedDevices.removeAll(removedCachedDevice);
     }
 
     private void clearNonBondedSubDevices() {
@@ -245,6 +251,7 @@
             if (subDevice != null
                     && subDevice.getDevice().getBondState() == BluetoothDevice.BOND_NONE) {
                 // Sub device exists and it is not bonded
+                subDevice.release();
                 cachedDevice.setSubDevice(null);
             }
         }
@@ -294,6 +301,7 @@
                 }
                 if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) {
                     cachedDevice.setJustDiscovered(false);
+                    cachedDevice.release();
                     mCachedDevices.remove(i);
                 }
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index f06aab3..6b7f733 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -176,11 +176,11 @@
      * @param device is the device for which we want to know if supports synchronized presets
      * @return {@code true} if the device supports synchronized presets
      */
-    public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsSynchronizedPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportSynchronizedPresets(device);
+        return mService.supportsSynchronizedPresets(device);
     }
 
     /**
@@ -189,11 +189,11 @@
      * @param device is the device for which we want to know if supports independent presets
      * @return {@code true} if the device supports independent presets
      */
-    public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsIndependentPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportIndependentPresets(device);
+        return mService.supportsIndependentPresets(device);
     }
 
     /**
@@ -202,11 +202,11 @@
      * @param device is the device for which we want to know if supports dynamic presets
      * @return {@code true} if the device supports dynamic presets
      */
-    public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+    public boolean supportsDynamicPresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportDynamicPresets(device);
+        return mService.supportsDynamicPresets(device);
     }
 
     /**
@@ -215,11 +215,11 @@
      * @param device is the device for which we want to know if supports writable presets
      * @return {@code true} if the device supports writable presets
      */
-    public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+    public boolean supportsWritablePresets(@NonNull BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
-        return mService.supportWritablePresets(device);
+        return mService.supportsWritablePresets(device);
     }
 
     @Override
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 65671a2..77c19a1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -998,10 +998,12 @@
 
         mCachedDevice.switchSubDeviceContent();
 
+        verify(mCachedDevice).release();
         assertThat(mCachedDevice.mRssi).isEqualTo(RSSI_2);
         assertThat(mCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_2);
         assertThat(mCachedDevice.mDevice).isEqualTo(mSubDevice);
         assertThat(mCachedDevice.getDeviceSide()).isEqualTo(HearingAidInfo.DeviceSide.SIDE_RIGHT);
+        verify(mSubCachedDevice).release();
         assertThat(mSubCachedDevice.mRssi).isEqualTo(RSSI_1);
         assertThat(mSubCachedDevice.mJustDiscovered).isEqualTo(JUSTDISCOVERED_1);
         assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
diff --git a/packages/SettingsProvider/res/xml/bookmarks.xml b/packages/SettingsProvider/res/xml/bookmarks.xml
index 454f456..4b97b47 100644
--- a/packages/SettingsProvider/res/xml/bookmarks.xml
+++ b/packages/SettingsProvider/res/xml/bookmarks.xml
@@ -19,7 +19,6 @@
      Bookmarks for vendor apps should be added to a bookmarks resource overlay; not here.
 
      Typical shortcuts (not necessarily defined here):
-       'a': Calculator
        'b': Browser
        'c': Contacts
        'e': Email
@@ -29,13 +28,11 @@
        'p': Music
        's': SMS
        't': Talk
+       'u': Calculator
        'y': YouTube
 -->
 <bookmarks>
     <bookmark
-        category="android.intent.category.APP_CALCULATOR"
-        shortcut="a" />
-    <bookmark
         category="android.intent.category.APP_BROWSER"
         shortcut="b" />
     <bookmark
@@ -46,7 +43,7 @@
         shortcut="e" />
     <bookmark
         category="android.intent.category.APP_CALENDAR"
-        shortcut="l" />
+        shortcut="k" />
     <bookmark
         category="android.intent.category.APP_MAPS"
         shortcut="m" />
@@ -56,4 +53,7 @@
     <bookmark
         category="android.intent.category.APP_MESSAGING"
         shortcut="s" />
+    <bookmark
+        category="android.intent.category.APP_CALCULATOR"
+        shortcut="u" />
 </bookmarks>
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 3e5802e..2ee890f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -111,7 +111,6 @@
                 });
         VALIDATORS.put(System.DISPLAY_COLOR_MODE_VENDOR_HINT, ANY_STRING_VALIDATOR);
         VALIDATORS.put(System.SCREEN_OFF_TIMEOUT, NON_NEGATIVE_INTEGER_VALIDATOR);
-        VALIDATORS.put(System.SCREEN_BRIGHTNESS_FOR_VR, new InclusiveIntegerRangeValidator(0, 255));
         VALIDATORS.put(System.SCREEN_BRIGHTNESS_MODE, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
         VALIDATORS.put(System.MODE_RINGER_STREAMS_AFFECTED, NON_NEGATIVE_INTEGER_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 85d0b18..7477416 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -33,7 +33,6 @@
 import android.net.ConnectivityManager;
 import android.os.Build;
 import android.os.Environment;
-import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -2243,9 +2242,6 @@
             loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS,
                     R.integer.def_screen_brightness);
 
-            loadIntegerSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
-                    com.android.internal.R.integer.config_screenBrightnessForVrSettingDefault);
-
             loadBooleanSetting(stmt, Settings.System.SCREEN_BRIGHTNESS_MODE,
                     R.bool.def_screen_brightness_automatic_mode);
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 17078c4..8d4a35d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2841,9 +2841,6 @@
                 Settings.System.SCREEN_BRIGHTNESS,
                 SystemSettingsProto.Screen.BRIGHTNESS);
         dumpSetting(s, p,
-                Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
-                SystemSettingsProto.Screen.BRIGHTNESS_FOR_VR);
-        dumpSetting(s, p,
                 Settings.System.SCREEN_BRIGHTNESS_MODE,
                 SystemSettingsProto.Screen.BRIGHTNESS_MODE);
         dumpSetting(s, p,
@@ -2852,9 +2849,6 @@
         dumpSetting(s, p,
                 Settings.System.SCREEN_BRIGHTNESS_FLOAT,
                 SystemSettingsProto.Screen.BRIGHTNESS_FLOAT);
-        dumpSetting(s, p,
-                Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
-                SystemSettingsProto.Screen.BRIGHTNESS_FOR_VR_FLOAT);
         p.end(screenToken);
 
         dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 0b7b2f9..9192086 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1144,7 +1144,7 @@
             Slog.v(LOG_TAG, "getConfigSetting(" + name + ")");
         }
 
-        DeviceConfig.enforceReadPermission(/*namespace=*/name.split("/")[0]);
+        Settings.Config.enforceReadPermission(/*namespace=*/name.split("/")[0]);
 
         // Get the value.
         synchronized (mLock) {
@@ -1317,7 +1317,7 @@
             Slog.v(LOG_TAG, "getAllConfigFlags() for " + prefix);
         }
 
-        DeviceConfig.enforceReadPermission(
+        Settings.Config.enforceReadPermission(
                 prefix != null ? prefix.split("/")[0] : null);
 
         synchronized (mLock) {
@@ -3595,8 +3595,8 @@
 
         private Uri getNotificationUriFor(int key, String name) {
             if (isConfigSettingsKey(key)) {
-                return (name != null) ? Uri.withAppendedPath(DeviceConfig.CONTENT_URI, name)
-                        : DeviceConfig.CONTENT_URI;
+                return (name != null) ? Uri.withAppendedPath(Settings.Config.CONTENT_URI, name)
+                        : Settings.Config.CONTENT_URI;
             } else if (isGlobalSettingsKey(key)) {
                 return (name != null) ? Uri.withAppendedPath(Settings.Global.CONTENT_URI, name)
                         : Settings.Global.CONTENT_URI;
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 153f0b4..48114ba1e 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -100,8 +100,6 @@
                     Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.PEAK_REFRESH_RATE, // depends on hardware capabilities
                     Settings.System.SCREEN_BRIGHTNESS_FLOAT,
-                    Settings.System.SCREEN_BRIGHTNESS_FOR_VR,
-                    Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT,
                     Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ,
                     Settings.System.MULTI_AUDIO_FOCUS_ENABLED // form-factor/OEM specific
                     );
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
index e588b3d..753378b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/DeviceConfigServiceTest.java
@@ -22,7 +22,6 @@
 
 import android.content.ContentResolver;
 import android.os.Bundle;
-import android.provider.DeviceConfig;
 import android.provider.Settings;
 
 import androidx.test.InstrumentationRegistry;
@@ -180,14 +179,14 @@
             args.putBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY, true);
         }
         resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
+                Settings.Config.CONTENT_URI, Settings.CALL_METHOD_PUT_CONFIG, compositeName, args);
     }
 
     private static String getFromContentProvider(ContentResolver resolver, String namespace,
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI, Settings.CALL_METHOD_GET_CONFIG, compositeName, null);
         assertNotNull(result);
         return result.getString(Settings.NameValueTable.VALUE);
     }
@@ -196,7 +195,8 @@
             String key) {
         String compositeName = namespace + "/" + key;
         Bundle result = resolver.call(
-                DeviceConfig.CONTENT_URI, Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
+                Settings.Config.CONTENT_URI,
+                Settings.CALL_METHOD_DELETE_CONFIG, compositeName, null);
         assertNotNull(result);
         return compositeName.equals(result.getString(Settings.NameValueTable.VALUE));
     }
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index 65f24b0..dd58743 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -361,6 +361,9 @@
     <!-- Permission needed to test wallpaper dimming -->
     <uses-permission android:name="android.permission.SET_WALLPAPER_DIM_AMOUNT" />
 
+    <!-- Permission needed to test wallpapers supporting ambient mode -->
+    <uses-permission android:name="android.permission.AMBIENT_WALLPAPER" />
+
     <!-- Permission required to test ContentResolver caching. -->
     <uses-permission android:name="android.permission.CACHE_CONTENT" />
 
@@ -775,6 +778,9 @@
     <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
     <uses-permission android:name="android.permission.USE_EXACT_ALARM" />
 
+    <!-- Permission required for CTS test - CtsHardwareTestCases -->
+    <uses-permission android:name="android.permission.REMAP_MODIFIER_KEYS" />
+
     <!-- Permissions required for CTS test - CtsAppFgsTestCases -->
     <uses-permission android:name="android.permission.health.READ_ACTIVE_CALORIES_BURNED" />
     <uses-permission android:name="android.permission.health.READ_BASAL_BODY_TEMPERATURE" />
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 7db68b0..a8216e8 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -914,6 +914,29 @@
         <service android:name=".controls.controller.AuxiliaryPersistenceWrapper$DeletionJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE"/>
 
+        <!-- region Note Task -->
+        <activity
+            android:name=".notetask.shortcut.CreateNoteTaskShortcutActivity"
+            android:enabled="false"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay"
+            android:label="@string/note_task_button_label"
+            android:icon="@drawable/ic_note_task_button">
+
+            <intent-filter>
+                <action android:name="android.intent.action.CREATE_SHORTCUT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name=".notetask.shortcut.LaunchNoteTaskActivity"
+            android:exported="true"
+            android:excludeFromRecents="true"
+            android:theme="@android:style/Theme.NoDisplay" />
+        <!-- endregion -->
+
         <!-- started from ControlsRequestReceiver -->
         <activity
             android:name=".controls.management.ControlsRequestDialog"
diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp
index 17ad55f..8acc2f8 100644
--- a/packages/SystemUI/animation/Android.bp
+++ b/packages/SystemUI/animation/Android.bp
@@ -44,23 +44,3 @@
     manifest: "AndroidManifest.xml",
     kotlincflags: ["-Xjvm-default=all"],
 }
-
-android_test {
-    name: "SystemUIAnimationLibTests",
-
-    static_libs: [
-        "SystemUIAnimationLib",
-        "androidx.test.ext.junit",
-        "androidx.test.rules",
-        "testables",
-    ],
-    libs: [
-        "android.test.base",
-    ],
-    srcs: [
-        "**/*.java",
-        "**/*.kt",
-    ],
-    kotlincflags: ["-Xjvm-default=all"],
-    test_suites: ["general-tests"],
-}
diff --git a/packages/SystemUI/animation/TEST_MAPPING b/packages/SystemUI/animation/TEST_MAPPING
deleted file mode 100644
index 3dc8510..0000000
--- a/packages/SystemUI/animation/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "SystemUIAnimationLibTests"
-    }
-  ]
-}
diff --git a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml b/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
deleted file mode 100644
index ee588f99..0000000
--- a/packages/SystemUI/res-keyguard/layout/fgs_footer.xml
+++ /dev/null
@@ -1,105 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     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.
--->
-<!-- TODO(b/242040009): Remove this file. -->
-<FrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="@dimen/qs_security_footer_single_line_height"
-    android:layout_weight="1"
-    android:gravity="center"
-    android:clickable="true"
-    android:visibility="gone">
-
-    <LinearLayout
-        android:id="@+id/fgs_text_container"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="@dimen/qs_footer_action_inset"
-        android:background="@drawable/qs_security_footer_background"
-        android:layout_gravity="end"
-        android:gravity="center"
-        android:paddingHorizontal="@dimen/qs_footer_padding"
-        >
-
-        <ImageView
-            android:id="@+id/primary_footer_icon"
-            android:layout_width="@dimen/qs_footer_icon_size"
-            android:layout_height="@dimen/qs_footer_icon_size"
-            android:gravity="start"
-            android:layout_marginEnd="12dp"
-            android:contentDescription="@null"
-            android:src="@drawable/ic_info_outline"
-            android:tint="?android:attr/textColorSecondary" />
-
-        <TextView
-            android:id="@+id/footer_text"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:maxLines="1"
-            android:ellipsize="end"
-            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-            android:textColor="?android:attr/textColorSecondary"/>
-
-        <ImageView
-            android:id="@+id/fgs_new"
-            android:layout_width="12dp"
-            android:layout_height="12dp"
-            android:scaleType="fitCenter"
-            android:src="@drawable/fgs_dot"
-            android:contentDescription="@string/fgs_dot_content_description"
-            />
-
-        <ImageView
-            android:id="@+id/footer_icon"
-            android:layout_width="@dimen/qs_footer_icon_size"
-            android:layout_height="@dimen/qs_footer_icon_size"
-            android:layout_marginStart="8dp"
-            android:contentDescription="@null"
-            android:src="@*android:drawable/ic_chevron_end"
-            android:autoMirrored="true"
-            android:tint="?android:attr/textColorSecondary" />
-    </LinearLayout>
-
-    <FrameLayout
-        android:id="@+id/fgs_number_container"
-        android:layout_width="@dimen/qs_footer_action_button_size"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:background="@drawable/qs_footer_action_circle"
-        android:focusable="true"
-        android:visibility="gone">
-
-        <TextView
-            android:id="@+id/fgs_number"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-            android:layout_gravity="center"
-            android:textColor="?android:attr/textColorPrimary"
-            android:textSize="18sp"/>
-        <ImageView
-            android:id="@+id/fgs_collapsed_new"
-            android:layout_width="12dp"
-            android:layout_height="12dp"
-            android:scaleType="fitCenter"
-            android:layout_gravity="bottom|end"
-            android:src="@drawable/fgs_dot"
-            android:contentDescription="@string/fgs_dot_content_description"
-            />
-    </FrameLayout>
-
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
index 2261ae8..4a2a1cb 100644
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
@@ -16,10 +16,8 @@
 -->
 
 <!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<!-- TODO(b/242040009): Clean up this file. -->
-<com.android.systemui.qs.FooterActionsView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:layout_width="match_parent"
     android:layout_height="@dimen/footer_actions_height"
     android:elevation="@dimen/qs_panel_elevation"
@@ -28,74 +26,4 @@
     android:background="@drawable/qs_footer_actions_background"
     android:gravity="center_vertical|end"
     android:layout_gravity="bottom"
->
-
-    <LinearLayout
-        android:id="@+id/security_footers_container"
-        android:orientation="horizontal"
-        android:layout_height="@dimen/qs_footer_action_button_size"
-        android:layout_width="0dp"
-        android:layout_weight="1"
-    />
-
-    <!-- Negative margin equal to -->
-    <LinearLayout
-        android:layout_height="match_parent"
-        android:layout_width="wrap_content"
-        android:layout_marginEnd="@dimen/qs_footer_action_inset_negative"
-        >
-
-        <com.android.systemui.statusbar.phone.MultiUserSwitch
-            android:id="@id/multi_user_switch"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle"
-            android:focusable="true">
-
-            <ImageView
-                android:id="@+id/multi_user_avatar"
-                android:layout_width="@dimen/qs_footer_icon_size"
-                android:layout_height="@dimen/qs_footer_icon_size"
-                android:layout_gravity="center"
-                android:scaleType="centerInside" />
-        </com.android.systemui.statusbar.phone.MultiUserSwitch>
-
-        <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-            android:id="@id/settings_button_container"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle"
-            android:clipChildren="false"
-            android:clipToPadding="false">
-
-            <com.android.systemui.statusbar.phone.SettingsButton
-                android:id="@+id/settings_button"
-                android:layout_width="@dimen/qs_footer_icon_size"
-                android:layout_height="@dimen/qs_footer_icon_size"
-                android:layout_gravity="center"
-                android:background="@android:color/transparent"
-                android:focusable="false"
-                android:clickable="false"
-                android:importantForAccessibility="yes"
-                android:contentDescription="@string/accessibility_quick_settings_settings"
-                android:scaleType="centerInside"
-                android:src="@drawable/ic_settings"
-                android:tint="?android:attr/textColorPrimary" />
-
-        </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
-
-        <com.android.systemui.statusbar.AlphaOptimizedImageView
-            android:id="@id/pm_lite"
-            android:layout_width="@dimen/qs_footer_action_button_size"
-            android:layout_height="@dimen/qs_footer_action_button_size"
-            android:background="@drawable/qs_footer_action_circle_color"
-            android:clickable="true"
-            android:clipToPadding="false"
-            android:focusable="true"
-            android:padding="@dimen/qs_footer_icon_padding"
-            android:src="@*android:drawable/ic_lock_power_off"
-            android:contentDescription="@string/accessibility_quick_settings_power_menu"
-            android:tint="?androidprv:attr/textColorOnAccent" />
-
-    </LinearLayout>
-</com.android.systemui.qs.FooterActionsView>
\ No newline at end of file
+/>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
index 316ad39..411fea5 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_num_pad_key.xml
@@ -18,8 +18,6 @@
     <TextView
         android:id="@+id/digit_text"
         style="@style/Widget.TextView.NumPadKey.Digit"
-        android:autoSizeMaxTextSize="32sp"
-        android:autoSizeTextType="uniform"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         />
diff --git a/packages/SystemUI/res/drawable/ic_circle_check_box.xml b/packages/SystemUI/res/drawable/ic_circle_check_box.xml
index b44a32d..00c10ce 100644
--- a/packages/SystemUI/res/drawable/ic_circle_check_box.xml
+++ b/packages/SystemUI/res/drawable/ic_circle_check_box.xml
@@ -18,7 +18,7 @@
     <item
         android:id="@+id/checked"
         android:state_checked="true"
-        android:drawable="@drawable/media_output_status_check" />
+        android:drawable="@drawable/media_output_status_filled_checked" />
     <item
         android:id="@+id/unchecked"
         android:state_checked="false"
diff --git a/packages/SystemUI/res/drawable/ic_note_task_button.xml b/packages/SystemUI/res/drawable/ic_note_task_button.xml
new file mode 100644
index 0000000..bb5e224
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_note_task_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24">
+    <path
+        android:fillColor="#636C6F"
+        android:pathData="M17.6258,4.96L19.0358,6.37L7.4058,18.01L5.9958,16.6L17.6258,4.96ZM16.1358,3.62L4.1258,15.63L3.0158,19.83C2.9058,20.45 3.3858,21 3.9958,21C4.0558,21 4.1058,21 4.1658,20.99L8.3658,19.88L20.3758,7.86C20.7758,7.46 20.9958,6.93 20.9958,6.37C20.9958,5.81 20.7758,5.28 20.3758,4.88L19.1058,3.61C18.7158,3.22 18.1858,3 17.6258,3C17.0658,3 16.5358,3.22 16.1358,3.62Z" />
+    <path
+        android:fillColor="#636C6F"
+        android:fillType="evenOdd"
+        android:pathData="M20.1936,15.3369C20.3748,16.3837 19.9151,17.5414 18.8846,18.7597C19.1546,18.872 19.4576,18.9452 19.7724,18.9867C20.0839,19.0278 20.3683,19.0325 20.5749,19.0266C20.6772,19.0236 20.7578,19.0181 20.8101,19.0138C20.8362,19.0116 20.855,19.0097 20.8657,19.0085L20.8754,19.0074L20.875,19.0075C21.4217,18.9385 21.9214,19.325 21.9918,19.8718C22.0624,20.4195 21.6756,20.9208 21.1279,20.9914L21,19.9996C21.1279,20.9914 21.1265,20.9916 21.1265,20.9916L21.1249,20.9918L21.1211,20.9923L21.1107,20.9935L21.0795,20.997C21.0542,20.9998 21.0199,21.0032 20.9775,21.0067C20.8929,21.0138 20.7753,21.0216 20.6323,21.0257C20.3481,21.0339 19.9533,21.0279 19.5109,20.9695C18.873,20.8854 18.0393,20.6793 17.3106,20.1662C16.9605,20.3559 16.5876,20.4952 16.2299,20.6003C15.5742,20.7927 14.8754,20.8968 14.2534,20.9534C13.6801,21.0055 13.4553,21.0037 13.1015,21.0008C13.0689,21.0005 13.0352,21.0002 13,21H12.8594C12.8214,21.0002 12.785,21.0006 12.7504,21.0009C12.6524,21.0019 12.5683,21.0027 12.5,21H12.0562C12.0277,21.0003 12.0054,21.0006 11.9926,21.001L11.9751,21H9L11,19H11.9795C11.9929,18.9997 12.0064,18.9997 12.0199,19H12.4117C12.4534,18.9996 12.4864,18.9995 12.5,19H12.9675C12.977,18.9999 12.9878,18.9999 13,19C13.0446,19.0003 13.0859,19.0007 13.1249,19.0011C13.4259,19.0038 13.591,19.0054 14.0723,18.9616C14.6201,18.9118 15.1795,18.8242 15.6665,18.6813C15.753,18.6559 15.8346,18.6295 15.9114,18.6022C15.0315,17.2981 14.7125,16.1044 15.015,15.0829C15.4095,13.7511 16.6784,13.2418 17.7026,13.2864C18.7262,13.3309 19.954,13.9529 20.1936,15.3369ZM16.9327,15.6508C16.873,15.8523 16.8651,16.3878 17.4697,17.334C18.2007,16.4284 18.2585,15.8839 18.2229,15.6781C18.1939,15.5108 18.0297,15.3025 17.6157,15.2845C17.2025,15.2665 16.9885,15.4626 16.9327,15.6508Z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
index 55dce8f..43cf003 100644
--- a/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
+++ b/packages/SystemUI/res/drawable/media_output_dialog_seekbar_background.xml
@@ -17,7 +17,10 @@
 <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
     <item android:id="@android:id/background">
         <shape>
-            <corners android:radius="28dp" />
+            <corners
+                     android:bottomRightRadius="28dp"
+                     android:topRightRadius="28dp"
+            />
             <solid android:color="@android:color/transparent" />
             <size
                 android:height="64dp"/>
diff --git a/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml
new file mode 100644
index 0000000..f29f44c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_icon_volume_off.xml
@@ -0,0 +1,27 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="48dp"
+        android:height="48dp"
+        android:viewportWidth="48"
+        android:viewportHeight="48"
+        android:tint="?attr/colorControlNormal"
+        android:autoMirrored="true">
+    <path
+        android:fillColor="@color/media_dialog_item_main_content"
+        android:pathData="M40.65,45.2 L34.05,38.6Q32.65,39.6 31.025,40.325Q29.4,41.05 27.65,41.45V38.35Q28.8,38 29.875,37.575Q30.95,37.15 31.9,36.45L23.65,28.15V40L13.65,30H5.65V18H13.45L2.45,7L4.6,4.85L42.8,43ZM38.85,33.6 L36.7,31.45Q37.7,29.75 38.175,27.85Q38.65,25.95 38.65,23.95Q38.65,18.8 35.65,14.725Q32.65,10.65 27.65,9.55V6.45Q33.85,7.85 37.75,12.725Q41.65,17.6 41.65,23.95Q41.65,26.5 40.95,28.95Q40.25,31.4 38.85,33.6ZM32.15,26.9 L27.65,22.4V15.9Q30,17 31.325,19.2Q32.65,21.4 32.65,24Q32.65,24.75 32.525,25.475Q32.4,26.2 32.15,26.9ZM23.65,18.4 L18.45,13.2 23.65,8ZM20.65,32.7V25.2L16.45,21H8.65V27H14.95ZM18.55,23.1Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_item_check_box.xml b/packages/SystemUI/res/drawable/media_output_item_check_box.xml
new file mode 100644
index 0000000..a0742900
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_item_check_box.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:id="@+id/checked"
+        android:state_checked="true"
+        android:drawable="@drawable/media_output_status_checked" />
+    <item
+        android:id="@+id/unchecked"
+        android:state_checked="false"
+        android:drawable="@drawable/media_output_status_selectable" />
+</selector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_checked.xml b/packages/SystemUI/res/drawable/media_output_status_checked.xml
new file mode 100644
index 0000000..8f83ee2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_checked.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9.55,18 L3.85,12.3 5.275,10.875 9.55,15.15 18.725,5.975 20.15,7.4Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_status_check.xml b/packages/SystemUI/res/drawable/media_output_status_filled_checked.xml
similarity index 100%
rename from packages/SystemUI/res/drawable/media_output_status_check.xml
rename to packages/SystemUI/res/drawable/media_output_status_filled_checked.xml
diff --git a/packages/SystemUI/res/drawable/media_output_status_selectable.xml b/packages/SystemUI/res/drawable/media_output_status_selectable.xml
new file mode 100644
index 0000000..5465aa7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_status_selectable.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ 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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M11,19V13H5V11H11V5H13V11H19V13H13V19Z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/media_output_title_icon_area.xml b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
new file mode 100644
index 0000000..b937937
--- /dev/null
+++ b/packages/SystemUI/res/drawable/media_output_title_icon_area.xml
@@ -0,0 +1,25 @@
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <corners
+        android:bottomLeftRadius="28dp"
+        android:topLeftRadius="28dp"
+        android:bottomRightRadius="0dp"
+        android:topRightRadius="0dp"/>
+    <solid android:color="@color/media_dialog_item_background" />
+</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml
index 73d3f02..f5fc48c 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml
@@ -33,7 +33,7 @@
                 android:layout_width="@dimen/rear_display_animation_width"
                 android:layout_height="@dimen/rear_display_animation_height"
                 android:layout_gravity="center"
-                android:contentDescription="@null"
+                android:contentDescription="@string/rear_display_accessibility_folded_animation"
                 android:scaleType="fitXY"
                 app:lottie_rawRes="@raw/rear_display_folded"
                 app:lottie_autoPlay="true"
diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
index 20b93d9..6de06f7 100644
--- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
+++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml
@@ -34,7 +34,7 @@
             android:layout_width="@dimen/rear_display_animation_width"
             android:layout_height="@dimen/rear_display_animation_height"
             android:layout_gravity="center"
-            android:contentDescription="@null"
+            android:contentDescription="@string/rear_display_accessibility_unfolded_animation"
             android:scaleType="fitXY"
             app:lottie_rawRes="@raw/rear_display_turnaround"
             app:lottie_autoPlay="true"
diff --git a/packages/SystemUI/res/layout/media_output_list_item_advanced.xml b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
new file mode 100644
index 0000000..d49b9f1
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_output_list_item_advanced.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/device_container"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="64dp"
+        android:id="@+id/item_layout"
+        android:background="@drawable/media_output_item_background"
+        android:layout_marginStart="16dp"
+        android:layout_marginEnd="80dp"
+        android:layout_marginBottom="12dp">
+        <FrameLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_gravity="center_vertical|start">
+            <com.android.systemui.media.dialog.MediaOutputSeekbar
+                android:id="@+id/volume_seekbar"
+                android:splitTrack="false"
+                android:visibility="gone"
+                android:paddingStart="64dp"
+                android:paddingEnd="0dp"
+                android:background="@null"
+                android:contentDescription="@string/media_output_dialog_accessibility_seekbar"
+                android:progressDrawable="@drawable/media_output_dialog_seekbar_background"
+                android:thumb="@null"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"/>
+        </FrameLayout>
+
+        <FrameLayout
+            android:id="@+id/icon_area"
+            android:layout_width="64dp"
+            android:layout_height="64dp"
+            android:background="@drawable/media_output_title_icon_area"
+            android:layout_gravity="center_vertical|start">
+            <ImageView
+                android:id="@+id/title_icon"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:animateLayoutChanges="true"
+                android:layout_gravity="center"/>
+            <TextView
+                android:id="@+id/volume_value"
+                android:animateLayoutChanges="true"
+                android:layout_gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+                android:textSize="16sp"
+                android:visibility="gone"/>
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical|start"
+            android:layout_marginStart="72dp"
+            android:layout_marginEnd="56dp"
+            android:ellipsize="end"
+            android:maxLines="1"
+            android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+            android:textSize="16sp"/>
+
+        <LinearLayout
+            android:id="@+id/two_line_layout"
+            android:orientation="vertical"
+            android:layout_width="wrap_content"
+            android:layout_gravity="center_vertical|start"
+            android:layout_height="48dp"
+            android:layout_marginEnd="56dp"
+            android:layout_marginStart="72dp">
+            <TextView
+                android:id="@+id/two_line_title"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+                android:textColor="@color/media_dialog_item_main_content"
+                android:textSize="16sp"/>
+            <TextView
+                android:id="@+id/subtitle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:textColor="@color/media_dialog_item_main_content"
+                android:textSize="14sp"
+                android:fontFamily="@*android:string/config_bodyFontFamily"
+                android:visibility="gone"/>
+        </LinearLayout>
+
+        <ProgressBar
+            android:id="@+id/volume_indeterminate_progress"
+            style="?android:attr/progressBarStyleSmallTitle"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginEnd="16dp"
+            android:indeterminate="true"
+            android:layout_gravity="end|center"
+            android:indeterminateOnly="true"
+            android:visibility="gone"/>
+
+        <ImageView
+            android:id="@+id/media_output_item_status"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:layout_marginEnd="16dp"
+            android:indeterminate="true"
+            android:layout_gravity="end|center"
+            android:indeterminateOnly="true"
+            android:importantForAccessibility="no"
+            android:visibility="gone"/>
+    </FrameLayout>
+    <FrameLayout
+        android:id="@+id/end_action_area"
+        android:layout_width="64dp"
+        android:layout_height="64dp"
+        android:visibility="gone"
+        android:layout_marginBottom="6dp"
+        android:layout_marginEnd="8dp"
+        android:layout_gravity="end|center"
+        android:gravity="center"
+        android:background="@drawable/media_output_item_background_active">
+        <CheckBox
+            android:id="@+id/check_box"
+            android:focusable="false"
+            android:importantForAccessibility="no"
+            android:layout_gravity="center"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:button="@drawable/media_output_item_check_box"
+            android:visibility="gone"
+            />
+    </FrameLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 530db0d..13c9a5e 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -35,7 +35,6 @@
         android:layout_height="@dimen/qs_media_session_height_expanded"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent"
         android:translationZ="0dp"
         android:scaleType="centerCrop"
diff --git a/packages/SystemUI/res/layout/quick_settings_security_footer.xml b/packages/SystemUI/res/layout/quick_settings_security_footer.xml
deleted file mode 100644
index 194f3dd..0000000
--- a/packages/SystemUI/res/layout/quick_settings_security_footer.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<!-- TODO(b/242040009): Remove this file. -->
-<LinearLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="0dp"
-    android:layout_height="@dimen/qs_security_footer_single_line_height"
-    android:layout_weight="1"
-    android:clickable="true"
-    android:orientation="horizontal"
-    android:paddingHorizontal="@dimen/qs_footer_padding"
-    android:gravity="center_vertical"
-    android:layout_gravity="center_vertical|center_horizontal"
-    android:layout_marginEnd="@dimen/qs_footer_action_inset"
-    android:background="@drawable/qs_security_footer_background"
-    >
-
-    <ImageView
-        android:id="@+id/primary_footer_icon"
-        android:layout_width="@dimen/qs_footer_icon_size"
-        android:layout_height="@dimen/qs_footer_icon_size"
-        android:gravity="start"
-        android:layout_marginEnd="12dp"
-        android:contentDescription="@null"
-        android:tint="?android:attr/textColorSecondary" />
-
-    <TextView
-        android:id="@+id/footer_text"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:layout_weight="1"
-        android:singleLine="true"
-        android:ellipsize="end"
-        android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
-        android:textColor="?android:attr/textColorSecondary"/>
-
-    <ImageView
-        android:id="@+id/footer_icon"
-        android:layout_width="@dimen/qs_footer_icon_size"
-        android:layout_height="@dimen/qs_footer_icon_size"
-        android:layout_marginStart="8dp"
-        android:contentDescription="@null"
-        android:src="@*android:drawable/ic_chevron_end"
-        android:autoMirrored="true"
-        android:tint="?android:attr/textColorSecondary" />
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index bafdb11..4abc176 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -98,16 +98,18 @@
             android:singleLine="true"
             android:ellipsize="marquee"
             android:focusable="true" />
-        <FrameLayout android:id="@+id/keyguard_bouncer_container"
-                     android:layout_height="0dp"
-                     android:layout_width="match_parent"
-                     android:layout_weight="1"
-                     android:background="@android:color/transparent"
-                     android:visibility="invisible"
-                     android:clipChildren="false"
-                     android:clipToPadding="false" />
     </LinearLayout>
 
+    <FrameLayout android:id="@+id/keyguard_bouncer_container"
+        android:paddingTop="@dimen/status_bar_height"
+        android:layout_height="match_parent"
+        android:layout_width="match_parent"
+        android:layout_weight="1"
+        android:background="@android:color/transparent"
+        android:visibility="invisible"
+        android:clipChildren="false"
+        android:clipToPadding="false" />
+
     <com.android.systemui.biometrics.AuthRippleView
         android:id="@+id/auth_ripple"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index dc7e4e4..73baeac 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1222,6 +1222,8 @@
     <dimen name="media_output_dialog_app_tier_icon_size">20dp</dimen>
     <dimen name="media_output_dialog_background_radius">16dp</dimen>
     <dimen name="media_output_dialog_active_background_radius">28dp</dimen>
+    <dimen name="media_output_dialog_default_margin_end">16dp</dimen>
+    <dimen name="media_output_dialog_selectable_margin_end">80dp</dimen>
 
     <!-- Distance that the full shade transition takes in order to complete by tapping on a button
          like "expand". -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 49dd574..fd2e324 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -36,4 +36,8 @@
       avatar will no longer show on the lockscreen -->
     <bool name="flag_user_switcher_chip">false</bool>
 
+    <!-- Whether the battery icon is allowed to display a shield when battery life is being
+         protected. -->
+    <bool name="flag_battery_shield_icon">false</bool>
+
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index a4e9983..5d4fa58 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -849,7 +849,7 @@
     <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
 
     <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
-    <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+    <string name="sensor_privacy_dialog_open_settings">Open Settings</string>
 
     <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
     <string name="media_seamless_other_device">Other device</string>
@@ -2481,7 +2481,7 @@
     <!-- Controls menu, edit [CHAR_LIMIT=30] -->
     <string name="controls_menu_edit">Edit controls</string>
 
-    <!-- Title for the media output group dialog with media related devices [CHAR LIMIT=50] -->
+    <!-- Title for the media output dialog with media related devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_add_output">Add outputs</string>
     <!-- Title for the media output slice with group devices [CHAR LIMIT=50] -->
     <string name="media_output_dialog_group">Group</string>
@@ -2505,6 +2505,8 @@
     <string name="media_output_dialog_accessibility_title">Available devices for audio output.</string>
     <!-- Accessibility text describing purpose of seekbar in media output dialog. [CHAR LIMIT=NONE] -->
     <string name="media_output_dialog_accessibility_seekbar">Volume</string>
+    <!-- Summary for media output volume of a device in percentage [CHAR LIMIT=NONE] -->
+    <string name="media_output_dialog_volume_percentage"><xliff:g id="percentage" example="10">%1$d</xliff:g>%%</string>
 
     <!-- Media Output Broadcast Dialog -->
     <!-- Title for Broadcast First Notify Dialog [CHAR LIMIT=60] -->
@@ -2773,6 +2775,10 @@
         <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
     </string>
 
+    <!-- TODO(b/259369672): Replace with final resource. -->
+    <!-- [CHAR LIMIT=30] Label used to open Note Task -->
+    <string name="note_task_button_label">Notetaking</string>
+
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
     <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
@@ -2868,4 +2874,8 @@
     <string name="rear_display_bottom_sheet_description">Use the rear-facing camera for a wider photo with higher resolution.</string>
     <!-- Text for education page description to warn user that the display will turn off if the button is clicked. [CHAR_LIMIT=NONE] -->
     <string name="rear_display_bottom_sheet_warning"><b>&#x2731; This screen will turn off</b></string>
+    <!-- Text for education page content description for folded animation. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_accessibility_folded_animation">Foldable device being unfolded</string>
+    <!-- Text for education page content description for unfolded animation. [CHAR_LIMIT=NONE] -->
+    <string name="rear_display_accessibility_unfolded_animation">Foldable device being flipped around</string>
 </resources>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index 6d0cc5e..738b37c 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -34,7 +34,6 @@
 import platform.test.screenshot.DeviceEmulationRule
 import platform.test.screenshot.DeviceEmulationSpec
 import platform.test.screenshot.MaterialYouColorsRule
-import platform.test.screenshot.PathConfig
 import platform.test.screenshot.ScreenshotTestRule
 import platform.test.screenshot.getEmulatedDevicePathConfig
 import platform.test.screenshot.matchers.BitmapMatcher
@@ -43,7 +42,6 @@
 class ViewScreenshotTestRule(
     emulationSpec: DeviceEmulationSpec,
     private val matcher: BitmapMatcher = UnitTestBitmapMatcher,
-    pathConfig: PathConfig = getEmulatedDevicePathConfig(emulationSpec),
     assetsPathRelativeToBuildRoot: String
 ) : TestRule {
     private val colorsRule = MaterialYouColorsRule()
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index d172690..d85292a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -38,6 +38,7 @@
         const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
         const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
         const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
+        const val ACTION_SYSUI_STARTED = "com.android.systemui.STARTED"
         const val EXTRA_ID = "id"
         const val EXTRA_VALUE = "value"
         const val EXTRA_FLAGS = "flags"
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
index da81d54..2d83458 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/CombinedCondition.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/CombinedCondition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition
+package com.android.systemui.shared.condition
 
 /**
  * A higher order [Condition] which combines multiple conditions with a specified
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
index b39adef..cc48090e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Condition.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Condition.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,13 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
 
 import android.util.Log;
 
-import com.android.systemui.statusbar.policy.CallbackController;
-
-import org.jetbrains.annotations.NotNull;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleEventObserver;
+import androidx.lifecycle.LifecycleOwner;
 
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -33,7 +34,7 @@
  * Base class for a condition that needs to be fulfilled in order for {@link Monitor} to inform
  * its callbacks.
  */
-public abstract class Condition implements CallbackController<Condition.Callback> {
+public abstract class Condition {
     private final String mTag = getClass().getSimpleName();
 
     private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>();
@@ -79,8 +80,7 @@
      * Registers a callback to receive updates once started. This should be called before
      * {@link #start()}. Also triggers the callback immediately if already started.
      */
-    @Override
-    public void addCallback(@NotNull Callback callback) {
+    public void addCallback(@NonNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "adding callback");
         mCallbacks.add(new WeakReference<>(callback));
 
@@ -96,8 +96,7 @@
     /**
      * Removes the provided callback from further receiving updates.
      */
-    @Override
-    public void removeCallback(@NotNull Callback callback) {
+    public void removeCallback(@NonNull Callback callback) {
         if (shouldLog()) Log.d(mTag, "removing callback");
         final Iterator<WeakReference<Callback>> iterator = mCallbacks.iterator();
         while (iterator.hasNext()) {
@@ -116,6 +115,29 @@
     }
 
     /**
+     * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
+     * and {@link #removeCallback(Callback)} when not resumed automatically.
+     */
+    public Callback observe(LifecycleOwner owner, Callback listener) {
+        return observe(owner.getLifecycle(), listener);
+    }
+
+    /**
+     * Wrapper to {@link #addCallback(Callback)} when a lifecycle is in the resumed state
+     * and {@link #removeCallback(Condition.Callback)} when not resumed automatically.
+     */
+    public Callback observe(Lifecycle lifecycle, Callback listener) {
+        lifecycle.addObserver((LifecycleEventObserver) (lifecycleOwner, event) -> {
+            if (event == Lifecycle.Event.ON_RESUME) {
+                addCallback(listener);
+            } else if (event == Lifecycle.Event.ON_PAUSE) {
+                removeCallback(listener);
+            }
+        });
+        return listener;
+    }
+
+    /**
      * Updates the value for whether the condition has been fulfilled, and sends an update if the
      * value changes and any callback is registered.
      *
@@ -187,7 +209,7 @@
      * Creates a new condition which will only be true when both this condition and all the provided
      * conditions are true.
      */
-    public Condition and(Collection<Condition> others) {
+    public Condition and(@NonNull Collection<Condition> others) {
         final List<Condition> conditions = new ArrayList<>(others);
         conditions.add(this);
         return new CombinedCondition(conditions, Evaluator.OP_AND);
@@ -197,7 +219,7 @@
      * Creates a new condition which will only be true when both this condition and the provided
      * condition is true.
      */
-    public Condition and(Condition other) {
+    public Condition and(@NonNull Condition other) {
         return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_AND);
     }
 
@@ -205,7 +227,7 @@
      * Creates a new condition which will only be true when either this condition or any of the
      * provided conditions are true.
      */
-    public Condition or(Collection<Condition> others) {
+    public Condition or(@NonNull Collection<Condition> others) {
         final List<Condition> conditions = new ArrayList<>(others);
         conditions.add(this);
         return new CombinedCondition(conditions, Evaluator.OP_OR);
@@ -215,7 +237,7 @@
      * Creates a new condition which will only be true when either this condition or the provided
      * condition is true.
      */
-    public Condition or(Condition other) {
+    public Condition or(@NonNull Condition other) {
         return new CombinedCondition(Arrays.asList(this, other), Evaluator.OP_OR);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
similarity index 81%
rename from packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
index cf44e84..23742c5 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Evaluator.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Evaluator.kt
@@ -1,4 +1,20 @@
-package com.android.systemui.util.condition
+/*
+ * 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.shared.condition
 
 import android.annotation.IntDef
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
index 24bc907..95675ce 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/condition/Monitor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
 
 import android.util.ArraySet;
 import android.util.Log;
 
-import com.android.systemui.dagger.qualifiers.Main;
+import androidx.annotation.NonNull;
 
-import org.jetbrains.annotations.NotNull;
+import com.android.systemui.dagger.qualifiers.Main;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -100,7 +100,7 @@
      * @param subscription A {@link Subscription} detailing the desired conditions and callback.
      * @return A {@link Subscription.Token} that can be used to remove the subscription.
      */
-    public Subscription.Token addSubscription(@NotNull Subscription subscription) {
+    public Subscription.Token addSubscription(@NonNull Subscription subscription) {
         final Subscription.Token token = new Subscription.Token();
         final SubscriptionState state = new SubscriptionState(subscription);
 
@@ -131,7 +131,7 @@
      * @param token The {@link Subscription.Token} returned when the {@link Subscription} was
      *              originally added.
      */
-    public void removeSubscription(@NotNull Subscription.Token token) {
+    public void removeSubscription(@NonNull Subscription.Token token) {
         mExecutor.execute(() -> {
             if (shouldLog()) Log.d(mTag, "removing subscription");
             if (!mSubscriptions.containsKey(token)) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
index c9ea794..5883b6c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/utilities/PreviewPositionHelper.java
@@ -87,8 +87,8 @@
                 taskPercent = mDesiredStagePosition != STAGE_POSITION_TOP_OR_LEFT
                         ? mSplitBounds.topTaskPercent
                         : (1 - (mSplitBounds.topTaskPercent + mSplitBounds.dividerHeightPercent));
-                // Scale portrait height to that of (actual screen - taskbar inset)
-                fullscreenTaskHeight = (screenHeightPx) * taskPercent;
+                // Scale portrait height to that of the actual screen
+                fullscreenTaskHeight = screenHeightPx * taskPercent;
                 if (mTaskbarInApp) {
                     canvasScreenRatio = canvasHeight / fullscreenTaskHeight;
                 } else {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index 860c8e3..7da27b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -260,7 +260,8 @@
         if (reason != PROMPT_REASON_NONE) {
             int promtReasonStringRes = mView.getPromptReasonStringRes(reason);
             if (promtReasonStringRes != 0) {
-                mMessageAreaController.setMessage(promtReasonStringRes);
+                mMessageAreaController.setMessage(
+                        mView.getResources().getString(promtReasonStringRes), false);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 2e9ad58..53b569a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -143,7 +143,9 @@
 
     public void startAppearAnimation() {
         if (TextUtils.isEmpty(mMessageAreaController.getMessage())) {
-            mMessageAreaController.setMessage(getInitialMessageResId());
+            mMessageAreaController.setMessage(
+                    mView.getResources().getString(getInitialMessageResId()),
+                    /* animate= */ false);
         }
         mView.startAppearAnimation();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 5d86ccd..67e3400 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -52,6 +52,7 @@
     private int mYTransOffset;
     private View mBouncerMessageView;
     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
+    public static final long ANIMATION_DURATION = 650;
 
     public KeyguardPINView(Context context) {
         this(context, null);
@@ -181,7 +182,7 @@
         if (mAppearAnimator.isRunning()) {
             mAppearAnimator.cancel();
         }
-        mAppearAnimator.setDuration(650);
+        mAppearAnimator.setDuration(ANIMATION_DURATION);
         mAppearAnimator.addUpdateListener(animation -> animate(animation.getAnimatedFraction()));
         mAppearAnimator.start();
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 2cc5ccdc..c985fd7 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -34,6 +34,7 @@
 import android.graphics.Rect;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.WindowInsets;
 import android.view.WindowInsetsAnimationControlListener;
 import android.view.WindowInsetsAnimationController;
 import android.view.animation.AnimationUtils;
@@ -236,4 +237,50 @@
         return getResources().getString(
                 com.android.internal.R.string.keyguard_accessibility_password_unlock);
     }
+
+    @Override
+    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+        if (!mPasswordEntry.isFocused() && isVisibleToUser()) {
+            mPasswordEntry.requestFocus();
+        }
+        return super.onApplyWindowInsets(insets);
+    }
+
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        super.onWindowFocusChanged(hasWindowFocus);
+        if (hasWindowFocus) {
+            if (isVisibleToUser()) {
+                showKeyboard();
+            } else {
+                hideKeyboard();
+            }
+        }
+    }
+
+    /**
+     * Sends signal to the focused window to show the keyboard.
+     */
+    public void showKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && !mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.requestFocus();
+                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
+            }
+        });
+    }
+
+    /**
+     * Sends signal to the focused window to hide the keyboard.
+     */
+    public void hideKeyboard() {
+        post(() -> {
+            if (mPasswordEntry.isAttachedToWindow()
+                    && mPasswordEntry.getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+                mPasswordEntry.clearFocus();
+                mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
+            }
+        });
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 195e8f9..d221e22 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -26,7 +26,6 @@
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
-import android.view.WindowInsets;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
@@ -200,12 +199,9 @@
             return;
         }
 
-        mView.post(() -> {
-            if (mView.isShown()) {
-                mPasswordEntry.requestFocus();
-                mPasswordEntry.getWindowInsetsController().show(WindowInsets.Type.ime());
-            }
-        });
+        if (mView.isShown()) {
+            mView.showKeyboard();
+        }
     }
 
     @Override
@@ -227,16 +223,12 @@
                 super.onPause();
             });
         }
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     @Override
     public void onStartingToHide() {
-        if (mPasswordEntry.isAttachedToWindow()) {
-            mPasswordEntry.getWindowInsetsController().hide(WindowInsets.Type.ime());
-        }
+        mView.hideKeyboard();
     }
 
     private void updateSwitchImeButton() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8f3484a..5d7a6f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -36,8 +36,11 @@
 
 import static java.lang.Integer.max;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
@@ -967,11 +970,23 @@
             }
 
             mUserSwitcherViewGroup.setAlpha(0f);
-            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
-                    1f);
-            alphaAnim.setInterpolator(Interpolators.ALPHA_IN);
-            alphaAnim.setDuration(500);
-            alphaAnim.start();
+            ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
+            int yTrans = mView.getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry);
+            animator.setInterpolator(Interpolators.STANDARD_DECELERATE);
+            animator.setDuration(650);
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mUserSwitcherViewGroup.setAlpha(1f);
+                    mUserSwitcherViewGroup.setTranslationY(0f);
+                }
+            });
+            animator.addUpdateListener(animation -> {
+                float value = (float) animation.getAnimatedValue();
+                mUserSwitcherViewGroup.setAlpha(value);
+                mUserSwitcherViewGroup.setTranslationY(yTrans - yTrans * value);
+            });
+            animator.start();
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 51ade29..84ef505 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -382,6 +382,9 @@
     private static final int HAL_ERROR_RETRY_MAX = 20;
 
     @VisibleForTesting
+    protected static final int HAL_POWER_PRESS_TIMEOUT = 50; // ms
+
+    @VisibleForTesting
     protected final Runnable mFpCancelNotReceived = this::onFingerprintCancelNotReceived;
 
     private final Runnable mFaceCancelNotReceived = this::onFaceCancelNotReceived;
@@ -902,7 +905,7 @@
         }
     }
 
-    private final Runnable mRetryFingerprintAuthentication = new Runnable() {
+    private final Runnable mRetryFingerprintAuthenticationAfterHwUnavailable = new Runnable() {
         @SuppressLint("MissingPermission")
         @Override
         public void run() {
@@ -911,7 +914,8 @@
                 updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
             } else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
                 mHardwareFingerprintUnavailableRetryCount++;
-                mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
+                mHandler.postDelayed(mRetryFingerprintAuthenticationAfterHwUnavailable,
+                        HAL_ERROR_RETRY_TIMEOUT);
             }
         }
     };
@@ -941,12 +945,16 @@
 
         if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
             mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, HAL_ERROR_RETRY_TIMEOUT);
-            mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
+            mHandler.postDelayed(mRetryFingerprintAuthenticationAfterHwUnavailable,
+                    HAL_ERROR_RETRY_TIMEOUT);
         }
 
         if (msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
-            mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, 0);
-            updateFingerprintListeningState(BIOMETRIC_ACTION_START);
+            mLogger.logRetryAfterFpErrorWithDelay(msgId, errString, HAL_POWER_PRESS_TIMEOUT);
+            mHandler.postDelayed(() -> {
+                mLogger.d("Retrying fingerprint listening after power pressed error.");
+                updateFingerprintListeningState(BIOMETRIC_ACTION_START);
+            }, HAL_POWER_PRESS_TIMEOUT);
         }
 
         boolean lockedOutStateChanged = false;
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index b66ae28..ceebe4c 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -231,7 +231,7 @@
             int2 = delay
             str1 = "$errString"
         }, {
-            "Fingerprint retrying auth after $int2 ms due to($int1) -> $str1"
+            "Fingerprint scheduling retry auth after $int2 ms due to($int1) -> $str1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 02a6d7b..e6f559b 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -210,8 +210,10 @@
                 (FaceScanningOverlay) getOverlayView(mFaceScanningViewId);
         if (faceScanningOverlay != null) {
             faceScanningOverlay.setHideOverlayRunnable(() -> {
+                Trace.beginSection("ScreenDecorations#hideOverlayRunnable");
                 updateOverlayWindowVisibilityIfViewExists(
                         faceScanningOverlay.findViewById(mFaceScanningViewId));
+                Trace.endSection();
             });
             faceScanningOverlay.enableShowProtection(false);
         }
@@ -273,16 +275,18 @@
             if (mOverlays == null || !shouldOptimizeVisibility()) {
                 return;
             }
-
+            Trace.beginSection("ScreenDecorations#updateOverlayWindowVisibilityIfViewExists");
             for (final OverlayWindow overlay : mOverlays) {
                 if (overlay == null) {
                     continue;
                 }
                 if (overlay.getView(view.getId()) != null) {
                     overlay.getRootView().setVisibility(getWindowVisibility(overlay, true));
+                    Trace.endSection();
                     return;
                 }
             }
+            Trace.endSection();
         });
     }
 
@@ -370,6 +374,7 @@
     }
 
     private void startOnScreenDecorationsThread() {
+        Trace.beginSection("ScreenDecorations#startOnScreenDecorationsThread");
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mContext.getDisplay().getDisplayInfo(mDisplayInfo);
@@ -472,6 +477,7 @@
 
         mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
         updateConfiguration();
+        Trace.endSection();
     }
 
     @VisibleForTesting
@@ -521,6 +527,12 @@
     }
 
     private void setupDecorations() {
+        Trace.beginSection("ScreenDecorations#setupDecorations");
+        setupDecorationsInner();
+        Trace.endSection();
+    }
+
+    private void setupDecorationsInner() {
         if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()
                 || mFaceScanningFactory.getHasProviders()) {
 
@@ -573,7 +585,11 @@
                 return;
             }
 
-            mMainExecutor.execute(() -> mTunerService.addTunable(this, SIZE));
+            mMainExecutor.execute(() -> {
+                Trace.beginSection("ScreenDecorations#addTunable");
+                mTunerService.addTunable(this, SIZE);
+                Trace.endSection();
+            });
 
             // Watch color inversion and invert the overlay as needed.
             if (mColorInversionSetting == null) {
@@ -593,7 +609,11 @@
             mUserTracker.addCallback(mUserChangedCallback, mExecutor);
             mIsRegistered = true;
         } else {
-            mMainExecutor.execute(() -> mTunerService.removeTunable(this));
+            mMainExecutor.execute(() -> {
+                Trace.beginSection("ScreenDecorations#removeTunable");
+                mTunerService.removeTunable(this);
+                Trace.endSection();
+            });
 
             if (mColorInversionSetting != null) {
                 mColorInversionSetting.setListening(false);
@@ -939,6 +959,7 @@
         }
 
         mExecutor.execute(() -> {
+            Trace.beginSection("ScreenDecorations#onConfigurationChanged");
             int oldRotation = mRotation;
             mPendingConfigChange = false;
             updateConfiguration();
@@ -951,6 +972,7 @@
                 // the updated rotation).
                 updateLayoutParams();
             }
+            Trace.endSection();
         });
     }
 
@@ -1119,6 +1141,7 @@
             if (mOverlays == null || !SIZE.equals(key)) {
                 return;
             }
+            Trace.beginSection("ScreenDecorations#onTuningChanged");
             try {
                 final int sizeFactor = Integer.parseInt(newValue);
                 mRoundedCornerResDelegate.setTuningSizeFactor(sizeFactor);
@@ -1132,6 +1155,7 @@
                     R.id.rounded_corner_bottom_right
             });
             updateHwLayerRoundedCornerExistAndSize();
+            Trace.endSection();
         });
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
index 1e14763..e2a9d54 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java
@@ -25,6 +25,9 @@
 import android.os.Looper;
 import android.util.Log;
 import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.OvershootInterpolator;
+import android.view.animation.TranslateAnimation;
 
 import androidx.dynamicanimation.animation.DynamicAnimation;
 import androidx.dynamicanimation.animation.FlingAnimation;
@@ -33,6 +36,7 @@
 import androidx.dynamicanimation.animation.SpringForce;
 import androidx.recyclerview.widget.RecyclerView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
@@ -55,7 +59,11 @@
     private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
     private static final float SPRING_STIFFNESS = 700f;
     private static final float ESCAPE_VELOCITY = 750f;
+    // Make tucked animation by using translation X relative to the view itself.
+    private static final float ANIMATION_TO_X_VALUE = 0.5f;
 
+    private static final int ANIMATION_START_OFFSET_MS = 600;
+    private static final int ANIMATION_DURATION_MS = 600;
     private static final int FADE_OUT_DURATION_MS = 1000;
     private static final int FADE_EFFECT_DURATION_MS = 3000;
 
@@ -64,10 +72,12 @@
     private final Handler mHandler;
     private boolean mIsFadeEffectEnabled;
     private DismissAnimationController.DismissCallback mDismissCallback;
+    private Runnable mSpringAnimationsEndAction;
 
     // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
     // DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
-    private final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
+    @VisibleForTesting
+    final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
             new HashMap<>();
 
     MenuAnimationController(MenuView menuView) {
@@ -102,6 +112,13 @@
         }
     }
 
+    /**
+     * Sets the action to be called when the all dynamic animations are completed.
+     */
+    void setSpringAnimationsEndAction(Runnable runnable) {
+        mSpringAnimationsEndAction = runnable;
+    }
+
     void setDismissCallback(
             DismissAnimationController.DismissCallback dismissCallback) {
         mDismissCallback = dismissCallback;
@@ -192,7 +209,7 @@
                         ? bounds.right
                         : bounds.bottom;
 
-        final FlingAnimation flingAnimation = new FlingAnimation(mMenuView, menuPositionProperty);
+        final FlingAnimation flingAnimation = createFlingAnimation(mMenuView, menuPositionProperty);
         flingAnimation.setFriction(friction)
                 .setStartVelocity(velocity)
                 .setMinValue(Math.min(currentValue, min))
@@ -217,7 +234,14 @@
         flingAnimation.start();
     }
 
-    private void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
+    @VisibleForTesting
+    FlingAnimation createFlingAnimation(MenuView menuView,
+            MenuPositionProperty menuPositionProperty) {
+        return new FlingAnimation(menuView, menuPositionProperty);
+    }
+
+    @VisibleForTesting
+    void springMenuWith(DynamicAnimation.ViewProperty property, SpringForce spring,
             float velocity, float finalPosition) {
         final MenuPositionProperty menuPositionProperty = new MenuPositionProperty(property);
         final SpringAnimation springAnimation =
@@ -228,8 +252,13 @@
                                 return;
                             }
 
-                            onSpringAnimationEnd(new PointF(mMenuView.getTranslationX(),
-                                    mMenuView.getTranslationY()));
+                            final boolean areAnimationsRunning =
+                                    mPositionAnimations.values().stream().anyMatch(
+                                            DynamicAnimation::isRunning);
+                            if (!areAnimationsRunning) {
+                                onSpringAnimationsEnd(new PointF(mMenuView.getTranslationX(),
+                                        mMenuView.getTranslationY()));
+                            }
                         })
                         .setStartVelocity(velocity);
 
@@ -332,11 +361,15 @@
                 .start();
     }
 
-    private void onSpringAnimationEnd(PointF position) {
+    private void onSpringAnimationsEnd(PointF position) {
         mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
         constrainPositionAndUpdate(position);
 
         fadeOutIfEnabled();
+
+        if (mSpringAnimationsEndAction != null) {
+            mSpringAnimationsEndAction.run();
+        }
     }
 
     private void constrainPositionAndUpdate(PointF position) {
@@ -387,6 +420,26 @@
         mHandler.removeCallbacksAndMessages(/* token= */ null);
     }
 
+    void startTuckedAnimationPreview() {
+        fadeInNowIfEnabled();
+
+        final float toXValue = isOnLeftSide()
+                ? -ANIMATION_TO_X_VALUE
+                : ANIMATION_TO_X_VALUE;
+        final TranslateAnimation animation =
+                new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0,
+                        Animation.RELATIVE_TO_SELF, toXValue,
+                        Animation.RELATIVE_TO_SELF, 0,
+                        Animation.RELATIVE_TO_SELF, 0);
+        animation.setDuration(ANIMATION_DURATION_MS);
+        animation.setRepeatMode(Animation.REVERSE);
+        animation.setInterpolator(new OvershootInterpolator());
+        animation.setRepeatCount(Animation.INFINITE);
+        animation.setStartOffset(ANIMATION_START_OFFSET_MS);
+
+        mMenuView.startAnimation(animation);
+    }
+
     private Handler createUiHandler() {
         return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
new file mode 100644
index 0000000..4400534
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipView.java
@@ -0,0 +1,222 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static android.util.TypedValue.COMPLEX_UNIT_PX;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.CornerPathEffect;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.ShapeDrawable;
+import android.text.method.LinkMovementMethod;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+
+import com.android.settingslib.Utils;
+import com.android.systemui.R;
+import com.android.systemui.recents.TriangleShape;
+
+/**
+ * The tooltip view shows the information about the operation of the anchor view {@link MenuView}
+ * . It's just shown on the left or right of the anchor view.
+ */
+@SuppressLint("ViewConstructor")
+class MenuEduTooltipView extends FrameLayout {
+    private int mFontSize;
+    private int mTextViewMargin;
+    private int mTextViewPadding;
+    private int mTextViewCornerRadius;
+    private int mArrowMargin;
+    private int mArrowWidth;
+    private int mArrowHeight;
+    private int mArrowCornerRadius;
+    private int mColorAccentPrimary;
+    private View mArrowLeftView;
+    private View mArrowRightView;
+    private TextView mMessageView;
+    private final MenuViewAppearance mMenuViewAppearance;
+
+    MenuEduTooltipView(@NonNull Context context, MenuViewAppearance menuViewAppearance) {
+        super(context);
+
+        mMenuViewAppearance = menuViewAppearance;
+
+        updateResources();
+        initViews();
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        updateResources();
+        updateMessageView();
+        updateArrowView();
+
+        updateLocationAndVisibility();
+    }
+
+    void show(CharSequence message) {
+        mMessageView.setText(message);
+
+        updateLocationAndVisibility();
+    }
+
+    void updateLocationAndVisibility() {
+        final boolean isTooltipOnRightOfAnchor = mMenuViewAppearance.isMenuOnLeftSide();
+        updateArrowVisibilityWith(isTooltipOnRightOfAnchor);
+        updateLocationWith(getMenuBoundsInParent(), isTooltipOnRightOfAnchor);
+    }
+
+    /**
+     * Gets the bounds of the {@link MenuView}. Besides, its parent view {@link MenuViewLayer} is
+     * also the root view of the tooltip view.
+     *
+     * @return The menu bounds based on its parent view.
+     */
+    private Rect getMenuBoundsInParent() {
+        final Rect bounds = new Rect();
+        final PointF position = mMenuViewAppearance.getMenuPosition();
+
+        bounds.set((int) position.x, (int) position.y,
+                (int) position.x + mMenuViewAppearance.getMenuWidth(),
+                (int) position.y + mMenuViewAppearance.getMenuHeight());
+
+        return bounds;
+    }
+
+    private void updateResources() {
+        final Resources res = getResources();
+
+        mArrowWidth =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
+        mArrowHeight =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height);
+        mArrowMargin =
+                res.getDimensionPixelSize(
+                        R.dimen.accessibility_floating_tooltip_arrow_margin);
+        mArrowCornerRadius =
+                res.getDimensionPixelSize(
+                        R.dimen.accessibility_floating_tooltip_arrow_corner_radius);
+        mFontSize =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size);
+        mTextViewMargin =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin);
+        mTextViewPadding =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding);
+        mTextViewCornerRadius =
+                res.getDimensionPixelSize(
+                        R.dimen.accessibility_floating_tooltip_text_corner_radius);
+        mColorAccentPrimary = Utils.getColorAttrDefaultColor(getContext(),
+                com.android.internal.R.attr.colorAccentPrimary);
+    }
+
+    private void updateLocationWith(Rect anchorBoundsInParent, boolean isTooltipOnRightOfAnchor) {
+        final int widthSpec = MeasureSpec.makeMeasureSpec(
+                getAvailableTextViewWidth(isTooltipOnRightOfAnchor), AT_MOST);
+        final int heightSpec = MeasureSpec.makeMeasureSpec(/* size= */ 0, UNSPECIFIED);
+        mMessageView.measure(widthSpec, heightSpec);
+        final LinearLayout.LayoutParams textViewParams =
+                (LinearLayout.LayoutParams) mMessageView.getLayoutParams();
+        textViewParams.width = mMessageView.getMeasuredWidth();
+        mMessageView.setLayoutParams(textViewParams);
+
+        final int layoutWidth = mMessageView.getMeasuredWidth() + mArrowWidth + mArrowMargin;
+        setTranslationX(isTooltipOnRightOfAnchor
+                ? anchorBoundsInParent.right
+                : anchorBoundsInParent.left - layoutWidth);
+
+        setTranslationY(anchorBoundsInParent.centerY() - (mMessageView.getMeasuredHeight() / 2.0f));
+    }
+
+    private void updateMessageView() {
+        mMessageView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
+        mMessageView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
+                mTextViewPadding);
+
+        final GradientDrawable gradientDrawable = (GradientDrawable) mMessageView.getBackground();
+        gradientDrawable.setCornerRadius(mTextViewCornerRadius);
+        gradientDrawable.setColor(mColorAccentPrimary);
+    }
+
+    private void updateArrowView() {
+        drawArrow(mArrowLeftView, /* isPointingLeft= */ true);
+        drawArrow(mArrowRightView, /* isPointingLeft= */ false);
+    }
+
+    private void updateArrowVisibilityWith(boolean isTooltipOnRightOfAnchor) {
+        if (isTooltipOnRightOfAnchor) {
+            mArrowLeftView.setVisibility(VISIBLE);
+            mArrowRightView.setVisibility(GONE);
+        } else {
+            mArrowLeftView.setVisibility(GONE);
+            mArrowRightView.setVisibility(VISIBLE);
+        }
+    }
+
+    private void drawArrow(View arrowView, boolean isPointingLeft) {
+        final TriangleShape triangleShape =
+                TriangleShape.createHorizontal(mArrowWidth, mArrowHeight, isPointingLeft);
+        final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
+        final Paint arrowPaint = arrowDrawable.getPaint();
+        arrowPaint.setColor(mColorAccentPrimary);
+
+        final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
+        arrowPaint.setPathEffect(effect);
+
+        arrowView.setBackground(arrowDrawable);
+    }
+
+    private void initViews() {
+        final View contentView = LayoutInflater.from(getContext()).inflate(
+                R.layout.accessibility_floating_menu_tooltip, /* root= */ this, /* attachToRoot= */
+                false);
+
+        mMessageView = contentView.findViewById(R.id.text);
+        mMessageView.setMovementMethod(LinkMovementMethod.getInstance());
+
+        mArrowLeftView = contentView.findViewById(R.id.arrow_left);
+        mArrowRightView = contentView.findViewById(R.id.arrow_right);
+
+        updateMessageView();
+        updateArrowView();
+
+        addView(contentView);
+    }
+
+    private int getAvailableTextViewWidth(boolean isOnRightOfAnchor) {
+        final PointF position = mMenuViewAppearance.getMenuPosition();
+        final int availableWidth = isOnRightOfAnchor
+                ? mMenuViewAppearance.getMenuDraggableBounds().width() - (int) position.x
+                : (int) position.x;
+
+        return availableWidth - mArrowWidth - mArrowMargin - mTextViewMargin;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
index 5bc7406..05e1d3f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java
@@ -17,6 +17,7 @@
 package com.android.systemui.accessibility.floatingmenu;
 
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
+import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
 import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
 import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
@@ -28,6 +29,7 @@
 import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.database.ContentObserver;
 import android.os.Handler;
@@ -40,6 +42,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Prefs;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 
 /**
@@ -52,12 +56,24 @@
     @FloatRange(from = 0.0, to = 1.0)
     private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
     private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
+    private static final boolean DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE = false;
+    private static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
 
     private final Context mContext;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final OnSettingsContentsChanged mSettingsContentsCallback;
     private Position mPercentagePosition;
 
+    @IntDef({
+            MigrationPrompt.DISABLED,
+            MigrationPrompt.ENABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface MigrationPrompt {
+        int DISABLED = 0;
+        int ENABLED = 1;
+    }
+
     private final ContentObserver mMenuTargetFeaturesContentObserver =
             new ContentObserver(mHandler) {
                 @Override
@@ -99,6 +115,19 @@
                         DEFAULT_MOVE_TO_TUCKED_VALUE));
     }
 
+    void loadDockTooltipVisibility(OnInfoReady<Boolean> callback) {
+        callback.onReady(Prefs.getBoolean(mContext,
+                Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
+                DEFAULT_HAS_SEEN_DOCK_TOOLTIP_VALUE));
+    }
+
+    void loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback) {
+        callback.onReady(Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+                DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT, UserHandle.USER_CURRENT)
+                == MigrationPrompt.ENABLED);
+    }
+
     void loadMenuPosition(OnInfoReady<Position> callback) {
         callback.onReady(mPercentagePosition);
     }
@@ -131,6 +160,18 @@
                 percentagePosition.toString());
     }
 
+    void updateDockTooltipVisibility(boolean hasSeen) {
+        Prefs.putBoolean(mContext, Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
+                hasSeen);
+    }
+
+    void updateMigrationTooltipVisibility(boolean visible) {
+        Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
+                visible ? MigrationPrompt.ENABLED : MigrationPrompt.DISABLED,
+                UserHandle.USER_CURRENT);
+    }
+
     private Position getStartPosition() {
         final String absolutePositionString = Prefs.getString(mContext,
                 Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
index bc3cf0a..8a31142 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java
@@ -25,6 +25,8 @@
 import androidx.annotation.NonNull;
 import androidx.recyclerview.widget.RecyclerView;
 
+import java.util.Optional;
+
 /**
  * Controls the all touch events of the accessibility target features view{@link RecyclerView} in
  * the {@link MenuView}. And then compute the gestures' velocity for fling and spring
@@ -39,6 +41,7 @@
     private boolean mIsDragging = false;
     private float mTouchSlop;
     private final DismissAnimationController mDismissAnimationController;
+    private Optional<Runnable> mOnActionDownEnd = Optional.empty();
 
     MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
             DismissAnimationController dismissAnimationController) {
@@ -65,6 +68,8 @@
 
                 mMenuAnimationController.cancelAnimations();
                 mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
+
+                mOnActionDownEnd.ifPresent(Runnable::run);
                 break;
             case MotionEvent.ACTION_MOVE:
                 if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
@@ -125,6 +130,10 @@
         // Do nothing
     }
 
+    void setOnActionDownEndListener(Runnable onActionDownEndListener) {
+        mOnActionDownEnd = Optional.ofNullable(onActionDownEndListener);
+    }
+
     /**
      * Adds a movement to the velocity tracker using raw screen coordinates.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index a7cdeab..3cd250f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -293,7 +293,7 @@
         return bounds;
     }
 
-    private boolean isMenuOnLeftSide() {
+    boolean isMenuOnLeftSide() {
         return mPercentagePosition.getPercentageX() < 0.5f;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index c42943c..6f5b39c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -20,6 +20,7 @@
 
 import static androidx.core.view.WindowInsetsCompat.Type;
 
+import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
 import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
 import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
@@ -27,8 +28,10 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.IntDef;
+import android.annotation.StringDef;
 import android.annotation.SuppressLint;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -37,6 +40,8 @@
 import android.provider.Settings;
 import android.util.PluralsMessageFormatter;
 import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
@@ -45,6 +50,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.lifecycle.Observer;
 
 import com.android.internal.accessibility.dialog.AccessibilityTarget;
 import com.android.internal.annotations.VisibleForTesting;
@@ -58,6 +64,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 /**
  * The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
@@ -66,11 +73,13 @@
  * message view would be shown and allowed users to undo it.
  */
 @SuppressLint("ViewConstructor")
-class MenuViewLayer extends FrameLayout {
+class MenuViewLayer extends FrameLayout implements
+        ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener {
     private static final int SHOW_MESSAGE_DELAY_MS = 3000;
 
     private final WindowManager mWindowManager;
     private final MenuView mMenuView;
+    private final MenuListViewTouchHandler mMenuListViewTouchHandler;
     private final MenuMessageView mMessageView;
     private final DismissView mDismissView;
     private final MenuViewAppearance mMenuViewAppearance;
@@ -79,18 +88,38 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final IAccessibilityFloatingMenu mFloatingMenu;
     private final DismissAnimationController mDismissAnimationController;
+    private final MenuViewModel mMenuViewModel;
+    private final Observer<Boolean> mDockTooltipObserver =
+            this::onDockTooltipVisibilityChanged;
+    private final Observer<Boolean> mMigrationTooltipObserver =
+            this::onMigrationTooltipVisibilityChanged;
     private final Rect mImeInsetsRect = new Rect();
+    private boolean mIsMigrationTooltipShowing;
+    private boolean mShouldShowDockTooltip;
+    private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
 
     @IntDef({
             LayerIndex.MENU_VIEW,
             LayerIndex.DISMISS_VIEW,
             LayerIndex.MESSAGE_VIEW,
+            LayerIndex.TOOLTIP_VIEW,
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface LayerIndex {
         int MENU_VIEW = 0;
         int DISMISS_VIEW = 1;
         int MESSAGE_VIEW = 2;
+        int TOOLTIP_VIEW = 3;
+    }
+
+    @StringDef({
+            TooltipType.MIGRATION,
+            TooltipType.DOCK,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface TooltipType {
+        String MIGRATION = "migration";
+        String DOCK = "dock";
     }
 
     @VisibleForTesting
@@ -125,12 +154,12 @@
         mAccessibilityManager = accessibilityManager;
         mFloatingMenu = floatingMenu;
 
-        final MenuViewModel menuViewModel = new MenuViewModel(context);
+        mMenuViewModel = new MenuViewModel(context);
         mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
-        mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance);
+        mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
         mMenuAnimationController = mMenuView.getMenuAnimationController();
         mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
-
+        mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
         mDismissView = new DismissView(context);
         mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
         mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@@ -153,9 +182,9 @@
             }
         });
 
-        final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
-                mMenuAnimationController, mDismissAnimationController);
-        mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
+        mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
+                mDismissAnimationController);
+        mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
 
         mMessageView = new MenuMessageView(context);
 
@@ -210,7 +239,12 @@
         super.onAttachedToWindow();
 
         mMenuView.show();
+        setOnClickListener(this);
         setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
+        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
+        mMenuViewModel.getDockTooltipVisibilityData().observeForever(mDockTooltipObserver);
+        mMenuViewModel.getMigrationTooltipVisibilityData().observeForever(
+                mMigrationTooltipObserver);
         mMessageView.setUndoListener(view -> undo());
         mContext.registerComponentCallbacks(mDismissAnimationController);
     }
@@ -220,11 +254,32 @@
         super.onDetachedFromWindow();
 
         mMenuView.hide();
+        setOnClickListener(null);
         setOnApplyWindowInsetsListener(null);
+        getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
+        mMenuViewModel.getDockTooltipVisibilityData().removeObserver(mDockTooltipObserver);
+        mMenuViewModel.getMigrationTooltipVisibilityData().removeObserver(
+                mMigrationTooltipObserver);
         mHandler.removeCallbacksAndMessages(/* token= */ null);
         mContext.unregisterComponentCallbacks(mDismissAnimationController);
     }
 
+    @Override
+    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+        inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+
+        if (mEduTooltipView.isPresent()) {
+            final int x = (int) getX();
+            final int y = (int) getY();
+            inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
+        }
+    }
+
+    @Override
+    public void onClick(View v) {
+        mEduTooltipView.ifPresent(this::removeTooltip);
+    }
+
     private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
         final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
         final WindowInsets windowInsets = windowMetrics.getWindowInsets();
@@ -249,6 +304,78 @@
         return insets;
     }
 
+    private void onMigrationTooltipVisibilityChanged(boolean visible) {
+        mIsMigrationTooltipShowing = visible;
+
+        if (mIsMigrationTooltipShowing) {
+            mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
+            mEduTooltipView.ifPresent(
+                    view -> addTooltipView(view, getMigrationMessage(), TooltipType.MIGRATION));
+        }
+    }
+
+    private void onDockTooltipVisibilityChanged(boolean hasSeenTooltip) {
+        mShouldShowDockTooltip = !hasSeenTooltip;
+    }
+
+    private void onSpringAnimationsEndAction() {
+        if (mShouldShowDockTooltip) {
+            mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
+            mEduTooltipView.ifPresent(view -> addTooltipView(view,
+                    getContext().getText(R.string.accessibility_floating_button_docking_tooltip),
+                    TooltipType.DOCK));
+
+            mMenuAnimationController.startTuckedAnimationPreview();
+        }
+    }
+
+    private CharSequence getMigrationMessage() {
+        final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(Intent.EXTRA_COMPONENT_NAME,
+                ACCESSIBILITY_BUTTON_COMPONENT_NAME.flattenToShortString());
+
+        final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo(
+                AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION,
+                v -> {
+                    getContext().startActivity(intent);
+                    mEduTooltipView.ifPresent(this::removeTooltip);
+                });
+
+        final int textResId = R.string.accessibility_floating_button_migration_tooltip;
+
+        return AnnotationLinkSpan.linkify(getContext().getText(textResId), linkInfo);
+    }
+
+    private void addTooltipView(MenuEduTooltipView tooltipView, CharSequence message,
+            CharSequence tag) {
+        addView(tooltipView, LayerIndex.TOOLTIP_VIEW);
+
+        tooltipView.show(message);
+        tooltipView.setTag(tag);
+
+        mMenuListViewTouchHandler.setOnActionDownEndListener(
+                () -> mEduTooltipView.ifPresent(this::removeTooltip));
+    }
+
+    private void removeTooltip(View tooltipView) {
+        if (tooltipView.getTag().equals(TooltipType.MIGRATION)) {
+            mMenuViewModel.updateMigrationTooltipVisibility(/* visible= */ false);
+            mIsMigrationTooltipShowing = false;
+        }
+
+        if (tooltipView.getTag().equals(TooltipType.DOCK)) {
+            mMenuViewModel.updateDockTooltipVisibility(/* hasSeen= */ true);
+            mMenuView.clearAnimation();
+            mShouldShowDockTooltip = false;
+        }
+
+        removeView(tooltipView);
+
+        mMenuListViewTouchHandler.setOnActionDownEndListener(null);
+        mEduTooltipView = Optional.empty();
+    }
+
     private void hideMenuAndShowMessage() {
         final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
                 SHOW_MESSAGE_DELAY_MS,
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
index bd41787..5fea3b0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java
@@ -36,6 +36,8 @@
     private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
             new MutableLiveData<>();
     private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
+    private final MutableLiveData<Boolean> mDockTooltipData = new MutableLiveData<>();
+    private final MutableLiveData<Boolean> mMigrationTooltipData = new MutableLiveData<>();
     private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
     private final MenuInfoRepository mInfoRepository;
 
@@ -66,11 +68,29 @@
         mInfoRepository.updateMenuSavingPosition(percentagePosition);
     }
 
+    void updateDockTooltipVisibility(boolean hasSeen) {
+        mInfoRepository.updateDockTooltipVisibility(hasSeen);
+    }
+
+    void updateMigrationTooltipVisibility(boolean visible) {
+        mInfoRepository.updateMigrationTooltipVisibility(visible);
+    }
+
     LiveData<Boolean> getMoveToTuckedData() {
         mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue);
         return mMoveToTuckedData;
     }
 
+    LiveData<Boolean> getDockTooltipVisibilityData() {
+        mInfoRepository.loadDockTooltipVisibility(mDockTooltipData::setValue);
+        return mDockTooltipData;
+    }
+
+    LiveData<Boolean> getMigrationTooltipVisibilityData() {
+        mInfoRepository.loadMigrationTooltipVisibility(mMigrationTooltipData::setValue);
+        return mMigrationTooltipData;
+    }
+
     LiveData<Position> getPercentagePositionData() {
         mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
         return mPercentagePositionData;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 19b0548..7fd4d6a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -97,6 +97,7 @@
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 import kotlin.Unit;
 
@@ -747,7 +748,7 @@
             @NonNull SystemUIDialogManager dialogManager,
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
-            @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
+            @NonNull Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider,
             @NonNull @BiometricsBackground Executor biometricsExecutor,
             @NonNull PrimaryBouncerInteractor primaryBouncerInteractor,
             @NonNull SinglePointerTouchProcessor singlePointerTouchProcessor) {
@@ -779,7 +780,7 @@
         mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
-        mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+        mAlternateTouchProvider = alternateTouchProvider.map(Provider::get).orElse(null);
         mSensorProps = new FingerprintSensorPropertiesInternal(
                 -1 /* sensorId */,
                 SensorProperties.STRENGTH_CONVENIENCE,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 142642a..802b9b6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -42,6 +42,7 @@
 import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlin.math.cos
 import kotlin.math.pow
 import kotlin.math.sin
@@ -64,7 +65,7 @@
     private val fingerprintManager: FingerprintManager?,
     private val handler: Handler,
     private val biometricExecutor: Executor,
-    private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
+    private val alternateTouchProvider: Optional<Provider<AlternateUdfpsTouchProvider>>,
     @Main private val fgExecutor: DelayableExecutor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val authController: AuthController,
@@ -126,6 +127,7 @@
                     if (!processedMotionEvent && goodOverlap) {
                         biometricExecutor.execute {
                             alternateTouchProvider
+                                .map(Provider<AlternateUdfpsTouchProvider>::get)
                                 .get()
                                 .onPointerDown(
                                     requestId,
@@ -142,7 +144,10 @@
 
                             view.configureDisplay {
                                 biometricExecutor.execute {
-                                    alternateTouchProvider.get().onUiReady()
+                                    alternateTouchProvider
+                                        .map(Provider<AlternateUdfpsTouchProvider>::get)
+                                        .get()
+                                        .onUiReady()
                                 }
                             }
 
@@ -158,7 +163,10 @@
             MotionEvent.ACTION_CANCEL -> {
                 if (processedMotionEvent && alternateTouchProvider.isPresent) {
                     biometricExecutor.execute {
-                        alternateTouchProvider.get().onPointerUp(requestId)
+                        alternateTouchProvider
+                            .map(Provider<AlternateUdfpsTouchProvider>::get)
+                            .get()
+                            .onPointerUp(requestId)
                     }
                     fgExecutor.execute {
                         if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
@@ -241,7 +249,10 @@
             if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
                 if (processedMotionEvent) {
                     biometricExecutor.execute {
-                        alternateTouchProvider.get().onPointerUp(requestId)
+                        alternateTouchProvider
+                            .map(Provider<AlternateUdfpsTouchProvider>::get)
+                            .get()
+                            .onPointerUp(requestId)
                     }
                     fgExecutor.execute {
                         if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index fb37def..63c2065 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -301,7 +301,9 @@
         } else {
             mView.showDefaultTextPreview();
         }
-        maybeShowRemoteCopy(clipData);
+        if (!isRemote) {
+            maybeShowRemoteCopy(clipData);
+        }
         animateIn();
         mView.announceForAccessibility(accessibilityAnnouncement);
         if (isRemote) {
diff --git a/core/java/android/view/InsetsVisibilities.aidl b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
similarity index 61%
copy from core/java/android/view/InsetsVisibilities.aidl
copy to packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
index bd573ea..5dabbbb 100644
--- a/core/java/android/view/InsetsVisibilities.aidl
+++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt
@@ -1,11 +1,11 @@
-/**
- * Copyright (c) 2021, The Android Open Source Project
+/*
+ * 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
+ *      http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package android.view;
+package com.android.systemui.common.shared.model
 
-parcelable InsetsVisibilities;
+import androidx.annotation.AttrRes
+
+/** Models an icon with a specific tint. */
+data class TintedIcon(
+    val icon: Icon,
+    @AttrRes val tintAttr: Int?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
new file mode 100644
index 0000000..dea8cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.common.ui.binder
+
+import android.widget.ImageView
+import com.android.settingslib.Utils
+import com.android.systemui.common.shared.model.TintedIcon
+
+object TintedIconViewBinder {
+    /**
+     * Binds the given tinted icon to the view.
+     *
+     * [TintedIcon.tintAttr] will always be applied, meaning that if it is null, then the tint
+     * *will* be reset to null.
+     */
+    fun bind(
+        tintedIcon: TintedIcon,
+        view: ImageView,
+    ) {
+        IconViewBinder.bind(tintedIcon.icon, view)
+        view.imageTintList =
+            if (tintedIcon.tintAttr != null) {
+                Utils.getColorAttr(view.context, tintedIcon.tintAttr)
+            } else {
+                null
+            }
+    }
+}
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 77d0496e4..27466d4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -19,7 +19,7 @@
 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.ControlsSettingsRepository
+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
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 9ae605e..6d6410d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -20,8 +20,8 @@
 import android.content.pm.PackageManager
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsMetricsLoggerImpl
-import com.android.systemui.controls.ControlsSettingsRepository
-import com.android.systemui.controls.ControlsSettingsRepositoryImpl
+import com.android.systemui.controls.settings.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsRepositoryImpl
 import com.android.systemui.controls.controller.ControlsBindingController
 import com.android.systemui.controls.controller.ControlsBindingControllerImpl
 import com.android.systemui.controls.controller.ControlsController
@@ -34,6 +34,8 @@
 import com.android.systemui.controls.management.ControlsListingControllerImpl
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.management.ControlsRequestDialog
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
 import com.android.systemui.controls.ui.ControlActionCoordinator
 import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
 import com.android.systemui.controls.ui.ControlsActivity
@@ -90,6 +92,11 @@
     ): ControlsSettingsRepository
 
     @Binds
+    abstract fun provideDialogManager(
+            manager: ControlsSettingsDialogManagerImpl
+    ): ControlsSettingsDialogManager
+
+    @Binds
     abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
new file mode 100644
index 0000000..bb2e2d7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsDialogManager.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.controls.settings
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.Context.MODE_PRIVATE
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.provider.Settings
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.R
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.settings.SecureSettings
+import javax.inject.Inject
+
+/**
+ * Manager to display a dialog to prompt user to enable controls related Settings:
+ *
+ * * [Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]
+ * * [Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS]
+ */
+interface ControlsSettingsDialogManager {
+
+    /**
+     * Shows the corresponding dialog. In order for a dialog to appear, the following must be true
+     *
+     * * At least one of the Settings in [ControlsSettingsRepository] are `false`.
+     * * The dialog has not been seen by the user too many times (as defined by
+     * [MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG]).
+     *
+     * When the dialogs are shown, the following outcomes are possible:
+     * * User cancels the dialog by clicking outside or going back: we register that the dialog was
+     * seen but the settings don't change.
+     * * User responds negatively to the dialog: we register that the user doesn't want to change
+     * the settings (dialog will not appear again) and the settings don't change.
+     * * User responds positively to the dialog: the settings are set to `true` and the dialog will
+     * not appear again.
+     * * SystemUI closes the dialogs (for example, the activity showing it is closed). In this case,
+     * we don't modify anything.
+     *
+     * Of those four scenarios, only the first three will cause [onAttemptCompleted] to be called.
+     * It will also be called if the dialogs are not shown.
+     */
+    fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit)
+
+    /**
+     * Closes the dialog without registering anything from the user. The state of the settings after
+     * this is called will be the same as before the dialogs were shown.
+     */
+    fun closeDialog()
+
+    companion object {
+        @VisibleForTesting internal const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+        @VisibleForTesting
+        internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
+    }
+}
+
+@SysUISingleton
+class ControlsSettingsDialogManagerImpl
+@VisibleForTesting
+internal constructor(
+    private val secureSettings: SecureSettings,
+    private val userFileManager: UserFileManager,
+    private val controlsSettingsRepository: ControlsSettingsRepository,
+    private val userTracker: UserTracker,
+    private val activityStarter: ActivityStarter,
+    private val dialogProvider: (context: Context, theme: Int) -> AlertDialog
+) : ControlsSettingsDialogManager {
+
+    @Inject
+    constructor(
+        secureSettings: SecureSettings,
+        userFileManager: UserFileManager,
+        controlsSettingsRepository: ControlsSettingsRepository,
+        userTracker: UserTracker,
+        activityStarter: ActivityStarter
+    ) : this(
+        secureSettings,
+        userFileManager,
+        controlsSettingsRepository,
+        userTracker,
+        activityStarter,
+        { context, theme -> SettingsDialog(context, theme) }
+    )
+
+    private var dialog: AlertDialog? = null
+        private set
+
+    private val showDeviceControlsInLockscreen: Boolean
+        get() = controlsSettingsRepository.canShowControlsInLockscreen.value
+
+    private val allowTrivialControls: Boolean
+        get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
+
+    override fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit) {
+        closeDialog()
+
+        val prefs =
+            userFileManager.getSharedPreferences(
+                DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                MODE_PRIVATE,
+                userTracker.userId
+            )
+        val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
+        if (
+            attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
+                (showDeviceControlsInLockscreen && allowTrivialControls)
+        ) {
+            onAttemptCompleted()
+            return
+        }
+
+        val listener = DialogListener(prefs, attempts, onAttemptCompleted)
+        val d =
+            dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
+                setIcon(R.drawable.ic_warning)
+                setOnCancelListener(listener)
+                setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
+                setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
+                if (showDeviceControlsInLockscreen) {
+                    setTitle(R.string.controls_settings_trivial_controls_dialog_title)
+                    setMessage(R.string.controls_settings_trivial_controls_dialog_message)
+                } else {
+                    setTitle(R.string.controls_settings_show_controls_dialog_title)
+                    setMessage(R.string.controls_settings_show_controls_dialog_message)
+                }
+            }
+
+        SystemUIDialog.registerDismissListener(d) { dialog = null }
+        SystemUIDialog.setDialogSize(d)
+        SystemUIDialog.setShowForAllUsers(d, true)
+        dialog = d
+        d.show()
+    }
+
+    private fun turnOnSettingSecurely(settings: List<String>) {
+        val action =
+            ActivityStarter.OnDismissAction {
+                settings.forEach { setting ->
+                    secureSettings.putIntForUser(setting, 1, userTracker.userId)
+                }
+                true
+            }
+        activityStarter.dismissKeyguardThenExecute(
+            action,
+            /* cancel */ null,
+            /* afterKeyguardGone */ true
+        )
+    }
+
+    override fun closeDialog() {
+        dialog?.dismiss()
+    }
+
+    private inner class DialogListener(
+        private val prefs: SharedPreferences,
+        private val attempts: Int,
+        private val onComplete: () -> Unit
+    ) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+        override fun onClick(dialog: DialogInterface?, which: Int) {
+            if (dialog == null) return
+            if (which == DialogInterface.BUTTON_POSITIVE) {
+                val settings = mutableListOf(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
+                if (!showDeviceControlsInLockscreen) {
+                    settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
+                }
+                turnOnSettingSecurely(settings)
+            }
+            if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+                prefs
+                    .edit()
+                    .putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+                    .apply()
+            }
+            onComplete()
+        }
+
+        override fun onCancel(dialog: DialogInterface?) {
+            if (dialog == null) return
+            if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
+                prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1).apply()
+            }
+            onComplete()
+        }
+    }
+
+    private fun AlertDialog.setNeutralButton(
+        msgId: Int,
+        listener: DialogInterface.OnClickListener
+    ) {
+        setButton(DialogInterface.BUTTON_NEUTRAL, context.getText(msgId), listener)
+    }
+
+    private fun AlertDialog.setPositiveButton(
+        msgId: Int,
+        listener: DialogInterface.OnClickListener
+    ) {
+        setButton(DialogInterface.BUTTON_POSITIVE, context.getText(msgId), listener)
+    }
+
+    private fun AlertDialog.setMessage(msgId: Int) {
+        setMessage(context.getText(msgId))
+    }
+
+    /** This is necessary because the constructors are `protected`. */
+    private class SettingsDialog(context: Context, theme: Int) : AlertDialog(context, theme)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
similarity index 95%
rename from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
rename to packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
index 3d10ab9..df2831c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
 
 import kotlinx.coroutines.flow.StateFlow
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
rename to packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
index 9dc422a..8e3b510 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ControlsSettingsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImpl.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
 
 import android.provider.Settings
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
index 041ed1d..99a10a3 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt
@@ -19,15 +19,12 @@
 import android.annotation.AnyThread
 import android.annotation.MainThread
 import android.app.Activity
-import android.app.AlertDialog
 import android.app.Dialog
 import android.app.PendingIntent
 import android.content.Context
 import android.content.pm.PackageManager
 import android.content.pm.ResolveInfo
-import android.os.UserHandle
 import android.os.VibrationEffect
-import android.provider.Settings.Secure
 import android.service.controls.Control
 import android.service.controls.actions.BooleanAction
 import android.service.controls.actions.CommandAction
@@ -35,39 +32,36 @@
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import com.android.internal.annotations.VisibleForTesting
-import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.ControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.phone.SystemUIDialog
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.TaskViewFactory
 import java.util.Optional
 import javax.inject.Inject
 
 @SysUISingleton
 class ControlActionCoordinatorImpl @Inject constructor(
-    private val context: Context,
-    private val bgExecutor: DelayableExecutor,
-    @Main private val uiExecutor: DelayableExecutor,
-    private val activityStarter: ActivityStarter,
-    private val broadcastSender: BroadcastSender,
-    private val keyguardStateController: KeyguardStateController,
-    private val taskViewFactory: Optional<TaskViewFactory>,
-    private val controlsMetricsLogger: ControlsMetricsLogger,
-    private val vibrator: VibratorHelper,
-    private val secureSettings: SecureSettings,
-    private val userContextProvider: UserContextProvider,
-    private val controlsSettingsRepository: ControlsSettingsRepository,
+        private val context: Context,
+        private val bgExecutor: DelayableExecutor,
+        @Main private val uiExecutor: DelayableExecutor,
+        private val activityStarter: ActivityStarter,
+        private val broadcastSender: BroadcastSender,
+        private val keyguardStateController: KeyguardStateController,
+        private val taskViewFactory: Optional<TaskViewFactory>,
+        private val controlsMetricsLogger: ControlsMetricsLogger,
+        private val vibrator: VibratorHelper,
+        private val controlsSettingsRepository: ControlsSettingsRepository,
+        private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+        private val featureFlags: FeatureFlags,
 ) : ControlActionCoordinator {
     private var dialog: Dialog? = null
     private var pendingAction: Action? = null
@@ -76,16 +70,16 @@
         get() = !keyguardStateController.isUnlocked()
     private val allowTrivialControls: Boolean
         get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
-    private val showDeviceControlsInLockscreen: Boolean
-        get() = controlsSettingsRepository.canShowControlsInLockscreen.value
     override lateinit var activityContext: Context
 
     companion object {
         private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
-        private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
     }
 
     override fun closeDialogs() {
+        if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+            controlsSettingsDialogManager.closeDialog()
+        }
         val isActivityFinishing =
             (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
         if (isActivityFinishing == true) {
@@ -253,71 +247,9 @@
         if (action.authIsRequired) {
             return
         }
-        val prefs = userContextProvider.userContext.getSharedPreferences(
-                PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
-        val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
-        if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
-                (showDeviceControlsInLockscreen && allowTrivialControls)) {
-            return
+        if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
+            controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
         }
-        val builder = AlertDialog
-                .Builder(activityContext, R.style.Theme_SystemUI_Dialog)
-                .setIcon(R.drawable.ic_warning)
-                .setOnCancelListener {
-                    if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
-                        prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1)
-                                .commit()
-                    }
-                    true
-                }
-                .setNeutralButton(R.string.controls_settings_dialog_neutral_button) { _, _ ->
-                    if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
-                        prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
-                                MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
-                                .commit()
-                    }
-                    true
-                }
-
-        if (showDeviceControlsInLockscreen) {
-            dialog = builder
-                    .setTitle(R.string.controls_settings_trivial_controls_dialog_title)
-                    .setMessage(R.string.controls_settings_trivial_controls_dialog_message)
-                    .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
-                        if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
-                            prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
-                                    MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
-                                    .commit()
-                        }
-                        secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 1,
-                                UserHandle.USER_CURRENT)
-                        true
-                    }
-                    .create()
-        } else {
-            dialog = builder
-                    .setTitle(R.string.controls_settings_show_controls_dialog_title)
-                    .setMessage(R.string.controls_settings_show_controls_dialog_message)
-                    .setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
-                        if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
-                            prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
-                                    MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
-                                    .commit()
-                        }
-                        secureSettings.putIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
-                                1, UserHandle.USER_CURRENT)
-                        secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
-                                1, UserHandle.USER_CURRENT)
-                        true
-                    }
-                    .create()
-        }
-
-        SystemUIDialog.registerDismissListener(dialog)
-        SystemUIDialog.setDialogSize(dialog)
-
-        dialog?.create()
-        dialog?.show()
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
index bd704c1..5d611c4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt
@@ -32,8 +32,10 @@
 import com.android.systemui.R
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.management.ControlsAnimations
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
 /**
@@ -47,7 +49,9 @@
     private val uiController: ControlsUiController,
     private val broadcastDispatcher: BroadcastDispatcher,
     private val dreamManager: IDreamManager,
-    private val featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlags,
+    private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
+    private val keyguardStateController: KeyguardStateController
 ) : ComponentActivity() {
 
     private lateinit var parent: ViewGroup
@@ -92,7 +96,13 @@
 
         parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
         parent.alpha = 0f
-        uiController.show(parent, { finishOrReturnToDream() }, this)
+        if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
+            controlsSettingsDialogManager.maybeShowDialog(this) {
+                uiController.show(parent, { finishOrReturnToDream() }, this)
+            }
+        } else {
+            uiController.show(parent, { finishOrReturnToDream() }, this)
+        }
 
         ControlsAnimations.enterAnimation(parent).start()
     }
@@ -124,6 +134,7 @@
         mExitToDream = false
 
         uiController.hide()
+        controlsSettingsDialogManager.closeDialog()
     }
 
     override fun onDestroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index 4c8e1ac..fb678aa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.service.controls.Control
+import android.service.controls.ControlsProviderService
 import android.util.Log
 import android.view.ContextThemeWrapper
 import android.view.LayoutInflater
@@ -48,6 +49,7 @@
 import com.android.systemui.R
 import com.android.systemui.controls.ControlsMetricsLogger
 import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
@@ -96,6 +98,7 @@
         private val userFileManager: UserFileManager,
         private val userTracker: UserTracker,
         private val taskViewFactory: Optional<TaskViewFactory>,
+        private val controlsSettingsRepository: ControlsSettingsRepository,
         dumpManager: DumpManager
 ) : ControlsUiController, Dumpable {
 
@@ -354,7 +357,6 @@
                 } else {
                     items[0]
                 }
-
         maybeUpdateSelectedItem(selectionItem)
 
         createControlsSpaceFrame()
@@ -374,11 +376,20 @@
     }
 
     private fun createPanelView(componentName: ComponentName) {
-        val pendingIntent = PendingIntent.getActivity(
+        val setting = controlsSettingsRepository
+                .allowActionOnTrivialControlsInLockscreen.value
+        val pendingIntent = PendingIntent.getActivityAsUser(
                 context,
                 0,
-                Intent().setComponent(componentName),
-                PendingIntent.FLAG_IMMUTABLE
+                Intent()
+                        .setComponent(componentName)
+                        .putExtra(
+                                ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                                setting
+                        ),
+                PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,
+                null,
+                userTracker.userHandle
         )
 
         parent.requireViewById<View>(R.id.controls_scroll_view).visibility = View.GONE
@@ -698,6 +709,8 @@
             println("hidden: $hidden")
             println("selectedItem: $selectedItem")
             println("lastSelections: $lastSelections")
+            println("setting: ${controlsSettingsRepository
+                    .allowActionOnTrivialControlsInLockscreen.value}")
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 81df4ed..267e036 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -87,7 +87,7 @@
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restart();
+                    mRestarter.restartSystemUI();
                 }
             };
 
@@ -327,9 +327,7 @@
             Log.i(TAG, "SystemUI Restart Suppressed");
             return;
         }
-        Log.i(TAG, "Restarting SystemUI");
-        // SysUI starts back when up exited. Is there a better way to do this?
-        System.exit(0);
+        mRestarter.restartSystemUI();
     }
 
     private void restartAndroid(boolean requestSuppress) {
@@ -337,7 +335,7 @@
             Log.i(TAG, "Android Restart Suppressed");
             return;
         }
-        mRestarter.restart();
+        mRestarter.restartAndroid();
     }
 
     void setBooleanFlagInternal(Flag<?> flag, boolean value) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
index 3d9f627..069e612 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
@@ -28,6 +28,8 @@
     private val systemExitRestarter: SystemExitRestarter,
 ) : Restarter {
 
+    private var androidRestartRequested = false
+
     val observer =
         object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
@@ -36,8 +38,18 @@
             }
         }
 
-    override fun restart() {
-        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting on next screen off.")
+    override fun restartSystemUI() {
+        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
+        scheduleRestart()
+    }
+
+    override fun restartAndroid() {
+        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
+        androidRestartRequested = true
+        scheduleRestart()
+    }
+
+    fun scheduleRestart() {
         if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
             restartNow()
         } else {
@@ -46,6 +58,10 @@
     }
 
     private fun restartNow() {
-        systemExitRestarter.restart()
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid()
+        } else {
+            systemExitRestarter.restartSystemUI()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 7189f00..b94d781 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -16,7 +16,9 @@
 
 package com.android.systemui.flags
 
+import android.content.Intent
 import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.commandline.CommandRegistry
 import dagger.Binds
@@ -31,7 +33,8 @@
     dumpManager: DumpManager,
     private val commandRegistry: CommandRegistry,
     private val flagCommand: FlagCommand,
-    private val featureFlags: FeatureFlagsDebug
+    private val featureFlags: FeatureFlagsDebug,
+    private val broadcastSender: BroadcastSender
 ) : CoreStartable {
 
     init {
@@ -43,6 +46,8 @@
     override fun start() {
         featureFlags.init()
         commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
+        val intent = Intent(FlagManager.ACTION_SYSUI_STARTED)
+        broadcastSender.sendBroadcast(intent)
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 3c83682..8bddacc 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -61,7 +61,7 @@
             new ServerFlagReader.ChangeListener() {
                 @Override
                 public void onChange() {
-                    mRestarter.restart();
+                    mRestarter.restartSystemUI();
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
index a3f0f66..7ff3876 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
@@ -34,35 +34,48 @@
     @Background private val bgExecutor: DelayableExecutor,
     private val systemExitRestarter: SystemExitRestarter
 ) : Restarter {
-    var shouldRestart = false
+    var listenersAdded = false
     var pendingRestart: Runnable? = null
+    var androidRestartRequested = false
 
     val observer =
         object : WakefulnessLifecycle.Observer {
             override fun onFinishedGoingToSleep() {
-                maybeScheduleRestart()
+                scheduleRestart()
             }
         }
 
     val batteryCallback =
         object : BatteryController.BatteryStateChangeCallback {
             override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                maybeScheduleRestart()
+                scheduleRestart()
             }
         }
 
-    override fun restart() {
-        Log.d(FeatureFlagsDebug.TAG, "Restart requested. Restarting when plugged in and idle.")
-        if (!shouldRestart) {
-            // Don't bother scheduling twice.
-            shouldRestart = true
-            wakefulnessLifecycle.addObserver(observer)
-            batteryController.addCallback(batteryCallback)
-            maybeScheduleRestart()
-        }
+    override fun restartSystemUI() {
+        Log.d(
+            FeatureFlagsDebug.TAG,
+            "SystemUI Restart requested. Restarting when plugged in and idle."
+        )
+        scheduleRestart()
     }
 
-    private fun maybeScheduleRestart() {
+    override fun restartAndroid() {
+        Log.d(
+            FeatureFlagsDebug.TAG,
+            "Android Restart requested. Restarting when plugged in and idle."
+        )
+        androidRestartRequested = true
+        scheduleRestart()
+    }
+
+    private fun scheduleRestart() {
+        // Don't bother adding listeners twice.
+        if (!listenersAdded) {
+            listenersAdded = true
+            wakefulnessLifecycle.addObserver(observer)
+            batteryController.addCallback(batteryCallback)
+        }
         if (
             wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
         ) {
@@ -77,6 +90,10 @@
 
     private fun restartNow() {
         Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
-        systemExitRestarter.restart()
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid()
+        } else {
+            systemExitRestarter.restartSystemUI()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 8019b56..ff3714f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -71,6 +71,9 @@
     val NOTIFICATION_MEMORY_MONITOR_ENABLED =
         releasedFlag(112, "notification_memory_monitor_enabled")
 
+    val NOTIFICATION_MEMORY_LOGGING_ENABLED =
+        unreleasedFlag(119, "notification_memory_logging_enabled", teamfood = true)
+
     // TODO(b/254512731): Tracking Bug
     @JvmField
     val NOTIFICATION_DISMISSAL_FADE =
@@ -83,8 +86,7 @@
     val SEMI_STABLE_SORT = unreleasedFlag(115, "semi_stable_sort", teamfood = true)
 
     @JvmField
-    val NOTIFICATION_GROUP_CORNER =
-        unreleasedFlag(116, "notification_group_corner", teamfood = true)
+    val USE_ROUNDNESS_SOURCETYPES = unreleasedFlag(116, "use_roundness_sourcetype", teamfood = true)
 
     // TODO(b/259217907)
     @JvmField
@@ -92,6 +94,7 @@
         unreleasedFlag(259217907, "notification_group_dismissal_animation", teamfood = true)
 
     // TODO(b/257506350): Tracking Bug
+    @JvmField
     val FSI_CHROME = unreleasedFlag(117, "fsi_chrome")
 
     @JvmField
@@ -99,7 +102,7 @@
         unreleasedFlag(259395680, "simplified_appear_fraction", teamfood = true)
 
     // TODO(b/257315550): Tracking Bug
-    val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when")
+    val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when", teamfood = true)
 
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
@@ -135,7 +138,8 @@
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
-    @JvmField val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation")
+    @JvmField
+    val STEP_CLOCK_ANIMATION = unreleasedFlag(212, "step_clock_animation", teamfood = true)
 
     /**
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
@@ -205,9 +209,6 @@
             "full_screen_user_switcher"
         )
 
-    // TODO(b/254512678): Tracking Bug
-    @JvmField val NEW_FOOTER_ACTIONS = releasedFlag(507, "new_footer_actions")
-
     // TODO(b/244064524): Tracking Bug
     @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
 
@@ -227,7 +228,9 @@
     val NEW_STATUS_BAR_WIFI_ICON_BACKEND = unreleasedFlag(609, "new_status_bar_wifi_icon_backend")
 
     // TODO(b/256623670): Tracking Bug
-    @JvmField val BATTERY_SHIELD_ICON = unreleasedFlag(610, "battery_shield_icon")
+    @JvmField
+    val BATTERY_SHIELD_ICON =
+        resourceBooleanFlag(610, R.bool.flag_battery_shield_icon, "battery_shield_icon")
 
     // TODO(b/260881289): Tracking Bug
     val NEW_STATUS_BAR_ICONS_DEBUG_COLORING =
@@ -434,6 +437,11 @@
     val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
         unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
 
+    // 2500 - output switcher
+    // TODO(b/261538825): Tracking Bug
+    @JvmField
+    val OUTPUT_SWITCHER_ADVANCED_LAYOUT = unreleasedFlag(2500, "output_switcher_advanced_layout")
+
     // TODO(b259590361): Tracking bug
     val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
index 8f095a2..ce8b821 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Restarter.kt
@@ -16,5 +16,7 @@
 package com.android.systemui.flags
 
 interface Restarter {
-    fun restart()
-}
\ No newline at end of file
+    fun restartSystemUI()
+
+    fun restartAndroid()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
index f1b1be4..89daa64 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/SystemExitRestarter.kt
@@ -16,10 +16,19 @@
 
 package com.android.systemui.flags
 
+import com.android.internal.statusbar.IStatusBarService
 import javax.inject.Inject
 
-class SystemExitRestarter @Inject constructor() : Restarter {
-    override fun restart() {
+class SystemExitRestarter
+@Inject
+constructor(
+    private val barService: IStatusBarService,
+) : Restarter {
+    override fun restartAndroid() {
+        barService.restart()
+    }
+
+    override fun restartSystemUI() {
         System.exit(0)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c4eac1c..c0d6cc9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -824,7 +824,11 @@
         surfaceBehindEntryAnimator.cancel()
         surfaceBehindAlpha = 1f
         setSurfaceBehindAppearAmount(1f)
-        launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+        try {
+            launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+        }  catch (e: RemoteException) {
+            Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+        }
 
         // That target is no longer valid since the animation finished, null it out.
         surfaceBehindRemoteAnimationTargets = null
diff --git a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
index c7e4c5e..b98a92f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/log/SessionTracker.java
@@ -49,7 +49,9 @@
 @SysUISingleton
 public class SessionTracker implements CoreStartable {
     private static final String TAG = "SessionTracker";
-    private static final boolean DEBUG = false;
+
+    // To enable logs: `adb shell setprop log.tag.SessionTracker DEBUG` & restart sysui
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     // At most 20 bits: ~1m possibilities, ~0.5% probability of collision in 100 values
     private final InstanceIdSequence mInstanceIdGenerator = new InstanceIdSequence(1 << 20);
@@ -81,8 +83,8 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mKeyguardStateController.addCallback(mKeyguardStateCallback);
 
-        mKeyguardSessionStarted = mKeyguardStateController.isShowing();
-        if (mKeyguardSessionStarted) {
+        if (mKeyguardStateController.isShowing()) {
+            mKeyguardSessionStarted = true;
             startSession(SESSION_KEYGUARD);
         }
     }
@@ -136,12 +138,11 @@
             new KeyguardUpdateMonitorCallback() {
         @Override
         public void onStartedGoingToSleep(int why) {
-            // we need to register to the KeyguardUpdateMonitor lifecycle b/c it gets called
-            // before the WakefulnessLifecycle
             if (mKeyguardSessionStarted) {
-                return;
+                endSession(SESSION_KEYGUARD);
             }
 
+            // Start a new session whenever the device goes to sleep
             mKeyguardSessionStarted = true;
             startSession(SESSION_KEYGUARD);
         }
@@ -154,6 +155,9 @@
             boolean wasSessionStarted = mKeyguardSessionStarted;
             boolean keyguardShowing = mKeyguardStateController.isShowing();
             if (keyguardShowing && !wasSessionStarted) {
+                // the keyguard can start showing without the device going to sleep (ie: lockdown
+                // from the power button), so we start a new keyguard session when the keyguard is
+                // newly shown in addition to when the device starts going to sleep
                 mKeyguardSessionStarted = true;
                 startSession(SESSION_KEYGUARD);
             } else if (!keyguardShowing && wasSessionStarted) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 3012bb4..2dd339d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -422,6 +422,7 @@
                     appUid = appUid
                 )
             mediaEntries.put(packageName, resumeData)
+            logSingleVsMultipleMediaAdded(appUid, packageName, instanceId)
             logger.logResumeMediaAdded(appUid, packageName, instanceId)
         }
         backgroundExecutor.execute {
@@ -812,6 +813,7 @@
         val appUid = appInfo?.uid ?: Process.INVALID_UID
 
         if (logEvent) {
+            logSingleVsMultipleMediaAdded(appUid, sbn.packageName, instanceId)
             logger.logActiveMediaAdded(appUid, sbn.packageName, instanceId, playbackLocation)
         } else if (playbackLocation != currentEntry?.playbackLocation) {
             logger.logPlaybackLocationChange(appUid, sbn.packageName, instanceId, playbackLocation)
@@ -855,6 +857,20 @@
         }
     }
 
+    private fun logSingleVsMultipleMediaAdded(
+        appUid: Int,
+        packageName: String,
+        instanceId: InstanceId
+    ) {
+        if (mediaEntries.size == 1) {
+            logger.logSingleMediaPlayerInCarousel(appUid, packageName, instanceId)
+        } else if (mediaEntries.size == 2) {
+            // Since this method is only called when there is a new media session added.
+            // logging needed once there is more than one media session in carousel.
+            logger.logMultipleMediaPlayersInCarousel(appUid, packageName, instanceId)
+        }
+    }
+
     private fun getAppInfoFromPackage(packageName: String): ApplicationInfo? {
         try {
             return context.packageManager.getApplicationInfo(packageName, 0)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 827ac78..df8fb91 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -940,19 +940,9 @@
         if (mIsSeekBarEnabled) {
             return ConstraintSet.VISIBLE;
         }
-        // If disabled and "neighbours" are visible, set progress bar to INVISIBLE instead of GONE
-        // so layout weights still work.
-        return areAnyExpandedBottomActionsVisible() ? ConstraintSet.INVISIBLE : ConstraintSet.GONE;
-    }
-
-    private boolean areAnyExpandedBottomActionsVisible() {
-        ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
-        for (int id : MediaViewHolder.Companion.getExpandedBottomActionIds()) {
-            if (expandedSet.getVisibility(id) == ConstraintSet.VISIBLE) {
-                return true;
-            }
-        }
-        return false;
+        // Set progress bar to INVISIBLE to keep the positions of text and buttons similar to the
+        // original positions when seekbar is enabled.
+        return ConstraintSet.INVISIBLE;
     }
 
     private void setGenericButton(
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
index 3ad8c21..ea943be 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaUiEventLogger.kt
@@ -213,6 +213,24 @@
             instanceId
         )
     }
+
+    fun logSingleMediaPlayerInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_CAROUSEL_SINGLE_PLAYER,
+            uid,
+            packageName,
+            instanceId
+        )
+    }
+
+    fun logMultipleMediaPlayersInCarousel(uid: Int, packageName: String, instanceId: InstanceId) {
+        logger.logWithInstanceId(
+            MediaUiEvent.MEDIA_CAROUSEL_MULTIPLE_PLAYERS,
+            uid,
+            packageName,
+            instanceId
+        )
+    }
 }
 
 enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -269,7 +287,11 @@
     @UiEvent(doc = "User tapped on a media recommendation card")
     MEDIA_RECOMMENDATION_CARD_TAP(1045),
     @UiEvent(doc = "User opened the broadcast dialog from a media control")
-    MEDIA_OPEN_BROADCAST_DIALOG(1079);
+    MEDIA_OPEN_BROADCAST_DIALOG(1079),
+    @UiEvent(doc = "The media carousel contains one media player card")
+    MEDIA_CAROUSEL_SINGLE_PLAYER(1244),
+    @UiEvent(doc = "The media carousel contains multiple media player cards")
+    MEDIA_CAROUSEL_MULTIPLE_PLAYERS(1245);
 
     override fun getId() = metricId
 }
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 ee59561..3dccae0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.media.dialog;
 
-import android.annotation.DrawableRes;
 import android.content.res.ColorStateList;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
@@ -122,17 +121,19 @@
                 // Set different layout for each device
                 if (device.isMutingExpectedDevice()
                         && !mController.isCurrentConnectedDeviceRemote()) {
-                    updateTitleIcon(R.drawable.media_output_icon_volume,
-                            mController.getColorItemContent());
+                    if (!mController.isAdvancedLayoutSupported()) {
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
+                    }
                     initMutingExpectedDevice();
                     mCurrentActivePosition = position;
-                    updateContainerClickListener(v -> onItemClick(v, device));
+                    updateFullItemClickListener(v -> onItemClick(v, device));
                     setSingleLineLayout(getItemTitle(device));
                 } else if (device.getState() == MediaDeviceState.STATE_CONNECTING_FAILED) {
                     setUpDeviceIcon(device);
                     updateConnectionFailedStatusIcon();
                     mSubTitleText.setText(R.string.media_output_dialog_connect_failed);
-                    updateContainerClickListener(v -> onItemClick(v, device));
+                    updateFullItemClickListener(v -> onItemClick(v, device));
                     setTwoLineLayout(device, false /* bFocused */, false /* showSeekBar */,
                             false /* showProgressBar */, true /* showSubtitle */,
                             true /* showStatus */);
@@ -146,8 +147,10 @@
                         && isDeviceIncluded(mController.getSelectedMediaDevice(), device)) {
                     boolean isDeviceDeselectable = isDeviceIncluded(
                             mController.getDeselectableMediaDevice(), device);
-                    updateTitleIcon(R.drawable.media_output_icon_volume,
-                            mController.getColorItemContent());
+                    if (!mController.isAdvancedLayoutSupported()) {
+                        updateTitleIcon(R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
+                    }
                     updateGroupableCheckBox(true, isDeviceDeselectable, device);
                     updateEndClickArea(device, isDeviceDeselectable);
                     setUpContentDescriptionForView(mContainerLayout, false, device);
@@ -161,8 +164,22 @@
                             && !mController.isCurrentConnectedDeviceRemote()) {
                         // mark as disconnected and set special click listener
                         setUpDeviceIcon(device);
-                        updateContainerClickListener(v -> cancelMuteAwaitConnection());
+                        updateFullItemClickListener(v -> cancelMuteAwaitConnection());
                         setSingleLineLayout(getItemTitle(device));
+                    } else if (mController.isCurrentConnectedDeviceRemote()
+                            && !mController.getSelectableMediaDevice().isEmpty()
+                            && mController.isAdvancedLayoutSupported()) {
+                        //If device is connected and there's other selectable devices, layout as
+                        // one of selected devices.
+                        boolean isDeviceDeselectable = isDeviceIncluded(
+                                mController.getDeselectableMediaDevice(), device);
+                        updateGroupableCheckBox(true, isDeviceDeselectable, device);
+                        updateEndClickArea(device, isDeviceDeselectable);
+                        setUpContentDescriptionForView(mContainerLayout, false, device);
+                        setSingleLineLayout(getItemTitle(device), true /* showSeekBar */,
+                                false /* showProgressBar */, true /* showCheckBox */,
+                                true /* showEndTouchArea */);
+                        initSeekbar(device, isCurrentSeekbarInvisible);
                     } else {
                         updateTitleIcon(R.drawable.media_output_icon_volume,
                                 mController.getColorItemContent());
@@ -176,14 +193,19 @@
                 } else if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
                     setUpDeviceIcon(device);
                     updateGroupableCheckBox(false, true, device);
-                    updateContainerClickListener(v -> onGroupActionTriggered(true, device));
+                    if (mController.isAdvancedLayoutSupported()) {
+                        updateEndClickArea(device, true);
+                    }
+                    updateFullItemClickListener(mController.isAdvancedLayoutSupported()
+                            ? v -> onItemClick(v, device)
+                            : v -> onGroupActionTriggered(true, device));
                     setSingleLineLayout(getItemTitle(device), false /* showSeekBar */,
                             false /* showProgressBar */, true /* showCheckBox */,
                             true /* showEndTouchArea */);
                 } else {
                     setUpDeviceIcon(device);
                     setSingleLineLayout(getItemTitle(device));
-                    updateContainerClickListener(v -> onItemClick(v, device));
+                    updateFullItemClickListener(v -> onItemClick(v, device));
                 }
             }
         }
@@ -214,6 +236,11 @@
                     isDeviceDeselectable ? (v) -> mCheckBox.performClick() : null);
             mEndTouchArea.setImportantForAccessibility(
                     View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+            if (mController.isAdvancedLayoutSupported()) {
+                mEndTouchArea.getBackground().setColorFilter(
+                        new PorterDuffColorFilter(mController.getColorItemBackground(),
+                                PorterDuff.Mode.SRC_IN));
+            }
             setUpContentDescriptionForView(mEndTouchArea, true, device);
         }
 
@@ -228,13 +255,9 @@
             setCheckBoxColor(mCheckBox, mController.getColorItemContent());
         }
 
-        private void updateTitleIcon(@DrawableRes int id, int color) {
-            mTitleIcon.setImageDrawable(mContext.getDrawable(id));
-            mTitleIcon.setColorFilter(color);
-        }
-
-        private void updateContainerClickListener(View.OnClickListener listener) {
+        private void updateFullItemClickListener(View.OnClickListener listener) {
             mContainerLayout.setOnClickListener(listener);
+            updateIconAreaClickListener(listener);
         }
 
         @Override
@@ -246,6 +269,11 @@
                 final Drawable addDrawable = mContext.getDrawable(R.drawable.ic_add);
                 mTitleIcon.setImageDrawable(addDrawable);
                 mTitleIcon.setColorFilter(mController.getColorItemContent());
+                if (mController.isAdvancedLayoutSupported()) {
+                    mIconAreaLayout.getBackground().setColorFilter(
+                            new PorterDuffColorFilter(mController.getColorItemBackground(),
+                                    PorterDuff.Mode.SRC_IN));
+                }
                 mContainerLayout.setOnClickListener(mController::launchBluetoothPairing);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index 3f7b226..db62e51 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -16,8 +16,11 @@
 
 package com.android.systemui.media.dialog;
 
+import static com.android.systemui.media.dialog.MediaOutputSeekbar.VOLUME_PERCENTAGE_SCALE_SIZE;
+
 import android.animation.Animator;
 import android.animation.ValueAnimator;
+import android.annotation.DrawableRes;
 import android.app.WallpaperColors;
 import android.content.Context;
 import android.graphics.PorterDuff;
@@ -80,8 +83,9 @@
     public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
             int viewType) {
         mContext = viewGroup.getContext();
-        mHolderView = LayoutInflater.from(mContext).inflate(R.layout.media_output_list_item,
-                viewGroup, false);
+        mHolderView = LayoutInflater.from(mContext).inflate(
+                mController.isAdvancedLayoutSupported() ? R.layout.media_output_list_item_advanced
+                        : R.layout.media_output_list_item, viewGroup, false);
 
         return null;
     }
@@ -129,18 +133,20 @@
 
         private static final int ANIM_DURATION = 500;
 
-        final LinearLayout mContainerLayout;
+        final ViewGroup mContainerLayout;
         final FrameLayout mItemLayout;
+        final FrameLayout mIconAreaLayout;
         final TextView mTitleText;
         final TextView mTwoLineTitleText;
         final TextView mSubTitleText;
+        final TextView mVolumeValueText;
         final ImageView mTitleIcon;
         final ProgressBar mProgressBar;
         final MediaOutputSeekbar mSeekBar;
         final LinearLayout mTwoLineLayout;
         final ImageView mStatusIcon;
         final CheckBox mCheckBox;
-        final LinearLayout mEndTouchArea;
+        final ViewGroup mEndTouchArea;
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
@@ -159,6 +165,13 @@
             mStatusIcon = view.requireViewById(R.id.media_output_item_status);
             mCheckBox = view.requireViewById(R.id.check_box);
             mEndTouchArea = view.requireViewById(R.id.end_action_area);
+            if (mController.isAdvancedLayoutSupported()) {
+                mVolumeValueText = view.requireViewById(R.id.volume_value);
+                mIconAreaLayout = view.requireViewById(R.id.icon_area);
+            } else {
+                mVolumeValueText = null;
+                mIconAreaLayout = null;
+            }
             initAnimator();
         }
 
@@ -170,9 +183,14 @@
             mEndTouchArea.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
             mContainerLayout.setOnClickListener(null);
             mContainerLayout.setContentDescription(null);
+            mTitleIcon.setOnClickListener(null);
             mTitleText.setTextColor(mController.getColorItemContent());
             mSubTitleText.setTextColor(mController.getColorItemContent());
             mTwoLineTitleText.setTextColor(mController.getColorItemContent());
+            if (mController.isAdvancedLayoutSupported()) {
+                mIconAreaLayout.setOnClickListener(null);
+                mVolumeValueText.setTextColor(mController.getColorItemContent());
+            }
             mSeekBar.getProgressDrawable().setColorFilter(
                     new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
                             PorterDuff.Mode.SRC_IN));
@@ -203,13 +221,28 @@
                                     .findDrawableByLayerId(android.R.id.progress);
                     final GradientDrawable progressDrawable =
                             (GradientDrawable) clipDrawable.getDrawable();
-                    progressDrawable.setCornerRadius(mController.getActiveRadius());
+                    if (mController.isAdvancedLayoutSupported()) {
+                        progressDrawable.setCornerRadii(
+                                new float[]{0, 0, mController.getActiveRadius(),
+                                        mController.getActiveRadius(),
+                                        mController.getActiveRadius(),
+                                        mController.getActiveRadius(), 0, 0});
+                    } else {
+                        progressDrawable.setCornerRadius(mController.getActiveRadius());
+                    }
                 }
             }
             mItemLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
                     isActive ? mController.getColorConnectedItemBackground()
                             : mController.getColorItemBackground(),
                     PorterDuff.Mode.SRC_IN));
+            if (mController.isAdvancedLayoutSupported()) {
+                mIconAreaLayout.getBackground().setColorFilter(new PorterDuffColorFilter(
+                        showSeekBar ? mController.getColorSeekbarProgress()
+                                : showProgressBar ? mController.getColorConnectedItemBackground()
+                                        : mController.getColorItemBackground(),
+                        PorterDuff.Mode.SRC_IN));
+            }
             mProgressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE);
             mSeekBar.setAlpha(1);
             mSeekBar.setVisibility(showSeekBar ? View.VISIBLE : View.GONE);
@@ -220,6 +253,13 @@
             mTitleText.setVisibility(View.VISIBLE);
             mCheckBox.setVisibility(showCheckBox ? View.VISIBLE : View.GONE);
             mEndTouchArea.setVisibility(showEndTouchArea ? View.VISIBLE : View.GONE);
+            if (mController.isAdvancedLayoutSupported()) {
+                ViewGroup.MarginLayoutParams params =
+                        (ViewGroup.MarginLayoutParams) mItemLayout.getLayoutParams();
+                params.rightMargin = showEndTouchArea ? mController.getItemMarginEndSelectable()
+                        : mController.getItemMarginEndDefault();
+            }
+            mTitleIcon.setColorFilter(mController.getColorItemContent());
         }
 
         void setTwoLineLayout(MediaDevice device, boolean bFocused, boolean showSeekBar,
@@ -263,42 +303,134 @@
             final int currentVolume = device.getCurrentVolume();
             if (mSeekBar.getVolume() != currentVolume) {
                 if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
+                    if (mController.isAdvancedLayoutSupported()) {
+                        updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
+                                        : R.drawable.media_output_icon_volume,
+                                mController.getColorItemContent());
+                    }
                     animateCornerAndVolume(mSeekBar.getProgress(),
                             MediaOutputSeekbar.scaleVolumeToProgress(currentVolume));
                 } else {
                     if (!mVolumeAnimator.isStarted()) {
+                        if (mController.isAdvancedLayoutSupported()) {
+                            int percentage =
+                                    (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+                                            / (double) mSeekBar.getMax());
+                            if (percentage == 0) {
+                                updateMutedVolumeIcon();
+                            } else {
+                                updateUnmutedVolumeIcon();
+                            }
+                        }
                         mSeekBar.setVolume(currentVolume);
                     }
                 }
+            } else if (mController.isAdvancedLayoutSupported() && currentVolume == 0) {
+                mSeekBar.resetVolume();
+                updateMutedVolumeIcon();
             }
             if (mIsInitVolumeFirstTime) {
                 mIsInitVolumeFirstTime = false;
             }
+            if (mController.isAdvancedLayoutSupported()) {
+                updateIconAreaClickListener((v) -> {
+                    mSeekBar.resetVolume();
+                    mController.adjustVolume(device, 0);
+                    updateMutedVolumeIcon();
+                });
+            }
             mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
                 @Override
                 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                     if (device == null || !fromUser) {
                         return;
                     }
-                    int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
+                    int progressToVolume = MediaOutputSeekbar.scaleProgressToVolume(progress);
                     int deviceVolume = device.getCurrentVolume();
-                    if (currentVolume != deviceVolume) {
-                        mController.adjustVolume(device, currentVolume);
+                    if (mController.isAdvancedLayoutSupported()) {
+                        int percentage =
+                                (int) ((double) progressToVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+                                        / (double) seekBar.getMax());
+                        mVolumeValueText.setText(mContext.getResources().getString(
+                                R.string.media_output_dialog_volume_percentage, percentage));
+                        mVolumeValueText.setVisibility(View.VISIBLE);
+                    }
+                    if (progressToVolume != deviceVolume) {
+                        mController.adjustVolume(device, progressToVolume);
+                        if (mController.isAdvancedLayoutSupported() && deviceVolume == 0) {
+                            updateUnmutedVolumeIcon();
+                        }
                     }
                 }
 
                 @Override
                 public void onStartTrackingTouch(SeekBar seekBar) {
+                    if (mController.isAdvancedLayoutSupported()) {
+                        mTitleIcon.setVisibility(View.INVISIBLE);
+                        mVolumeValueText.setVisibility(View.VISIBLE);
+                    }
                     mIsDragging = true;
                 }
 
                 @Override
                 public void onStopTrackingTouch(SeekBar seekBar) {
+                    if (mController.isAdvancedLayoutSupported()) {
+                        int currentVolume = MediaOutputSeekbar.scaleProgressToVolume(
+                                seekBar.getProgress());
+                        int percentage =
+                                (int) ((double) currentVolume * VOLUME_PERCENTAGE_SCALE_SIZE
+                                        / (double) seekBar.getMax());
+                        if (percentage == 0) {
+                            seekBar.setProgress(0);
+                            updateMutedVolumeIcon();
+                        } else {
+                            updateUnmutedVolumeIcon();
+                        }
+                        mTitleIcon.setVisibility(View.VISIBLE);
+                        mVolumeValueText.setVisibility(View.GONE);
+                    }
                     mIsDragging = false;
                 }
             });
         }
 
+        void updateMutedVolumeIcon() {
+            updateTitleIcon(R.drawable.media_output_icon_volume_off,
+                    mController.getColorItemContent());
+            final GradientDrawable iconAreaBackgroundDrawable =
+                    (GradientDrawable) mIconAreaLayout.getBackground();
+            iconAreaBackgroundDrawable.setCornerRadius(mController.getActiveRadius());
+        }
+
+        void updateUnmutedVolumeIcon() {
+            updateTitleIcon(R.drawable.media_output_icon_volume,
+                    mController.getColorItemContent());
+            final GradientDrawable iconAreaBackgroundDrawable =
+                    (GradientDrawable) mIconAreaLayout.getBackground();
+            iconAreaBackgroundDrawable.setCornerRadii(new float[]{
+                    mController.getActiveRadius(),
+                    mController.getActiveRadius(),
+                    0, 0, 0, 0, mController.getActiveRadius(), mController.getActiveRadius()
+            });
+        }
+
+        void updateTitleIcon(@DrawableRes int id, int color) {
+            mTitleIcon.setImageDrawable(mContext.getDrawable(id));
+            mTitleIcon.setColorFilter(color);
+            if (mController.isAdvancedLayoutSupported()) {
+                mIconAreaLayout.getBackground().setColorFilter(
+                        new PorterDuffColorFilter(mController.getColorSeekbarProgress(),
+                                PorterDuff.Mode.SRC_IN));
+            }
+        }
+
+        void updateIconAreaClickListener(View.OnClickListener listener) {
+            if (mController.isAdvancedLayoutSupported()) {
+                mIconAreaLayout.setOnClickListener(listener);
+            }
+            mTitleIcon.setOnClickListener(listener);
+        }
+
         void initMutingExpectedDevice() {
             disableSeekBar();
             final Drawable backgroundDrawable = mContext.getDrawable(
@@ -316,11 +448,26 @@
             final ClipDrawable clipDrawable =
                     (ClipDrawable) ((LayerDrawable) mSeekBar.getProgressDrawable())
                             .findDrawableByLayerId(android.R.id.progress);
-            final GradientDrawable progressDrawable = (GradientDrawable) clipDrawable.getDrawable();
+            final GradientDrawable targetBackgroundDrawable =
+                    (GradientDrawable) (mController.isAdvancedLayoutSupported()
+                            ? mIconAreaLayout.getBackground()
+                            : clipDrawable.getDrawable());
             mCornerAnimator.addUpdateListener(animation -> {
                 float value = (float) animation.getAnimatedValue();
                 layoutBackgroundDrawable.setCornerRadius(value);
-                progressDrawable.setCornerRadius(value);
+                if (mController.isAdvancedLayoutSupported()) {
+                    if (toProgress == 0) {
+                        targetBackgroundDrawable.setCornerRadius(value);
+                    } else {
+                        targetBackgroundDrawable.setCornerRadii(new float[]{
+                                value,
+                                value,
+                                0, 0, 0, 0, value, value
+                        });
+                    }
+                } else {
+                    targetBackgroundDrawable.setCornerRadius(value);
+                }
             });
             mVolumeAnimator.setIntValues(fromProgress, toProgress);
             mVolumeAnimator.start();
@@ -391,6 +538,7 @@
                         return;
                     }
                     mTitleIcon.setImageIcon(icon);
+                    mTitleIcon.setColorFilter(mController.getColorItemContent());
                 });
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
index 2b5d6fd..cdd00f9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt
@@ -26,6 +26,7 @@
 import com.android.settingslib.bluetooth.LocalBluetoothManager
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -47,7 +48,8 @@
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager
+    private val keyGuardManager: KeyguardManager,
+    private val featureFlags: FeatureFlags
 ) {
     var mediaOutputBroadcastDialog: MediaOutputBroadcastDialog? = null
 
@@ -59,7 +61,7 @@
         val controller = MediaOutputController(context, packageName,
                 mediaSessionManager, lbm, starter, notifCollection,
                 dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
-                powerExemptionManager, keyGuardManager)
+                powerExemptionManager, keyGuardManager, featureFlags)
         val dialog =
                 MediaOutputBroadcastDialog(context, aboveStatusBar, broadcastSender, controller)
         mediaOutputBroadcastDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index 19b401d..9b361e3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -73,6 +73,8 @@
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.monet.ColorScheme;
 import com.android.systemui.plugins.ActivityStarter;
@@ -145,8 +147,11 @@
     private int mColorConnectedItemBackground;
     private int mColorPositiveButtonText;
     private int mColorDialogBackground;
+    private int mItemMarginEndDefault;
+    private int mItemMarginEndSelectable;
     private float mInactiveRadius;
     private float mActiveRadius;
+    private FeatureFlags mFeatureFlags;
 
     public enum BroadcastNotifyDialog {
         ACTION_FIRST_LAUNCH,
@@ -162,7 +167,8 @@
             Optional<NearbyMediaDevicesManager> nearbyMediaDevicesManagerOptional,
             AudioManager audioManager,
             PowerExemptionManager powerExemptionManager,
-            KeyguardManager keyGuardManager) {
+            KeyguardManager keyGuardManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mPackageName = packageName;
         mMediaSessionManager = mediaSessionManager;
@@ -172,6 +178,7 @@
         mAudioManager = audioManager;
         mPowerExemptionManager = powerExemptionManager;
         mKeyGuardManager = keyGuardManager;
+        mFeatureFlags = featureFlags;
         InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
         mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
@@ -195,6 +202,10 @@
                 R.dimen.media_output_dialog_active_background_radius);
         mColorDialogBackground = Utils.getColorStateListDefaultColor(mContext,
                 R.color.media_dialog_background);
+        mItemMarginEndDefault = (int) mContext.getResources().getDimension(
+                R.dimen.media_output_dialog_default_margin_end);
+        mItemMarginEndSelectable = (int) mContext.getResources().getDimension(
+                R.dimen.media_output_dialog_selectable_margin_end);
     }
 
     void start(@NonNull Callback cb) {
@@ -527,6 +538,14 @@
         return mActiveRadius;
     }
 
+    public int getItemMarginEndDefault() {
+        return mItemMarginEndDefault;
+    }
+
+    public int getItemMarginEndSelectable() {
+        return mItemMarginEndSelectable;
+    }
+
     private void buildMediaDevices(List<MediaDevice> devices) {
         synchronized (mMediaDevicesLock) {
             attachRangeInfo(devices);
@@ -599,6 +618,10 @@
                 currentConnectedMediaDevice);
     }
 
+    public boolean isAdvancedLayoutSupported() {
+        return mFeatureFlags.isEnabled(Flags.OUTPUT_SWITCHER_ADVANCED_LAYOUT);
+    }
+
     List<MediaDevice> getGroupMediaDevices() {
         final List<MediaDevice> selectedDevices = getSelectedMediaDevice();
         final List<MediaDevice> selectableDevices = getSelectableMediaDevice();
@@ -792,7 +815,7 @@
         MediaOutputController controller = new MediaOutputController(mContext, mPackageName,
                 mMediaSessionManager, mLocalBluetoothManager, mActivityStarter,
                 mNotifCollection, mDialogLaunchAnimator, Optional.of(mNearbyMediaDevicesManager),
-                mAudioManager, mPowerExemptionManager, mKeyGuardManager);
+                mAudioManager, mPowerExemptionManager, mKeyGuardManager, mFeatureFlags);
         MediaOutputBroadcastDialog dialog = new MediaOutputBroadcastDialog(mContext, true,
                 broadcastSender, controller);
         mDialogLaunchAnimator.showFromView(dialog, mediaOutputDialog);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 543efed..7dbf876 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
+import com.android.systemui.flags.FeatureFlags
 import java.util.Optional
 import javax.inject.Inject
 
@@ -49,7 +50,8 @@
     private val nearbyMediaDevicesManagerOptional: Optional<NearbyMediaDevicesManager>,
     private val audioManager: AudioManager,
     private val powerExemptionManager: PowerExemptionManager,
-    private val keyGuardManager: KeyguardManager
+    private val keyGuardManager: KeyguardManager,
+    private val featureFlags: FeatureFlags
 ) {
     companion object {
         private const val INTERACTION_JANK_TAG = "media_output"
@@ -65,7 +67,7 @@
             context, packageName,
             mediaSessionManager, lbm, starter, notifCollection,
             dialogLaunchAnimator, nearbyMediaDevicesManagerOptional, audioManager,
-            powerExemptionManager, keyGuardManager)
+            powerExemptionManager, keyGuardManager, featureFlags)
         val dialog =
             MediaOutputDialog(context, aboveStatusBar, broadcastSender, controller, uiEventLogger)
         mediaOutputDialog = dialog
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
index 4ff79d6..253c3c7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputSeekbar.java
@@ -26,6 +26,7 @@
  */
 public class MediaOutputSeekbar extends SeekBar {
     private static final int SCALE_SIZE = 1000;
+    public static final int VOLUME_PERCENTAGE_SCALE_SIZE = 100000;
 
     public MediaOutputSeekbar(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
index 647beb9..b10abb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt
@@ -48,52 +48,66 @@
     /** All commands for the sender device. */
     inner class SenderCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
-            val commandName = args[1]
+            if (args.size < 2) {
+                help(pw)
+                return
+            }
+
+            val senderArgs = processArgs(args)
+
             @StatusBarManager.MediaTransferSenderState
             val displayState: Int?
             try {
-                displayState = ChipStateSender.getSenderStateIdFromName(commandName)
+                displayState = ChipStateSender.getSenderStateIdFromName(senderArgs.commandName)
             } catch (ex: IllegalArgumentException) {
-                pw.println("Invalid command name $commandName")
+                pw.println("Invalid command name ${senderArgs.commandName}")
                 return
             }
 
             @SuppressLint("WrongConstant") // sysui allowed to call STATUS_BAR_SERVICE
             val statusBarManager = context.getSystemService(Context.STATUS_BAR_SERVICE)
                     as StatusBarManager
-            val routeInfo = MediaRoute2Info.Builder(if (args.size >= 4) args[3] else "id", args[0])
+            val routeInfo = MediaRoute2Info.Builder(senderArgs.id, senderArgs.deviceName)
                     .addFeature("feature")
-            val useAppIcon = !(args.size >= 3 && args[2] == "useAppIcon=false")
-            if (useAppIcon) {
+            if (senderArgs.useAppIcon) {
                 routeInfo.setClientPackageName(TEST_PACKAGE_NAME)
             }
 
+            var undoExecutor: Executor? = null
+            var undoRunnable: Runnable? = null
+            if (isSucceededState(displayState) && senderArgs.showUndo) {
+                undoExecutor = mainExecutor
+                undoRunnable = Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
+            }
+
             statusBarManager.updateMediaTapToTransferSenderDisplay(
                 displayState,
                 routeInfo.build(),
-                getUndoExecutor(displayState),
-                getUndoCallback(displayState)
+                undoExecutor,
+                undoRunnable,
             )
         }
 
-        private fun getUndoExecutor(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Executor? {
-            return if (isSucceededState(displayState)) {
-                mainExecutor
-            } else {
-                null
-            }
-        }
+        private fun processArgs(args: List<String>): SenderArgs {
+            val senderArgs = SenderArgs(
+                deviceName = args[0],
+                commandName = args[1],
+            )
 
-        private fun getUndoCallback(
-            @StatusBarManager.MediaTransferSenderState displayState: Int
-        ): Runnable? {
-            return if (isSucceededState(displayState)) {
-                Runnable { Log.i(CLI_TAG, "Undo triggered for $displayState") }
-            } else {
-                null
+            if (args.size == 2) {
+                return senderArgs
             }
+
+            // Process any optional arguments
+            args.subList(2, args.size).forEach {
+                when {
+                    it == "useAppIcon=false" -> senderArgs.useAppIcon = false
+                    it == "showUndo=false" -> senderArgs.showUndo = false
+                    it.substring(0, 3) == "id=" -> senderArgs.id = it.substring(3)
+                }
+            }
+
+            return senderArgs
         }
 
         private fun isSucceededState(
@@ -106,14 +120,31 @@
         }
 
         override fun help(pw: PrintWriter) {
-            pw.println("Usage: adb shell cmd statusbar $SENDER_COMMAND " +
-                    "<deviceName> <chipState> useAppIcon=[true|false] <id>")
+            pw.println(
+                "Usage: adb shell cmd statusbar $SENDER_COMMAND " +
+                "<deviceName> <chipState> " +
+                "useAppIcon=[true|false] id=<id> showUndo=[true|false]"
+            )
+            pw.println("Note: useAppIcon, id, and showUndo are optional additional commands.")
         }
     }
 
+    private data class SenderArgs(
+        val deviceName: String,
+        val commandName: String,
+        var id: String = "id",
+        var useAppIcon: Boolean = true,
+        var showUndo: Boolean = true,
+    )
+
     /** All commands for the receiver device. */
     inner class ReceiverCommand : Command {
         override fun execute(pw: PrintWriter, args: List<String>) {
+            if (args.isEmpty()) {
+                help(pw)
+                return
+            }
+
             val commandName = args[0]
             @StatusBarManager.MediaTransferReceiverState
             val displayState: Int?
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
index 120f7d6..b55bedd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttLogger.kt
@@ -58,6 +58,27 @@
         )
     }
 
+    /**
+     * Logs an invalid sender state transition error in trying to update to [desiredState].
+     *
+     * @param currentState the previous state of the chip.
+     * @param desiredState the new state of the chip.
+     */
+    fun logInvalidStateTransitionError(
+        currentState: String,
+        desiredState: String
+    ) {
+        buffer.log(
+                tag,
+                LogLevel.ERROR,
+                {
+                    str1 = currentState
+                    str2 = desiredState
+                },
+                { "Cannot display state=$str2 after state=$str1; invalid transition" }
+        )
+    }
+
     /** Logs that we couldn't find information for [packageName]. */
     fun logPackageNotFound(packageName: String) {
         buffer.log(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 769494a..009595a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -19,10 +19,12 @@
 import android.content.Context
 import android.content.pm.PackageManager
 import android.graphics.drawable.Drawable
-import com.android.settingslib.Utils
+import androidx.annotation.AttrRes
+import androidx.annotation.DrawableRes
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.TintedIcon
 
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
@@ -34,23 +36,6 @@
         const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
 
         /**
-         * Returns the information needed to display the icon in [Icon] form.
-         *
-         * See [getIconInfoFromPackageName].
-         */
-        fun getIconFromPackageName(
-            context: Context,
-            appPackageName: String?,
-            logger: MediaTttLogger,
-        ): Icon {
-            val iconInfo = getIconInfoFromPackageName(context, appPackageName, logger)
-            return Icon.Loaded(
-                iconInfo.drawable,
-                ContentDescription.Loaded(iconInfo.contentDescription)
-            )
-        }
-
-        /**
          * Returns the information needed to display the icon.
          *
          * The information will either contain app name and icon of the app playing media, or a
@@ -65,18 +50,22 @@
             logger: MediaTttLogger
         ): IconInfo {
             if (appPackageName != null) {
+                val packageManager = context.packageManager
                 try {
                     val contentDescription =
-                        context.packageManager
-                            .getApplicationInfo(
-                                appPackageName,
-                                PackageManager.ApplicationInfoFlags.of(0)
-                            )
-                            .loadLabel(context.packageManager)
-                            .toString()
+                        ContentDescription.Loaded(
+                            packageManager
+                                .getApplicationInfo(
+                                    appPackageName,
+                                    PackageManager.ApplicationInfoFlags.of(0)
+                                )
+                                .loadLabel(packageManager)
+                                .toString()
+                        )
                     return IconInfo(
                         contentDescription,
-                        drawable = context.packageManager.getApplicationIcon(appPackageName),
+                        MediaTttIcon.Loaded(packageManager.getApplicationIcon(appPackageName)),
+                        tintAttr = null,
                         isAppIcon = true
                     )
                 } catch (e: PackageManager.NameNotFoundException) {
@@ -84,25 +73,41 @@
                 }
             }
             return IconInfo(
-                contentDescription =
-                    context.getString(R.string.media_output_dialog_unknown_launch_app_name),
-                drawable =
-                    context.resources.getDrawable(R.drawable.ic_cast).apply {
-                        this.setTint(
-                            Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary)
-                        )
-                    },
+                ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name),
+                MediaTttIcon.Resource(R.drawable.ic_cast),
+                tintAttr = android.R.attr.textColorPrimary,
                 isAppIcon = false
             )
         }
     }
 }
 
+/** Stores all the information for an icon shown with media TTT. */
 data class IconInfo(
-    val contentDescription: String,
-    val drawable: Drawable,
+    val contentDescription: ContentDescription,
+    val icon: MediaTttIcon,
+    @AttrRes val tintAttr: Int?,
     /**
      * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon.
      */
     val isAppIcon: Boolean
-)
+) {
+    /** Converts this into a [TintedIcon]. */
+    fun toTintedIcon(): TintedIcon {
+        val iconOutput =
+            when (icon) {
+                is MediaTttIcon.Loaded -> Icon.Loaded(icon.drawable, contentDescription)
+                is MediaTttIcon.Resource -> Icon.Resource(icon.res, contentDescription)
+            }
+        return TintedIcon(iconOutput, tintAttr)
+    }
+}
+
+/**
+ * Mimics [com.android.systemui.common.shared.model.Icon] but without the content description, since
+ * the content description may need to be overridden.
+ */
+sealed interface MediaTttIcon {
+    data class Loaded(val drawable: Drawable) : MediaTttIcon
+    data class Resource(@DrawableRes val res: Int) : MediaTttIcon
+}
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 cc5e256..1c3a53c 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
@@ -33,9 +33,12 @@
 import com.android.internal.widget.CachingIconView
 import com.android.settingslib.Utils
 import com.android.systemui.R
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.media.taptotransfer.MediaTttFlags
+import com.android.systemui.media.taptotransfer.common.MediaTttIcon
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
@@ -161,11 +164,23 @@
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
-        val iconInfo = MediaTttUtils.getIconInfoFromPackageName(
+        var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context, newInfo.routeInfo.clientPackageName, logger
         )
-        val iconDrawable = newInfo.appIconDrawableOverride ?: iconInfo.drawable
-        val iconContentDescription = newInfo.appNameOverride ?: iconInfo.contentDescription
+
+        if (newInfo.appNameOverride != null) {
+            iconInfo = iconInfo.copy(
+                contentDescription = ContentDescription.Loaded(newInfo.appNameOverride.toString())
+            )
+        }
+
+        if (newInfo.appIconDrawableOverride != null) {
+            iconInfo = iconInfo.copy(
+                icon = MediaTttIcon.Loaded(newInfo.appIconDrawableOverride),
+                isAppIcon = true,
+            )
+        }
+
         val iconPadding =
             if (iconInfo.isAppIcon) {
                 0
@@ -175,8 +190,7 @@
 
         val iconView = currentView.getAppIconView()
         iconView.setPadding(iconPadding, iconPadding, iconPadding, iconPadding)
-        iconView.setImageDrawable(iconDrawable)
-        iconView.contentDescription = iconContentDescription
+        TintedIconViewBinder.bind(iconInfo.toTintedIcon(), iconView)
     }
 
     override fun animateViewIn(view: ViewGroup) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index af7317c..1f27582 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -56,7 +56,12 @@
         R.string.media_move_closer_to_start_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that the two devices are close but not close enough to *end* a cast
@@ -70,7 +75,12 @@
         R.string.media_move_closer_to_end_cast,
         transferStatus = TransferStatus.NOT_STARTED,
         endItem = null,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been initiated (but not
@@ -83,7 +93,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_RECEIVER_SUCCEEDED ||
+                    nextState == TRANSFER_TO_RECEIVER_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer from the receiver device and back to this device (the
@@ -96,7 +112,13 @@
         transferStatus = TransferStatus.IN_PROGRESS,
         endItem = SenderEndItem.Loading,
         timeout = TRANSFER_TRIGGERED_TIMEOUT_MILLIS
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_SUCCEEDED ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_FAILED
+        }
+    },
 
     /**
      * A state representing that a transfer to the receiver device has been successfully completed.
@@ -112,7 +134,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /**
      * A state representing that a transfer back to this device has been successfully completed.
@@ -128,7 +156,13 @@
             newState =
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED
         ),
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer to the receiver device has failed. */
     TRANSFER_TO_RECEIVER_FAILED(
@@ -137,7 +171,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_START_CAST ||
+                    nextState == TRANSFER_TO_THIS_DEVICE_TRIGGERED
+        }
+    },
 
     /** A state representing that a transfer back to this device has failed. */
     TRANSFER_TO_THIS_DEVICE_FAILED(
@@ -146,7 +186,13 @@
         R.string.media_transfer_failed,
         transferStatus = TransferStatus.FAILED,
         endItem = SenderEndItem.Error,
-    ),
+    ) {
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState == ALMOST_CLOSE_TO_END_CAST ||
+                    nextState == TRANSFER_TO_RECEIVER_TRIGGERED
+        }
+    },
 
     /** A state representing that this device is far away from any receiver device. */
     FAR_FROM_RECEIVER(
@@ -162,6 +208,12 @@
             throw IllegalArgumentException("FAR_FROM_RECEIVER should never be displayed, " +
                 "so its string should never be fetched")
         }
+
+        override fun isValidNextState(nextState: ChipStateSender): Boolean {
+            return nextState == FAR_FROM_RECEIVER ||
+                    nextState.transferStatus == TransferStatus.NOT_STARTED ||
+                    nextState.transferStatus == TransferStatus.IN_PROGRESS
+        }
     };
 
     /**
@@ -175,6 +227,8 @@
         return Text.Loaded(context.getString(stringResId!!, otherDeviceName))
     }
 
+    abstract fun isValidNextState(nextState: ChipStateSender): Boolean
+
     companion object {
         /**
          * Returns the sender state enum associated with the given [displayState] from
@@ -197,6 +251,31 @@
          */
         @StatusBarManager.MediaTransferSenderState
         fun getSenderStateIdFromName(name: String): Int = valueOf(name).stateInt
+
+        /**
+         * Validates the transition from a chip state to another.
+         *
+         * @param currentState is the current state of the chip.
+         * @param desiredState is the desired state of the chip.
+         * @return true if the transition from [currentState] to [desiredState] is valid, and false
+         * otherwise.
+         */
+        fun isValidStateTransition(
+                currentState: ChipStateSender?,
+                desiredState: ChipStateSender,
+        ): Boolean {
+            // Far from receiver is the default state.
+            if (currentState == null) {
+                return FAR_FROM_RECEIVER.isValidNextState(desiredState)
+            }
+
+            // No change in state is valid.
+            if (currentState == desiredState) {
+                return true
+            }
+
+            return currentState.isValidNextState(desiredState)
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index bb7bc6f..ec1984d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -52,6 +52,8 @@
 ) : CoreStartable {
 
     private var displayedState: ChipStateSender? = null
+    // A map to store current chip state per id.
+    private var stateMap: MutableMap<String, ChipStateSender> = mutableMapOf()
 
     private val commandQueueCallbacks =
         object : CommandQueue.Callbacks {
@@ -87,9 +89,22 @@
             logger.logStateChangeError(displayState)
             return
         }
+
+        val currentState = stateMap[routeInfo.id]
+        if (!ChipStateSender.isValidStateTransition(currentState, chipState)) {
+            // ChipStateSender.FAR_FROM_RECEIVER is the default state when there is no state.
+            logger.logInvalidStateTransitionError(
+                currentState = currentState?.name ?: ChipStateSender.FAR_FROM_RECEIVER.name,
+                chipState.name
+            )
+            return
+        }
         uiEventLogger.logSenderStateChange(chipState)
 
+        stateMap.put(routeInfo.id, chipState)
         if (chipState == ChipStateSender.FAR_FROM_RECEIVER) {
+            // No need to store the state since it is the default state
+            stateMap.remove(routeInfo.id)
             // Return early if we're not displaying a chip anyway
             val currentDisplayedState = displayedState ?: return
 
@@ -119,7 +134,7 @@
                     context,
                     logger,
                 )
-            )
+            ) { stateMap.remove(routeInfo.id) }
         }
     }
 
@@ -138,7 +153,9 @@
 
         return ChipbarInfo(
             // Display the app's icon as the start icon
-            startIcon = MediaTttUtils.getIconFromPackageName(context, packageName, logger),
+            startIcon =
+                MediaTttUtils.getIconInfoFromPackageName(context, packageName, logger)
+                    .toTintedIcon(),
             text = chipStateSender.getChipTextString(context, otherDeviceName),
             endItem =
                 when (chipStateSender.endItem) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index 3fd1aa7..9791e82 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -98,7 +98,7 @@
 
     // Tracks config changes that will actually recreate the nav bar
     private final InterestingConfigChanges mConfigChanges = new InterestingConfigChanges(
-            ActivityInfo.CONFIG_FONT_SCALE | ActivityInfo.CONFIG_SCREEN_LAYOUT
+            ActivityInfo.CONFIG_FONT_SCALE
                     | ActivityInfo.CONFIG_UI_MODE);
 
     @Inject
@@ -145,7 +145,7 @@
         boolean willApplyConfig = mConfigChanges.applyNewConfig(mContext.getResources());
         boolean largeScreenChanged = mIsTablet != isOldConfigTablet;
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "NavbarController: newConfig=" + newConfig
                 + " mTaskbarDelegate initialized=" + mTaskbarDelegate.isInitialized()
                 + " willApplyConfigToNavbars=" + willApplyConfig
                 + " navBarCount=" + mNavigationBars.size());
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 26d3902..f97385b 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -977,7 +977,7 @@
         }
 
         // TODO(b/243765256): Disable this logging once b/243765256 is fixed.
-        Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
+        Log.i(DEBUG_MISSING_GESTURE_TAG, "Config changed: newConfig=" + newConfig
                 + " lastReportedConfig=" + mLastReportedConfig);
         mLastReportedConfig.updateFrom(newConfig);
         updateDisplaySize();
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index b964b76..6dd60d0 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
+import android.content.pm.PackageManager
 import android.os.UserManager
-import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
 import com.android.systemui.util.kotlin.getOrNull
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -45,15 +47,22 @@
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    fun handleSystemKey(keyCode: Int) {
+    /**
+     * Shows a note task. How the task is shown will depend on when the method is invoked.
+     *
+     * If in multi-window mode, notes will open as a full screen experience. That is particularly
+     * important for Large screen devices. These devices may support a taskbar that let users to
+     * drag and drop a shortcut into multi-window mode, and notes should comply with this behaviour.
+     *
+     * If the keyguard is locked, notes will open as a full screen experience. A locked device has
+     * no contextual information which let us use the whole screen space available.
+     *
+     * If no in multi-window or the keyguard is unlocked, notes will open as a floating experience.
+     * That will let users open other apps in full screen, and take contextual notes.
+     */
+    fun showNoteTask(isInMultiWindowMode: Boolean = false) {
         if (!isEnabled) return
 
-        if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
-            showNoteTask()
-        }
-    }
-
-    private fun showNoteTask() {
         val bubbles = optionalBubbles.getOrNull() ?: return
         val keyguardManager = optionalKeyguardManager.getOrNull() ?: return
         val userManager = optionalUserManager.getOrNull() ?: return
@@ -62,11 +71,35 @@
         // TODO(b/249954038): We should handle direct boot (isUserUnlocked). For now, we do nothing.
         if (!userManager.isUserUnlocked) return
 
-        if (keyguardManager.isKeyguardLocked) {
+        if (isInMultiWindowMode || keyguardManager.isKeyguardLocked) {
             context.startActivity(intent)
         } else {
             // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter.
             bubbles.showAppBubble(intent)
         }
     }
+
+    /**
+     * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut
+     * component to [value].
+     *
+     * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the
+     * Widget Picker to all users.
+     */
+    fun setNoteTaskShortcutEnabled(value: Boolean) {
+        val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+
+        val enabledState =
+            if (value) {
+                PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+            } else {
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+            }
+
+        context.packageManager.setComponentEnabledSetting(
+            componentName,
+            enabledState,
+            PackageManager.DONT_KILL_APP,
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
index 0a5b600..d14b7a7 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt
@@ -16,9 +16,10 @@
 
 package com.android.systemui.notetask
 
+import android.view.KeyEvent
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.statusbar.CommandQueue
 import com.android.wm.shell.bubbles.Bubbles
-import dagger.Lazy
 import java.util.Optional
 import javax.inject.Inject
 
@@ -27,15 +28,18 @@
 @Inject
 constructor(
     private val optionalBubbles: Optional<Bubbles>,
-    private val lazyNoteTaskController: Lazy<NoteTaskController>,
+    private val noteTaskController: NoteTaskController,
     private val commandQueue: CommandQueue,
     @NoteTaskEnabledKey private val isEnabled: Boolean,
 ) {
 
-    private val callbacks =
+    @VisibleForTesting
+    val callbacks =
         object : CommandQueue.Callbacks {
             override fun handleSystemKey(keyCode: Int) {
-                lazyNoteTaskController.get().handleSystemKey(keyCode)
+                if (keyCode == KeyEvent.KEYCODE_VIDEO_APP_1) {
+                    noteTaskController.showNoteTask()
+                }
             }
         }
 
@@ -43,5 +47,6 @@
         if (isEnabled && optionalBubbles.isPresent) {
             commandQueue.addCallback(callbacks)
         }
+        noteTaskController.setNoteTaskShortcutEnabled(isEnabled)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
index 035396a..8bdf319 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskModule.kt
@@ -16,32 +16,47 @@
 
 package com.android.systemui.notetask
 
+import android.app.Activity
 import android.app.KeyguardManager
 import android.content.Context
 import android.os.UserManager
 import androidx.core.content.getSystemService
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import java.util.*
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+import java.util.Optional
 
 /** Compose all dependencies required by Note Task feature. */
 @Module
-internal class NoteTaskModule {
+internal interface NoteTaskModule {
 
-    @[Provides NoteTaskEnabledKey]
-    fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
-        return featureFlags.isEnabled(Flags.NOTE_TASKS)
-    }
+    @[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
+    fun bindNoteTaskLauncherActivity(activity: LaunchNoteTaskActivity): Activity?
 
-    @Provides
-    fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
-        return Optional.ofNullable(context.getSystemService())
-    }
+    @[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
+    fun bindNoteTaskShortcutActivity(activity: CreateNoteTaskShortcutActivity): Activity?
 
-    @Provides
-    fun provideOptionalUserManager(context: Context): Optional<UserManager> {
-        return Optional.ofNullable(context.getSystemService())
+    companion object {
+
+        @[Provides NoteTaskEnabledKey]
+        fun provideIsNoteTaskEnabled(featureFlags: FeatureFlags): Boolean {
+            return featureFlags.isEnabled(Flags.NOTE_TASKS)
+        }
+
+        @Provides
+        fun provideOptionalKeyguardManager(context: Context): Optional<KeyguardManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
+
+        @Provides
+        fun provideOptionalUserManager(context: Context): Optional<UserManager> {
+            return Optional.ofNullable(context.getSystemService())
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
new file mode 100644
index 0000000..f6a623e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/CreateNoteTaskShortcutActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.notetask.shortcut
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.annotation.DrawableRes
+import androidx.core.content.pm.ShortcutInfoCompat
+import androidx.core.content.pm.ShortcutManagerCompat
+import androidx.core.graphics.drawable.IconCompat
+import com.android.systemui.R
+import javax.inject.Inject
+
+/**
+ * Activity responsible for create a shortcut for notes action. If the shortcut is enabled, a new
+ * shortcut will appear in the widget picker. If the shortcut is selected, the Activity here will be
+ * launched, creating a new shortcut for [CreateNoteTaskShortcutActivity], and will finish.
+ *
+ * @see <a
+ * href="https://developer.android.com/develop/ui/views/launch/shortcuts/creating-shortcuts#custom-pinned">Creating
+ * a custom shortcut activity</a>
+ */
+internal class CreateNoteTaskShortcutActivity @Inject constructor() : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        val intent =
+            createShortcutIntent(
+                id = SHORTCUT_ID,
+                shortLabel = getString(R.string.note_task_button_label),
+                intent = LaunchNoteTaskActivity.newIntent(context = this),
+                iconResource = R.drawable.ic_note_task_button,
+            )
+        setResult(Activity.RESULT_OK, intent)
+
+        finish()
+    }
+
+    private fun createShortcutIntent(
+        id: String,
+        shortLabel: String,
+        intent: Intent,
+        @DrawableRes iconResource: Int,
+    ): Intent {
+        val shortcutInfo =
+            ShortcutInfoCompat.Builder(this, id)
+                .setIntent(intent)
+                .setShortLabel(shortLabel)
+                .setLongLived(true)
+                .setIcon(IconCompat.createWithResource(this, iconResource))
+                .build()
+
+        return ShortcutManagerCompat.createShortcutResultIntent(
+            this,
+            shortcutInfo,
+        )
+    }
+
+    private companion object {
+        private const val SHORTCUT_ID = "note-task-shortcut-id"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
new file mode 100644
index 0000000..47fe676
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivity.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.notetask.shortcut
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import com.android.systemui.notetask.NoteTaskController
+import com.android.systemui.notetask.NoteTaskIntentResolver
+import javax.inject.Inject
+
+/** Activity responsible for launching the note experience, and finish. */
+internal class LaunchNoteTaskActivity
+@Inject
+constructor(
+    private val noteTaskController: NoteTaskController,
+) : ComponentActivity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        noteTaskController.showNoteTask(isInMultiWindowMode)
+
+        finish()
+    }
+
+    companion object {
+
+        /** Creates a new [Intent] set to start [LaunchNoteTaskActivity]. */
+        fun newIntent(context: Context): Intent {
+            return Intent(context, LaunchNoteTaskActivity::class.java).apply {
+                // Intent's action must be set in shortcuts, or an exception will be thrown.
+                // TODO(b/254606432): Use Intent.ACTION_NOTES instead.
+                action = NoteTaskIntentResolver.NOTES_ACTION
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 3c10778..930de13 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -122,10 +122,6 @@
     /** Remove a [OnDialogDismissedListener]. */
     fun removeOnDialogDismissedListener(listener: OnDialogDismissedListener)
 
-    /** Whether we should update the footer visibility. */
-    // TODO(b/242040009): Remove this.
-    fun shouldUpdateFooterVisibility(): Boolean
-
     @VisibleForTesting
     fun visibleButtonsCount(): Int
 
@@ -375,8 +371,6 @@
         }
     }
 
-    override fun shouldUpdateFooterVisibility() = dialog == null
-
     override fun showDialog(expandable: Expandable?) {
         synchronized(lock) {
             if (dialog == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
index a9943e8..b52233f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsController.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -16,261 +16,17 @@
 
 package com.android.systemui.qs
 
-import android.content.Intent
-import android.content.res.Configuration
-import android.os.Handler
-import android.os.UserManager
-import android.provider.Settings
-import android.provider.Settings.Global.USER_SWITCHER_ENABLED
-import android.view.View
-import android.view.ViewGroup
-import androidx.annotation.VisibleForTesting
-import com.android.internal.jank.InteractionJankMonitor
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.nano.MetricsProto
-import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.animation.Expandable
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.plugins.FalsingManager
-import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
-import com.android.systemui.qs.dagger.QSScope
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.statusbar.policy.UserInfoController.OnUserInfoChangedListener
-import com.android.systemui.util.LargeScreenUtils
-import com.android.systemui.util.ViewController
-import com.android.systemui.util.settings.GlobalSettings
+import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
-import javax.inject.Named
-import javax.inject.Provider
 
-/**
- * Manages [FooterActionsView] behaviour, both when it's placed in QS or QQS (split shade).
- * Main difference between QS and QQS behaviour is condition when buttons should be visible,
- * determined by [buttonsVisibleState]
- */
-@QSScope
-// TODO(b/242040009): Remove this file.
-internal class FooterActionsController @Inject constructor(
-    view: FooterActionsView,
-    multiUserSwitchControllerFactory: MultiUserSwitchController.Factory,
-    private val activityStarter: ActivityStarter,
-    private val userManager: UserManager,
-    private val userTracker: UserTracker,
-    private val userInfoController: UserInfoController,
-    private val deviceProvisionedController: DeviceProvisionedController,
-    private val securityFooterController: QSSecurityFooter,
-    private val fgsManagerFooterController: QSFgsManagerFooter,
-    private val falsingManager: FalsingManager,
-    private val metricsLogger: MetricsLogger,
-    private val globalActionsDialogProvider: Provider<GlobalActionsDialogLite>,
-    private val uiEventLogger: UiEventLogger,
-    @Named(PM_LITE_ENABLED) private val showPMLiteButton: Boolean,
-    private val globalSetting: GlobalSettings,
-    private val handler: Handler,
-    private val configurationController: ConfigurationController,
-) : ViewController<FooterActionsView>(view) {
-
-    private var globalActionsDialog: GlobalActionsDialogLite? = null
-
-    private var lastExpansion = -1f
-    private var listening: Boolean = false
-    private var inSplitShade = false
-
-    private val singleShadeAnimator by lazy {
-        // In single shade, the actions footer should only appear at the end of the expansion,
-        // so that it doesn't overlap with the notifications panel.
-        TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).setStartDelay(0.9f).build()
-    }
-
-    private val splitShadeAnimator by lazy {
-        // The Actions footer view has its own background which is the same color as the qs panel's
-        // background.
-        // We don't want it to fade in at the same time as the rest of the panel, otherwise it is
-        // more opaque than the rest of the panel's background. Only applies to split shade.
-        val alphaAnimator = TouchAnimator.Builder().addFloat(mView, "alpha", 0f, 1f).build()
-        val bgAlphaAnimator =
-            TouchAnimator.Builder()
-                .addFloat(mView, "backgroundAlpha", 0f, 1f)
-                .setStartDelay(0.9f)
-                .build()
-        // In split shade, we want the actions footer to fade in exactly at the same time as the
-        // rest of the shade, as there is no overlap.
-        TouchAnimator.Builder()
-            .addFloat(alphaAnimator, "position", 0f, 1f)
-            .addFloat(bgAlphaAnimator, "position", 0f, 1f)
-            .build()
-    }
-
-    private val animators: TouchAnimator
-        get() = if (inSplitShade) splitShadeAnimator else singleShadeAnimator
-
-    var visible = true
-        set(value) {
-            field = value
-            updateVisibility()
-        }
-
-    private val settingsButtonContainer: View = view.findViewById(R.id.settings_button_container)
-    private val securityFootersContainer: ViewGroup? =
-        view.findViewById(R.id.security_footers_container)
-    private val powerMenuLite: View = view.findViewById(R.id.pm_lite)
-    private val multiUserSwitchController = multiUserSwitchControllerFactory.create(view)
-
-    @VisibleForTesting
-    internal val securityFootersSeparator = View(context).apply { visibility = View.GONE }
-
-    private val onUserInfoChangedListener = OnUserInfoChangedListener { _, picture, _ ->
-        val isGuestUser: Boolean = userManager.isGuestUser(KeyguardUpdateMonitor.getCurrentUser())
-        mView.onUserInfoChanged(picture, isGuestUser)
-    }
-
-    private val multiUserSetting =
-            object : SettingObserver(
-                    globalSetting, handler, USER_SWITCHER_ENABLED, userTracker.userId) {
-                override fun handleValueChanged(value: Int, observedChange: Boolean) {
-                    if (observedChange) {
-                        updateView()
-                    }
-                }
-            }
-
-    private val onClickListener = View.OnClickListener { v ->
-        // Don't do anything if the tap looks suspicious.
-        if (!visible || falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-            return@OnClickListener
-        }
-        if (v === settingsButtonContainer) {
-            if (!deviceProvisionedController.isCurrentUserSetup) {
-                // If user isn't setup just unlock the device and dump them back at SUW.
-                activityStarter.postQSRunnableDismissingKeyguard {}
-                return@OnClickListener
-            }
-            metricsLogger.action(MetricsProto.MetricsEvent.ACTION_QS_EXPANDED_SETTINGS_LAUNCH)
-            startSettingsActivity()
-        } else if (v === powerMenuLite) {
-            uiEventLogger.log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
-            globalActionsDialog?.showOrHideDialog(false, true, Expandable.fromView(powerMenuLite))
-        }
-    }
-
-    private val configurationListener =
-        object : ConfigurationController.ConfigurationListener {
-            override fun onConfigChanged(newConfig: Configuration?) {
-                updateResources()
-            }
-        }
-
-    private fun updateResources() {
-        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
-    }
-
-    override fun onInit() {
-        multiUserSwitchController.init()
-        securityFooterController.init()
-        fgsManagerFooterController.init()
-    }
-
-    private fun updateVisibility() {
-        val previousVisibility = mView.visibility
-        mView.visibility = if (visible) View.VISIBLE else View.INVISIBLE
-        if (previousVisibility != mView.visibility) updateView()
-    }
-
-    private fun startSettingsActivity() {
-        val animationController = settingsButtonContainer?.let {
-            ActivityLaunchAnimator.Controller.fromView(
-                    it,
-                    InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON)
-            }
-        activityStarter.startActivity(Intent(Settings.ACTION_SETTINGS),
-                true /* dismissShade */, animationController)
-    }
-
-    @VisibleForTesting
-    public override fun onViewAttached() {
-        globalActionsDialog = globalActionsDialogProvider.get()
-        if (showPMLiteButton) {
-            powerMenuLite.visibility = View.VISIBLE
-            powerMenuLite.setOnClickListener(onClickListener)
-        } else {
-            powerMenuLite.visibility = View.GONE
-        }
-        settingsButtonContainer.setOnClickListener(onClickListener)
-        multiUserSetting.isListening = true
-
-        val securityFooter = securityFooterController.view
-        securityFootersContainer?.addView(securityFooter)
-        val separatorWidth = resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
-        securityFootersContainer?.addView(securityFootersSeparator, separatorWidth, 1)
-
-        val fgsFooter = fgsManagerFooterController.view
-        securityFootersContainer?.addView(fgsFooter)
-
-        val visibilityListener =
-            VisibilityChangedDispatcher.OnVisibilityChangedListener { visibility ->
-                if (securityFooter.visibility == View.VISIBLE &&
-                    fgsFooter.visibility == View.VISIBLE) {
-                    securityFootersSeparator.visibility = View.VISIBLE
-                } else {
-                    securityFootersSeparator.visibility = View.GONE
-                }
-                fgsManagerFooterController
-                    .setCollapsed(securityFooter.visibility == View.VISIBLE)
-            }
-        securityFooterController.setOnVisibilityChangedListener(visibilityListener)
-        fgsManagerFooterController.setOnVisibilityChangedListener(visibilityListener)
-
-        configurationController.addCallback(configurationListener)
-
-        updateResources()
-        updateView()
-    }
-
-    private fun updateView() {
-        mView.updateEverything(multiUserSwitchController.isMultiUserEnabled)
-    }
-
-    override fun onViewDetached() {
-        globalActionsDialog?.destroy()
-        globalActionsDialog = null
-        setListening(false)
-        multiUserSetting.isListening = false
-        configurationController.removeCallback(configurationListener)
-    }
-
-    fun setListening(listening: Boolean) {
-        if (this.listening == listening) {
-            return
-        }
-        this.listening = listening
-        if (this.listening) {
-            userInfoController.addCallback(onUserInfoChangedListener)
-            updateView()
-        } else {
-            userInfoController.removeCallback(onUserInfoChangedListener)
-        }
-
-        fgsManagerFooterController.setListening(listening)
-        securityFooterController.setListening(listening)
-    }
-
-    fun disable(state2: Int) {
-        mView.disable(state2, multiUserSwitchController.isMultiUserEnabled)
-    }
-
-    fun setExpansion(headerExpansionFraction: Float) {
-        animators.setPosition(headerExpansionFraction)
-    }
-
-    fun setKeyguardShowing(showing: Boolean) {
-        setExpansion(lastExpansion)
+/** Controller for the footer actions. This manages the initialization of its dependencies. */
+@SysUISingleton
+class FooterActionsController
+@Inject
+constructor(
+    private val fgsManagerController: FgsManagerController,
+) {
+    fun init() {
+        fgsManagerController.init()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt b/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
deleted file mode 100644
index d602b0b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/FooterActionsView.kt
+++ /dev/null
@@ -1,135 +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.qs
-
-import android.app.StatusBarManager
-import android.content.Context
-import android.graphics.PorterDuff
-import android.graphics.drawable.Drawable
-import android.graphics.drawable.RippleDrawable
-import android.os.UserManager
-import android.util.AttributeSet
-import android.util.Log
-import android.view.MotionEvent
-import android.view.View
-import android.widget.ImageView
-import android.widget.LinearLayout
-import androidx.annotation.Keep
-import com.android.settingslib.Utils
-import com.android.settingslib.drawable.UserIconDrawable
-import com.android.systemui.R
-import com.android.systemui.statusbar.phone.MultiUserSwitch
-
-/**
- * Quick Settings bottom buttons placed in footer (aka utility bar) - always visible in expanded QS,
- * in split shade mode visible also in collapsed state. May contain up to 5 buttons: settings,
- * edit tiles, power off and conditionally: user switch and tuner
- */
-// TODO(b/242040009): Remove this file.
-class FooterActionsView(context: Context?, attrs: AttributeSet?) : LinearLayout(context, attrs) {
-    private lateinit var settingsContainer: View
-    private lateinit var multiUserSwitch: MultiUserSwitch
-    private lateinit var multiUserAvatar: ImageView
-
-    private var qsDisabled = false
-    private var expansionAmount = 0f
-
-    /**
-     * Sets the alpha of the background of this view.
-     *
-     * Used from a [TouchAnimator] in the controller.
-     */
-    var backgroundAlpha: Float = 1f
-        @Keep
-        set(value) {
-            field = value
-            background?.alpha = (value * 255).toInt()
-        }
-        @Keep get
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-        settingsContainer = findViewById(R.id.settings_button_container)
-        multiUserSwitch = findViewById(R.id.multi_user_switch)
-        multiUserAvatar = multiUserSwitch.findViewById(R.id.multi_user_avatar)
-
-        // RenderThread is doing more harm than good when touching the header (to expand quick
-        // settings), so disable it for this view
-        if (settingsContainer.background is RippleDrawable) {
-            (settingsContainer.background as RippleDrawable).setForceSoftware(true)
-        }
-        importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
-    }
-
-    fun disable(
-        state2: Int,
-        multiUserEnabled: Boolean
-    ) {
-        val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
-        if (disabled == qsDisabled) return
-        qsDisabled = disabled
-        updateEverything(multiUserEnabled)
-    }
-
-    fun updateEverything(
-        multiUserEnabled: Boolean
-    ) {
-        post {
-            updateVisibilities(multiUserEnabled)
-            updateClickabilities()
-            isClickable = false
-        }
-    }
-
-    private fun updateClickabilities() {
-        multiUserSwitch.isClickable = multiUserSwitch.visibility == VISIBLE
-        settingsContainer.isClickable = settingsContainer.visibility == VISIBLE
-    }
-
-    private fun updateVisibilities(
-        multiUserEnabled: Boolean
-    ) {
-        settingsContainer.visibility = if (qsDisabled) GONE else VISIBLE
-        multiUserSwitch.visibility = if (multiUserEnabled) VISIBLE else GONE
-        val isDemo = UserManager.isDeviceInDemoMode(context)
-        settingsContainer.visibility = if (isDemo) INVISIBLE else VISIBLE
-    }
-
-    fun onUserInfoChanged(picture: Drawable?, isGuestUser: Boolean) {
-        var pictureToSet = picture
-        if (picture != null && isGuestUser && picture !is UserIconDrawable) {
-            pictureToSet = picture.constantState.newDrawable(resources).mutate()
-            pictureToSet.setColorFilter(
-                    Utils.getColorAttrDefaultColor(mContext, android.R.attr.colorForeground),
-                    PorterDuff.Mode.SRC_IN)
-        }
-        multiUserAvatar.setImageDrawable(pictureToSet)
-    }
-
-    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
-        if (VERBOSE) Log.d(TAG, "FooterActionsView onInterceptTouchEvent ${ev?.string}")
-        return super.onInterceptTouchEvent(ev)
-    }
-
-    override fun onTouchEvent(event: MotionEvent?): Boolean {
-        if (VERBOSE) Log.d(TAG, "FooterActionsView onTouchEvent ${event?.string}")
-        return super.onTouchEvent(event)
-    }
-}
-private const val TAG = "FooterActionsView"
-private val VERBOSE = Log.isLoggable(TAG, Log.VERBOSE)
-private val MotionEvent.string
-    get() = "($id): ($x,$y)"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt b/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt
deleted file mode 100644
index 7c67d9f..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/NewFooterActionsController.kt
+++ /dev/null
@@ -1,33 +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.qs
-
-import com.android.systemui.dagger.SysUISingleton
-import javax.inject.Inject
-
-/** Controller for the footer actions. This manages the initialization of its dependencies. */
-@SysUISingleton
-class NewFooterActionsController
-@Inject
-// TODO(b/242040009): Rename this to FooterActionsController.
-constructor(
-    private val fgsManagerController: FgsManagerController,
-) {
-    fun init() {
-        fgsManagerController.init()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index dc9dcc2..0c242d9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -215,7 +215,7 @@
                 // Some views are always full width or have dependent padding
                 continue;
             }
-            if (!(view instanceof FooterActionsView)) {
+            if (view.getId() != R.id.qs_footer_actions) {
                 // Only padding for FooterActionsView, no margin. That way, the background goes
                 // all the way to the edge.
                 LayoutParams lp = (LayoutParams) view.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
deleted file mode 100644
index b1b9dd7..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ /dev/null
@@ -1,178 +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.qs;
-
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
-import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
-
-import android.content.Context;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.dagger.QSScope;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Footer entry point for the foreground service manager
- */
-// TODO(b/242040009): Remove this file.
-@QSScope
-public class QSFgsManagerFooter implements View.OnClickListener,
-        FgsManagerController.OnDialogDismissedListener,
-        FgsManagerController.OnNumberOfPackagesChangedListener,
-        VisibilityChangedDispatcher {
-
-    private final View mRootView;
-    private final TextView mFooterText;
-    private final Context mContext;
-    private final Executor mMainExecutor;
-    private final Executor mExecutor;
-
-    private final FgsManagerController mFgsManagerController;
-
-    private boolean mIsInitialized = false;
-    private int mNumPackages;
-
-    private final View mTextContainer;
-    private final View mNumberContainer;
-    private final TextView mNumberView;
-    private final ImageView mDotView;
-    private final ImageView mCollapsedDotView;
-
-    @Nullable
-    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
-
-    @Inject
-    QSFgsManagerFooter(@Named(QS_FGS_MANAGER_FOOTER_VIEW) View rootView,
-            @Main Executor mainExecutor, @Background Executor executor,
-            FgsManagerController fgsManagerController) {
-        mRootView = rootView;
-        mFooterText = mRootView.findViewById(R.id.footer_text);
-        mTextContainer = mRootView.findViewById(R.id.fgs_text_container);
-        mNumberContainer = mRootView.findViewById(R.id.fgs_number_container);
-        mNumberView = mRootView.findViewById(R.id.fgs_number);
-        mDotView = mRootView.findViewById(R.id.fgs_new);
-        mCollapsedDotView = mRootView.findViewById(R.id.fgs_collapsed_new);
-        mContext = rootView.getContext();
-        mMainExecutor = mainExecutor;
-        mExecutor = executor;
-        mFgsManagerController = fgsManagerController;
-    }
-
-    /**
-     * Whether to show the footer in collapsed mode (just a number) or not (text).
-     * @param collapsed
-     */
-    public void setCollapsed(boolean collapsed) {
-        mTextContainer.setVisibility(collapsed ? View.GONE : View.VISIBLE);
-        mNumberContainer.setVisibility(collapsed ? View.VISIBLE : View.GONE);
-        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) mRootView.getLayoutParams();
-        lp.width = collapsed ? ViewGroup.LayoutParams.WRAP_CONTENT : 0;
-        lp.weight = collapsed ? 0f : 1f;
-        mRootView.setLayoutParams(lp);
-    }
-
-    public void init() {
-        if (mIsInitialized) {
-            return;
-        }
-
-        mFgsManagerController.init();
-
-        mRootView.setOnClickListener(this);
-
-        mIsInitialized = true;
-    }
-
-    public void setListening(boolean listening) {
-        if (listening) {
-            mFgsManagerController.addOnDialogDismissedListener(this);
-            mFgsManagerController.addOnNumberOfPackagesChangedListener(this);
-            mNumPackages = mFgsManagerController.getNumRunningPackages();
-            refreshState();
-        } else {
-            mFgsManagerController.removeOnDialogDismissedListener(this);
-            mFgsManagerController.removeOnNumberOfPackagesChangedListener(this);
-        }
-    }
-
-    @Override
-    public void setOnVisibilityChangedListener(
-            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
-        mVisibilityChangedListener = onVisibilityChangedListener;
-    }
-
-    @Override
-    public void onClick(View view) {
-        mFgsManagerController.showDialog(Expandable.fromView(view));
-    }
-
-    public void refreshState() {
-        mExecutor.execute(this::handleRefreshState);
-    }
-
-    public View getView() {
-        return mRootView;
-    }
-
-    public void handleRefreshState() {
-        mMainExecutor.execute(() -> {
-            CharSequence text = icuMessageFormat(mContext.getResources(),
-                    R.string.fgs_manager_footer_label, mNumPackages);
-            mFooterText.setText(text);
-            mNumberView.setText(Integer.toString(mNumPackages));
-            mNumberView.setContentDescription(text);
-            if (mFgsManagerController.shouldUpdateFooterVisibility()) {
-                mRootView.setVisibility(mNumPackages > 0
-                        && mFgsManagerController.isAvailable().getValue() ? View.VISIBLE
-                        : View.GONE);
-                int dotVis = mFgsManagerController.getShowFooterDot().getValue()
-                        && mFgsManagerController.getNewChangesSinceDialogWasDismissed()
-                        ? View.VISIBLE : View.GONE;
-                mDotView.setVisibility(dotVis);
-                mCollapsedDotView.setVisibility(dotVis);
-                if (mVisibilityChangedListener != null) {
-                    mVisibilityChangedListener.onVisibilityChanged(mRootView.getVisibility());
-                }
-            }
-        });
-    }
-
-    @Override
-    public void onDialogDismissed() {
-        refreshState();
-    }
-
-    @Override
-    public void onNumberOfPackagesChanged(int numPackages) {
-        mNumPackages = numPackages;
-        refreshState();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index c0533ba..893574a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -33,6 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
+import android.widget.LinearLayout;
 
 import androidx.annotation.FloatRange;
 import androidx.annotation.Nullable;
@@ -48,7 +49,6 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
@@ -114,7 +114,7 @@
     private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
     private final QSTileHost mHost;
     private final FeatureFlags mFeatureFlags;
-    private final NewFooterActionsController mNewFooterActionsController;
+    private final FooterActionsController mFooterActionsController;
     private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
     private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
     private boolean mShowCollapsedOnKeyguard;
@@ -132,9 +132,6 @@
     private QSPanelController mQSPanelController;
     private QuickQSPanelController mQuickQSPanelController;
     private QSCustomizerController mQSCustomizerController;
-    @Nullable
-    private FooterActionsController mQSFooterActionController;
-    @Nullable
     private FooterActionsViewModel mQSFooterActionsViewModel;
     @Nullable
     private ScrollListener mScrollListener;
@@ -185,7 +182,7 @@
             QSFragmentComponent.Factory qsComponentFactory,
             QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
             FalsingManager falsingManager, DumpManager dumpManager, FeatureFlags featureFlags,
-            NewFooterActionsController newFooterActionsController,
+            FooterActionsController footerActionsController,
             FooterActionsViewModel.Factory footerActionsViewModelFactory) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
@@ -199,7 +196,7 @@
         mStatusBarStateController = statusBarStateController;
         mDumpManager = dumpManager;
         mFeatureFlags = featureFlags;
-        mNewFooterActionsController = newFooterActionsController;
+        mFooterActionsController = footerActionsController;
         mFooterActionsViewModelFactory = footerActionsViewModelFactory;
         mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
     }
@@ -226,18 +223,12 @@
         mQSPanelController.init();
         mQuickQSPanelController.init();
 
-        if (mFeatureFlags.isEnabled(Flags.NEW_FOOTER_ACTIONS)) {
-            mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
-                    this);
-            FooterActionsView footerActionsView = view.findViewById(R.id.qs_footer_actions);
-            FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
-                    mListeningAndVisibilityLifecycleOwner);
-
-            mNewFooterActionsController.init();
-        } else {
-            mQSFooterActionController = qsFragmentComponent.getQSFooterActionController();
-            mQSFooterActionController.init();
-        }
+        mQSFooterActionsViewModel = mFooterActionsViewModelFactory.create(/* lifecycleOwner */
+                this);
+        LinearLayout footerActionsView = view.findViewById(R.id.qs_footer_actions);
+        FooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
+                mListeningAndVisibilityLifecycleOwner);
+        mFooterActionsController.init();
 
         mQSPanelScrollView = view.findViewById(R.id.expanded_qs_scroll_view);
         mQSPanelScrollView.addOnLayoutChangeListener(
@@ -436,9 +427,6 @@
         mContainer.disable(state1, state2, animate);
         mHeader.disable(state1, state2, animate);
         mFooter.disable(state1, state2, animate);
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.disable(state2);
-        }
         updateQsState();
     }
 
@@ -457,11 +445,7 @@
         boolean footerVisible = qsPanelVisible && (mQsExpanded || !keyguardShowing
                 || mHeaderAnimating || mShowCollapsedOnKeyguard);
         mFooter.setVisibility(footerVisible ? View.VISIBLE : View.INVISIBLE);
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.setVisible(footerVisible);
-        } else {
-            mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
-        }
+        mQSFooterActionsViewModel.onVisibilityChangeRequested(footerVisible);
         mFooter.setExpanded((keyguardShowing && !mHeaderAnimating && !mShowCollapsedOnKeyguard)
                 || (mQsExpanded && !mStackScrollerOverscrolling));
         mQSPanelController.setVisibility(qsPanelVisible ? View.VISIBLE : View.INVISIBLE);
@@ -534,9 +518,6 @@
         }
 
         mFooter.setKeyguardShowing(keyguardShowing);
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.setKeyguardShowing(keyguardShowing);
-        }
         updateQsState();
     }
 
@@ -552,9 +533,6 @@
         if (DEBUG) Log.d(TAG, "setListening " + listening);
         mListening = listening;
         mQSContainerImplController.setListening(listening && mQsVisible);
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.setListening(listening && mQsVisible);
-        }
         mListeningAndVisibilityLifecycleOwner.updateState();
         updateQsPanelControllerListening();
     }
@@ -665,12 +643,8 @@
         mFooter.setExpansion(onKeyguardAndExpanded ? 1 : expansion);
         float footerActionsExpansion =
                 onKeyguardAndExpanded ? 1 : mInSplitShade ? alphaProgress : expansion;
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.setExpansion(footerActionsExpansion);
-        } else {
-            mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
-                    mInSplitShade);
-        }
+        mQSFooterActionsViewModel.onQuickSettingsExpansionChanged(footerActionsExpansion,
+                mInSplitShade);
         mQSPanelController.setRevealExpansion(expansion);
         mQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
         mQuickQSPanelController.getTileLayout().setExpansion(expansion, proposedTranslation);
@@ -835,11 +809,7 @@
         boolean customizing = isCustomizing();
         mQSPanelScrollView.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         mFooter.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
-        if (mQSFooterActionController != null) {
-            mQSFooterActionController.setVisible(!customizing);
-        } else {
-            mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
-        }
+        mQSFooterActionsViewModel.onVisibilityChangeRequested(!customizing);
         mHeader.setVisibility(!customizing ? View.VISIBLE : View.INVISIBLE);
         // Let the panel know the position changed and it needs to update where notifications
         // and whatnot are.
@@ -927,6 +897,11 @@
         updateShowCollapsedOnKeyguard();
     }
 
+    @VisibleForTesting
+    public ListeningAndVisibilityLifecycleOwner getListeningAndVisibilityLifecycleOwner() {
+        return mListeningAndVisibilityLifecycleOwner;
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         IndentingPrintWriter indentingPw = new IndentingPrintWriter(pw, /* singleIndent= */ "  ");
@@ -994,7 +969,8 @@
      *  - STARTED when mListening == true && mQsVisible == false.
      *  - RESUMED when mListening == true && mQsVisible == true.
      */
-    private class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
+    @VisibleForTesting
+    class ListeningAndVisibilityLifecycleOwner implements LifecycleOwner {
         private final LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);
         private boolean mDestroyed = false;
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
deleted file mode 100644
index 6c1e956..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES 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.qs;
-
-import static com.android.systemui.qs.dagger.QSFragmentModule.QS_SECURITY_FOOTER_VIEW;
-
-import android.app.admin.DevicePolicyEventLogger;
-import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.res.Resources;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.UserHandle;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import androidx.annotation.Nullable;
-
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.systemui.FontSizeUtils;
-import com.android.systemui.R;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.common.shared.model.Icon;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
-import com.android.systemui.security.data.model.SecurityModel;
-import com.android.systemui.statusbar.policy.SecurityController;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/** ViewController for the footer actions. */
-// TODO(b/242040009): Remove this class.
-@QSScope
-public class QSSecurityFooter extends ViewController<View>
-        implements OnClickListener, VisibilityChangedDispatcher {
-    protected static final String TAG = "QSSecurityFooter";
-
-    private final TextView mFooterText;
-    private final ImageView mPrimaryFooterIcon;
-    private Context mContext;
-    private final Callback mCallback = new Callback();
-    private final SecurityController mSecurityController;
-    private final Handler mMainHandler;
-    private final BroadcastDispatcher mBroadcastDispatcher;
-    private final QSSecurityFooterUtils mQSSecurityFooterUtils;
-
-    protected H mHandler;
-
-    private boolean mIsVisible;
-    private boolean mIsClickable;
-    @Nullable
-    private CharSequence mFooterTextContent = null;
-    private Icon mFooterIcon;
-
-    @Nullable
-    private VisibilityChangedDispatcher.OnVisibilityChangedListener mVisibilityChangedListener;
-
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(
-                    DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG)) {
-                showDeviceMonitoringDialog();
-            }
-        }
-    };
-
-    @Inject
-    QSSecurityFooter(@Named(QS_SECURITY_FOOTER_VIEW) View rootView,
-            @Main Handler mainHandler, SecurityController securityController,
-            @Background Looper bgLooper, BroadcastDispatcher broadcastDispatcher,
-            QSSecurityFooterUtils qSSecurityFooterUtils) {
-        super(rootView);
-        mFooterText = mView.findViewById(R.id.footer_text);
-        mPrimaryFooterIcon = mView.findViewById(R.id.primary_footer_icon);
-        mFooterIcon = new Icon.Resource(
-                R.drawable.ic_info_outline, /* contentDescription= */ null);
-        mContext = rootView.getContext();
-        mSecurityController = securityController;
-        mMainHandler = mainHandler;
-        mHandler = new H(bgLooper);
-        mBroadcastDispatcher = broadcastDispatcher;
-        mQSSecurityFooterUtils = qSSecurityFooterUtils;
-    }
-
-    @Override
-    protected void onViewAttached() {
-        // Use background handler, as it's the same thread that handleClick is called on.
-        mBroadcastDispatcher.registerReceiverWithHandler(mReceiver,
-                new IntentFilter(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG),
-                mHandler, UserHandle.ALL);
-        mView.setOnClickListener(this);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        mBroadcastDispatcher.unregisterReceiver(mReceiver);
-        mView.setOnClickListener(null);
-    }
-
-    public void setListening(boolean listening) {
-        if (listening) {
-            mSecurityController.addCallback(mCallback);
-            refreshState();
-        } else {
-            mSecurityController.removeCallback(mCallback);
-        }
-    }
-
-    @Override
-    public void setOnVisibilityChangedListener(
-            @Nullable OnVisibilityChangedListener onVisibilityChangedListener) {
-        mVisibilityChangedListener = onVisibilityChangedListener;
-    }
-
-    public void onConfigurationChanged() {
-        FontSizeUtils.updateFontSize(mFooterText, R.dimen.qs_tile_text_size);
-        Resources r = mContext.getResources();
-
-        int padding = r.getDimensionPixelSize(R.dimen.qs_footer_padding);
-        mView.setPaddingRelative(padding, 0, padding, 0);
-        mView.setBackground(mContext.getDrawable(R.drawable.qs_security_footer_background));
-    }
-
-    public View getView() {
-        return mView;
-    }
-
-    public boolean hasFooter() {
-        return mView.getVisibility() != View.GONE;
-    }
-
-    @Override
-    public void onClick(View v) {
-        if (!hasFooter()) return;
-        mHandler.sendEmptyMessage(H.CLICK);
-    }
-
-    private void handleClick() {
-        showDeviceMonitoringDialog();
-        DevicePolicyEventLogger
-                .createEvent(FrameworkStatsLog.DEVICE_POLICY_EVENT__EVENT_ID__DO_USER_INFO_CLICKED)
-                .write();
-    }
-
-    // TODO(b/242040009): Remove this.
-    public void showDeviceMonitoringDialog() {
-        mQSSecurityFooterUtils.showDeviceMonitoringDialog(mContext, Expandable.fromView(mView));
-    }
-
-    public void refreshState() {
-        mHandler.sendEmptyMessage(H.REFRESH_STATE);
-    }
-
-    private void handleRefreshState() {
-        SecurityModel securityModel = SecurityModel.create(mSecurityController);
-        SecurityButtonConfig buttonConfig = mQSSecurityFooterUtils.getButtonConfig(securityModel);
-
-        if (buttonConfig == null) {
-            mIsVisible = false;
-        } else {
-            mIsVisible = true;
-            mIsClickable = buttonConfig.isClickable();
-            mFooterTextContent = buttonConfig.getText();
-            mFooterIcon = buttonConfig.getIcon();
-        }
-
-        // Update the UI.
-        mMainHandler.post(mUpdatePrimaryIcon);
-        mMainHandler.post(mUpdateDisplayState);
-    }
-
-    private final Runnable mUpdatePrimaryIcon = new Runnable() {
-        @Override
-        public void run() {
-            if (mFooterIcon instanceof Icon.Loaded) {
-                mPrimaryFooterIcon.setImageDrawable(((Icon.Loaded) mFooterIcon).getDrawable());
-            } else if (mFooterIcon instanceof Icon.Resource) {
-                mPrimaryFooterIcon.setImageResource(((Icon.Resource) mFooterIcon).getRes());
-            }
-        }
-    };
-
-    private final Runnable mUpdateDisplayState = new Runnable() {
-        @Override
-        public void run() {
-            if (mFooterTextContent != null) {
-                mFooterText.setText(mFooterTextContent);
-            }
-            mView.setVisibility(mIsVisible ? View.VISIBLE : View.GONE);
-            if (mVisibilityChangedListener != null) {
-                mVisibilityChangedListener.onVisibilityChanged(mView.getVisibility());
-            }
-
-            if (mIsVisible && mIsClickable) {
-                mView.setClickable(true);
-                mView.findViewById(R.id.footer_icon).setVisibility(View.VISIBLE);
-            } else {
-                mView.setClickable(false);
-                mView.findViewById(R.id.footer_icon).setVisibility(View.GONE);
-            }
-        }
-    };
-
-    private class Callback implements SecurityController.SecurityControllerCallback {
-        @Override
-        public void onStateChanged() {
-            refreshState();
-        }
-    }
-
-    private class H extends Handler {
-        private static final int CLICK = 0;
-        private static final int REFRESH_STATE = 1;
-
-        private H(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            String name = null;
-            try {
-                if (msg.what == REFRESH_STATE) {
-                    name = "handleRefreshState";
-                    handleRefreshState();
-                } else if (msg.what == CLICK) {
-                    name = "handleClick";
-                    handleClick();
-                }
-            } catch (Throwable t) {
-                final String error = "Error in " + name;
-                Log.w(TAG, error, t);
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
index 67bc769..5dbf0f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooterUtils.java
@@ -247,7 +247,7 @@
 
         Icon icon;
         ContentDescription contentDescription = null;
-        if (isParentalControlsEnabled) {
+        if (isParentalControlsEnabled && securityModel.getDeviceAdminIcon() != null) {
             icon = new Icon.Loaded(securityModel.getDeviceAdminIcon(), contentDescription);
         } else if (vpnName != null || vpnNameWorkProfile != null) {
             if (securityModel.isVpnBranded()) {
@@ -476,7 +476,7 @@
     @VisibleForTesting
     View createDialogView(Context quickSettingsContext) {
         if (mSecurityController.isParentalControlsEnabled()) {
-            return createParentalControlsDialogView();
+            return createParentalControlsDialogView(quickSettingsContext);
         }
         return createOrganizationDialogView(quickSettingsContext);
     }
@@ -579,8 +579,8 @@
         return dialogView;
     }
 
-    private View createParentalControlsDialogView() {
-        View dialogView = LayoutInflater.from(mContext)
+    private View createParentalControlsDialogView(Context quickSettingsContext) {
+        View dialogView = LayoutInflater.from(quickSettingsContext)
                 .inflate(R.layout.quick_settings_footer_dialog_parental_controls, null, false);
 
         DeviceAdminInfo info = mSecurityController.getDeviceAdminInfo();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 6240c10..cad296b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -608,7 +608,7 @@
 
         if (TextUtils.isEmpty(tileList)) {
             tileList = res.getString(R.string.quick_settings_tiles);
-            if (DEBUG) Log.d(TAG, "Loaded tile specs from config: " + tileList);
+            if (DEBUG) Log.d(TAG, "Loaded tile specs from default config: " + tileList);
         } else {
             if (DEBUG) Log.d(TAG, "Loaded tile specs from setting: " + tileList);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
index aa505fb..01eb636 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSFragmentModule.java
@@ -28,7 +28,6 @@
 import com.android.systemui.dagger.qualifiers.RootView;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.privacy.OngoingPrivacyChip;
-import com.android.systemui.qs.FooterActionsView;
 import com.android.systemui.qs.QSContainerImpl;
 import com.android.systemui.qs.QSFooter;
 import com.android.systemui.qs.QSFooterView;
@@ -51,8 +50,6 @@
  */
 @Module
 public interface QSFragmentModule {
-    String QS_FGS_MANAGER_FOOTER_VIEW = "qs_fgs_manager_footer";
-    String QS_SECURITY_FOOTER_VIEW = "qs_security_footer";
     String QS_USING_MEDIA_PLAYER = "qs_using_media_player";
     String QS_USING_COLLAPSED_LANDSCAPE_MEDIA = "qs_using_collapsed_landscape_media";
 
@@ -119,16 +116,6 @@
         return view.findViewById(R.id.qs_footer);
     }
 
-    /**
-     * Provides a {@link FooterActionsView}.
-     *
-     * This will replace a ViewStub either in {@link QSFooterView} or in {@link QSContainerImpl}.
-     */
-    @Provides
-    static FooterActionsView providesQSFooterActionsView(@RootView View view) {
-        return view.findViewById(R.id.qs_footer_actions);
-    }
-
     /** */
     @Provides
     @QSScope
@@ -146,18 +133,6 @@
 
     /** */
     @Provides
-    @QSScope
-    @Named(QS_SECURITY_FOOTER_VIEW)
-    static View providesQSSecurityFooterView(
-            @QSThemedContext LayoutInflater layoutInflater,
-            FooterActionsView footerActionsView
-    ) {
-        return layoutInflater.inflate(R.layout.quick_settings_security_footer, footerActionsView,
-                false);
-    }
-
-    /** */
-    @Provides
     @Named(QS_USING_MEDIA_PLAYER)
     static boolean providesQSUsingMediaPlayer(Context context) {
         return useQsMediaPlayer(context);
@@ -183,15 +158,4 @@
     static StatusIconContainer providesStatusIconContainer(QuickStatusBarHeader qsHeader) {
         return qsHeader.findViewById(R.id.statusIcons);
     }
-
-    /** */
-    @Provides
-    @QSScope
-    @Named(QS_FGS_MANAGER_FOOTER_VIEW)
-    static View providesQSFgsManagerFooterView(
-            @QSThemedContext LayoutInflater layoutInflater,
-            FooterActionsView footerActionsView
-    ) {
-        return layoutInflater.inflate(R.layout.fgs_footer, footerActionsView, false);
-    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 3e39c8e..6db3c99 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -35,35 +35,31 @@
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.people.ui.view.PeopleViewBinder.bind
-import com.android.systemui.qs.FooterActionsView
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import kotlin.math.roundToInt
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 /** A ViewBinder for [FooterActionsViewBinder]. */
 object FooterActionsViewBinder {
-    /**
-     * Create a [FooterActionsView] that can later be [bound][bind] to a [FooterActionsViewModel].
-     */
+    /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
     @JvmStatic
-    fun create(context: Context): FooterActionsView {
+    fun create(context: Context): LinearLayout {
         return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
-            as FooterActionsView
+            as LinearLayout
     }
 
     /** Bind [view] to [viewModel]. */
     @JvmStatic
     fun bind(
-        view: FooterActionsView,
+        view: LinearLayout,
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
     ) {
-        // Remove all children of the FooterActionsView that are used by the old implementation.
-        // TODO(b/242040009): Clean up the XML once the old implementation is removed.
-        view.removeAllViews()
+        view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
 
         // Add the views used by this new implementation.
         val context = view.context
@@ -117,7 +113,11 @@
                 }
 
                 launch { viewModel.alpha.collect { view.alpha = it } }
-                launch { viewModel.backgroundAlpha.collect { view.backgroundAlpha = it } }
+                launch {
+                    viewModel.backgroundAlpha.collect {
+                        view.background?.alpha = (it * 255).roundToInt()
+                    }
+                }
             }
 
             // Listen for model changes only when QS are visible.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
index 350d8b0..28dd986 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java
@@ -188,8 +188,6 @@
         int mWifiSignalIconId;
         @Nullable
         String mSsid;
-        boolean mActivityIn;
-        boolean mActivityOut;
         @Nullable
         String mWifiSignalContentDescription;
         boolean mIsTransient;
@@ -207,8 +205,6 @@
                     .append(",mConnected=").append(mConnected)
                     .append(",mWifiSignalIconId=").append(mWifiSignalIconId)
                     .append(",mSsid=").append(mSsid)
-                    .append(",mActivityIn=").append(mActivityIn)
-                    .append(",mActivityOut=").append(mActivityOut)
                     .append(",mWifiSignalContentDescription=").append(mWifiSignalContentDescription)
                     .append(",mIsTransient=").append(mIsTransient)
                     .append(",mNoDefaultNetwork=").append(mNoDefaultNetwork)
@@ -226,8 +222,6 @@
         CharSequence mDataContentDescription;
         int mMobileSignalIconId;
         int mQsTypeIcon;
-        boolean mActivityIn;
-        boolean mActivityOut;
         boolean mNoSim;
         boolean mRoaming;
         boolean mMultipleSubs;
@@ -243,8 +237,6 @@
                 .append(",mDataContentDescription=").append(mDataContentDescription)
                 .append(",mMobileSignalIconId=").append(mMobileSignalIconId)
                 .append(",mQsTypeIcon=").append(mQsTypeIcon)
-                .append(",mActivityIn=").append(mActivityIn)
-                .append(",mActivityOut=").append(mActivityOut)
                 .append(",mNoSim=").append(mNoSim)
                 .append(",mRoaming=").append(mRoaming)
                 .append(",mMultipleSubs=").append(mMultipleSubs)
@@ -275,8 +267,6 @@
             mWifiInfo.mWifiSignalContentDescription = indicators.qsIcon.contentDescription;
             mWifiInfo.mEnabled = indicators.enabled;
             mWifiInfo.mSsid = indicators.description;
-            mWifiInfo.mActivityIn = indicators.activityIn;
-            mWifiInfo.mActivityOut = indicators.activityOut;
             mWifiInfo.mIsTransient = indicators.isTransient;
             mWifiInfo.mStatusLabel = indicators.statusLabel;
             refreshState(mWifiInfo);
@@ -297,8 +287,6 @@
                     ? indicators.typeContentDescriptionHtml : null;
             mCellularInfo.mMobileSignalIconId = indicators.qsIcon.icon;
             mCellularInfo.mQsTypeIcon = indicators.qsType;
-            mCellularInfo.mActivityIn = indicators.activityIn;
-            mCellularInfo.mActivityOut = indicators.activityOut;
             mCellularInfo.mRoaming = indicators.roaming;
             mCellularInfo.mMultipleSubs = mController.getNumberSubscriptions() > 1;
             refreshState(mCellularInfo);
@@ -345,7 +333,14 @@
             mCellularInfo.mAirplaneModeEnabled = icon.visible;
             mWifiInfo.mAirplaneModeEnabled = icon.visible;
             if (!mSignalCallback.mEthernetInfo.mConnected) {
-                if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
+                // Always use mWifiInfo to refresh the Internet Tile if airplane mode is enabled,
+                // because Internet Tile will show different information depending on whether WiFi
+                // is enabled or not.
+                if (mWifiInfo.mAirplaneModeEnabled) {
+                    refreshState(mWifiInfo);
+                // If airplane mode is disabled, we will use mWifiInfo to refresh the Internet Tile
+                // if WiFi is currently connected to avoid any icon flickering.
+                } else if (mWifiInfo.mEnabled && (mWifiInfo.mWifiSignalIconId > 0)
                         && (mWifiInfo.mSsid != null)) {
                     refreshState(mWifiInfo);
                 } else {
@@ -428,8 +423,6 @@
         state.state = Tile.STATE_ACTIVE;
         state.dualTarget = true;
         state.value = cb.mEnabled;
-        state.activityIn = cb.mEnabled && cb.mActivityIn;
-        state.activityOut = cb.mEnabled && cb.mActivityOut;
         final StringBuffer minimalContentDescription = new StringBuffer();
         final StringBuffer minimalStateDescription = new StringBuffer();
         final Resources r = mContext.getResources();
@@ -503,8 +496,6 @@
         boolean mobileDataEnabled = mDataController.isMobileDataSupported()
                 && mDataController.isMobileDataEnabled();
         state.value = mobileDataEnabled;
-        state.activityIn = mobileDataEnabled && cb.mActivityIn;
-        state.activityOut = mobileDataEnabled && cb.mActivityOut;
         state.expandedAccessibilityClassName = Switch.class.getName();
 
         if (cb.mAirplaneModeEnabled && cb.mQsTypeIcon != TelephonyIcons.ICON_CWF) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
index 7130294..a6c7781 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -27,6 +27,7 @@
 import android.view.View;
 import android.widget.Switch;
 
+import androidx.annotation.MainThread;
 import androidx.annotation.Nullable;
 
 import com.android.internal.logging.MetricsLogger;
@@ -91,11 +92,13 @@
     }
 
     @Override
+    @MainThread
     public void onManagedProfileChanged() {
         refreshState(mProfileController.isWorkModeEnabled());
     }
 
     @Override
+    @MainThread
     public void onManagedProfileRemoved() {
         mHost.removeTile(getTileSpec());
         mHost.unmarkTileAsAutoAdded(getTileSpec());
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 547b496..00d129a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -565,13 +565,25 @@
         statusBarWinController.registerCallback(mStatusBarWindowCallback);
         mScreenshotHelper = new ScreenshotHelper(context);
 
-        // Listen for tracing state changes
         commandQueue.addCallback(new CommandQueue.Callbacks() {
+
+            // Listen for tracing state changes
             @Override
             public void onTracingStateChanged(boolean enabled) {
                 mSysUiState.setFlag(SYSUI_STATE_TRACING_ENABLED, enabled)
                         .commitUpdate(mContext.getDisplayId());
             }
+
+            @Override
+            public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+                if (mOverviewProxy != null) {
+                    try {
+                        mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop);
+                    } catch (RemoteException e) {
+                        Log.w(TAG_OPS, "Unable to enter stage split from the current running app");
+                    }
+                }
+            }
         });
         mCommandQueue = commandQueue;
 
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index fae938d..9c7718d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -881,6 +881,13 @@
     }
 
     void addQuickShareChip(Notification.Action quickShareAction) {
+        if (mQuickShareChip != null) {
+            mSmartChips.remove(mQuickShareChip);
+            mActionsView.removeView(mQuickShareChip);
+        }
+        if (mPendingInteraction == PendingInteraction.QUICK_SHARE) {
+            mPendingInteraction = null;
+        }
         if (mPendingInteraction == null) {
             LayoutInflater inflater = LayoutInflater.from(mContext);
             mQuickShareChip = (OverlayActionChip) inflater.inflate(
diff --git a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
index 50af260..1cf5a8f 100644
--- a/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/security/data/model/SecurityModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.security.data.model
 
 import android.graphics.drawable.Drawable
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.statusbar.policy.SecurityController
 import kotlinx.coroutines.CoroutineDispatcher
@@ -55,8 +56,8 @@
          * Important: This method should be called from a background thread as this will do a lot of
          * binder calls.
          */
-        // TODO(b/242040009): Remove this.
         @JvmStatic
+        @VisibleForTesting
         fun create(securityController: SecurityController): SecurityModel {
             val deviceAdminInfo =
                 if (securityController.isParentalControlsEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
index 5880003..6bd9158 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java
@@ -67,11 +67,6 @@
 
     private static final Uri BRIGHTNESS_MODE_URI =
             Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE);
-    private static final Uri BRIGHTNESS_FOR_VR_FLOAT_URI =
-            Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_VR_FLOAT);
-
-    private final float mMinimumBacklightForVr;
-    private final float mMaximumBacklightForVr;
 
     private final int mDisplayId;
     private final Context mContext;
@@ -126,8 +121,6 @@
             if (BRIGHTNESS_MODE_URI.equals(uri)) {
                 mBackgroundHandler.post(mUpdateModeRunnable);
                 mBackgroundHandler.post(mUpdateSliderRunnable);
-            } else if (BRIGHTNESS_FOR_VR_FLOAT_URI.equals(uri)) {
-                mBackgroundHandler.post(mUpdateSliderRunnable);
             } else {
                 mBackgroundHandler.post(mUpdateModeRunnable);
                 mBackgroundHandler.post(mUpdateSliderRunnable);
@@ -140,9 +133,6 @@
             cr.registerContentObserver(
                     BRIGHTNESS_MODE_URI,
                     false, this, UserHandle.USER_ALL);
-            cr.registerContentObserver(
-                    BRIGHTNESS_FOR_VR_FLOAT_URI,
-                    false, this, UserHandle.USER_ALL);
             mDisplayManager.registerDisplayListener(mDisplayListener, mHandler,
                     DisplayManager.EVENT_FLAG_DISPLAY_BRIGHTNESS);
         }
@@ -304,10 +294,6 @@
 
         mDisplayId = mContext.getDisplayId();
         PowerManager pm = context.getSystemService(PowerManager.class);
-        mMinimumBacklightForVr = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MINIMUM_VR);
-        mMaximumBacklightForVr = pm.getBrightnessConstraint(
-                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_MAXIMUM_VR);
 
         mDisplayManager = context.getSystemService(DisplayManager.class);
         mVrManager = IVrManager.Stub.asInterface(ServiceManager.getService(
@@ -336,17 +322,12 @@
         final float maxBacklight;
         final int metric;
 
-        if (mIsVrModeEnabled) {
-            metric = MetricsEvent.ACTION_BRIGHTNESS_FOR_VR;
-            minBacklight = mMinimumBacklightForVr;
-            maxBacklight = mMaximumBacklightForVr;
-        } else {
-            metric = mAutomatic
-                    ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
-                    : MetricsEvent.ACTION_BRIGHTNESS;
-            minBacklight = mBrightnessMin;
-            maxBacklight = mBrightnessMax;
-        }
+
+        metric = mAutomatic
+                ? MetricsEvent.ACTION_BRIGHTNESS_AUTO
+                : MetricsEvent.ACTION_BRIGHTNESS;
+        minBacklight = mBrightnessMin;
+        maxBacklight = mBrightnessMax;
         final float valFloat = MathUtils.min(
                 convertGammaToLinearFloat(value, minBacklight, maxBacklight),
                 maxBacklight);
@@ -398,15 +379,8 @@
     }
 
     private void updateSlider(float brightnessValue, boolean inVrMode) {
-        final float min;
-        final float max;
-        if (inVrMode) {
-            min = mMinimumBacklightForVr;
-            max = mMaximumBacklightForVr;
-        } else {
-            min = mBrightnessMin;
-            max = mBrightnessMax;
-        }
+        final float min = mBrightnessMin;
+        final float max = mBrightnessMax;
 
         // Ensure the slider is in a fixed position first, then check if we should animate.
         if (mSliderAnimator != null && mSliderAnimator.isStarted()) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3b40aeb..507dec6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -465,7 +465,11 @@
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
 
-    /** Indicates drag starting height when swiping down or up on heads-up notifications */
+    /**
+     * Indicates drag starting height when swiping down or up on heads-up notifications.
+     * This usually serves as a threshold from when shade expansion should really start. Otherwise
+     * this value would be height of shade and it will be immediately expanded to some extent.
+     */
     private int mHeadsUpStartHeight;
     private HeadsUpTouchHelper mHeadsUpTouchHelper;
     private boolean mListenForHeadsUp;
@@ -3412,9 +3416,12 @@
                 && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
         boolean goingBetweenClosedShadeAndExpandedQs =
                 mQsExpandImmediate || collapsingShadeFromExpandedQs;
-        // we don't want to update QS expansion when HUN is visible because then the whole shade is
-        // initially hidden, even though it has non-zero height
-        if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
+        // in split shade we react when HUN is visible only if shade height is over HUN start
+        // height - which means user is swiping down. Otherwise shade QS will either not show at all
+        // with HUN movement or it will blink when touching HUN initially
+        boolean qsShouldExpandWithHeadsUp = !mSplitShadeEnabled
+                || (!mHeadsUpManager.isTrackingHeadsUp() || expandedHeight > mHeadsUpStartHeight);
+        if (goingBetweenClosedShadeAndExpandedQs && qsShouldExpandWithHeadsUp) {
             float qsExpansionFraction;
             if (mSplitShadeEnabled) {
                 qsExpansionFraction = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 1dd3a96..590a04a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -164,6 +164,8 @@
     private static final int MSG_UNREGISTER_NEARBY_MEDIA_DEVICE_PROVIDER = 67 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT;
     private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT;
+    private static final int MSG_GO_TO_FULLSCREEN_FROM_SPLIT = 70 << MSG_SHIFT;
+    private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -478,6 +480,16 @@
          * @see IStatusBar#showRearDisplayDialog
          */
         default void showRearDisplayDialog(int currentBaseState) {}
+
+        /**
+         * @see IStatusBar#goToFullscreenFromSplit
+         */
+        default void goToFullscreenFromSplit() {}
+
+        /**
+         * @see IStatusBar#enterStageSplitFromRunningApp
+         */
+        default void enterStageSplitFromRunningApp(boolean leftOrTop) {}
     }
 
     public CommandQueue(Context context) {
@@ -1239,6 +1251,14 @@
     }
 
     @Override
+    public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        synchronized (mLock) {
+            mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP,
+                    leftOrTop).sendToTarget();
+        }
+    }
+
+    @Override
     public void requestAddTile(
             @NonNull ComponentName componentName,
             @NonNull CharSequence appName,
@@ -1299,6 +1319,11 @@
                 .sendToTarget();
     }
 
+    @Override
+    public void goToFullscreenFromSplit() {
+        mHandler.obtainMessage(MSG_GO_TO_FULLSCREEN_FROM_SPLIT).sendToTarget();
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -1738,6 +1763,17 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).showRearDisplayDialog((Integer) msg.obj);
                     }
+                    break;
+                case MSG_GO_TO_FULLSCREEN_FROM_SPLIT:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).goToFullscreenFromSplit();
+                    }
+                    break;
+                case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj);
+                    }
+                    break;
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index d7eddf5..56c34a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,6 +39,7 @@
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
@@ -66,6 +67,8 @@
     // the next icon has translated out of the way, to avoid overlapping.
     private static final Interpolator ICON_ALPHA_INTERPOLATOR =
             new PathInterpolator(0.6f, 0f, 0.6f, 0f);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType SHELF_SCROLL = SourceType.from("ShelfScroll");
 
     private NotificationIconContainer mShelfIcons;
     private int[] mTmp = new int[2];
@@ -112,19 +115,24 @@
         setClipChildren(false);
         setClipToPadding(false);
         mShelfIcons.setIsStaticLayout(false);
-        requestBottomRoundness(1.0f, /* animate = */ false, SourceType.DefaultValue);
-        requestTopRoundness(1f, false, SourceType.DefaultValue);
+        requestRoundness(/* top = */ 1f, /* bottom = */ 1f, BASE_VALUE, /* animate = */ false);
 
-        // Setting this to first in section to get the clipping to the top roundness correct. This
-        // value determines the way we are clipping to the top roundness of the overall shade
-        setFirstInSection(true);
+        if (!mUseRoundnessSourceTypes) {
+            // Setting this to first in section to get the clipping to the top roundness correct.
+            // This value determines the way we are clipping to the top roundness of the overall
+            // shade
+            setFirstInSection(true);
+        }
         updateResources();
     }
 
     public void bind(AmbientState ambientState,
-            NotificationStackScrollLayoutController hostLayoutController) {
+                     NotificationStackScrollLayoutController hostLayoutController) {
         mAmbientState = ambientState;
         mHostLayoutController = hostLayoutController;
+        hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> {
+            child.requestRoundnessReset(SHELF_SCROLL);
+        });
     }
 
     private void updateResources() {
@@ -185,9 +193,11 @@
                 + " indexOfFirstViewInShelf=" + mIndexOfFirstViewInShelf + ')';
     }
 
-    /** Update the state of the shelf. */
+    /**
+     * Update the state of the shelf.
+     */
     public void updateState(StackScrollAlgorithm.StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                            AmbientState ambientState) {
         ExpandableView lastView = ambientState.getLastVisibleBackgroundChild();
         ShelfState viewState = (ShelfState) getViewState();
         if (mShowNotificationShelf && lastView != null) {
@@ -246,7 +256,7 @@
 
     /**
      * @param fractionToShade Fraction of lockscreen to shade transition
-     * @param shortestWidth Shortest width to use for lockscreen shelf
+     * @param shortestWidth   Shortest width to use for lockscreen shelf
      */
     @VisibleForTesting
     public void updateActualWidth(float fractionToShade, float shortestWidth) {
@@ -281,9 +291,9 @@
 
     /**
      * @param localX Click x from left of screen
-     * @param slop Margin of error within which we count x for valid click
-     * @param left Left of shelf, from left of screen
-     * @param right Right of shelf, from left of screen
+     * @param slop   Margin of error within which we count x for valid click
+     * @param left   Left of shelf, from left of screen
+     * @param right  Right of shelf, from left of screen
      * @return Whether click x was in view
      */
     @VisibleForTesting
@@ -293,8 +303,8 @@
 
     /**
      * @param localY Click y from top of shelf
-     * @param slop Margin of error within which we count y for valid click
-     * @param top Top of shelf
+     * @param slop   Margin of error within which we count y for valid click
+     * @param top    Top of shelf
      * @param bottom Height of shelf
      * @return Whether click y was in view
      */
@@ -306,7 +316,7 @@
     /**
      * @param localX Click x
      * @param localY Click y
-     * @param slop Margin of error for valid click
+     * @param slop   Margin of error for valid click
      * @return Whether this click was on the visible (non-clipped) part of the shelf
      */
     @Override
@@ -478,13 +488,15 @@
         }
     }
 
-    private void updateCornerRoundnessOnScroll(ActivatableNotificationView anv, float viewStart,
+    private void updateCornerRoundnessOnScroll(
+            ActivatableNotificationView anv,
+            float viewStart,
             float shelfStart) {
 
         final boolean isUnlockedHeadsUp = !mAmbientState.isOnKeyguard()
                 && !mAmbientState.isShadeExpanded()
                 && anv instanceof ExpandableNotificationRow
-                && ((ExpandableNotificationRow) anv).isHeadsUp();
+                && anv.isHeadsUp();
 
         final boolean isHunGoingToShade = mAmbientState.isShadeExpanded()
                 && anv == mAmbientState.getTrackedHeadsUpRow();
@@ -506,41 +518,40 @@
                 * mAmbientState.getExpansionFraction();
         final float cornerAnimationTop = shelfStart - cornerAnimationDistance;
 
-        if (viewEnd >= cornerAnimationTop) {
-            // Round bottom corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
-                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestBottomRoundness(
-                    /* value = */ anv.isLastInSection() ? 1f : changeFraction,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
-
-        } else if (viewEnd < cornerAnimationTop) {
-            // Fast scroll skips frames and leaves corners with unfinished rounding.
-            // Reset top and bottom corners outside of animation bounds.
-            anv.requestBottomRoundness(
-                    /* value = */ anv.isLastInSection() ? 1f : 0f,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
+        final SourceType sourceType;
+        if (mUseRoundnessSourceTypes) {
+            sourceType = SHELF_SCROLL;
+        } else {
+            sourceType = LegacySourceType.OnScroll;
         }
 
-        if (viewStart >= cornerAnimationTop) {
+        final float topValue;
+        if (!mUseRoundnessSourceTypes && anv.isFirstInSection()) {
+            topValue = 1f;
+        } else if (viewStart >= cornerAnimationTop) {
             // Round top corners within animation bounds
-            final float changeFraction = MathUtils.saturate(
+            topValue = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
-            anv.requestTopRoundness(
-                    /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
-
-        } else if (viewStart < cornerAnimationTop) {
+        } else {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
-            anv.requestTopRoundness(
-                    /* value = */ anv.isFirstInSection() ? 1f : 0f,
-                    /* animate = */ false,
-                    SourceType.OnScroll);
+            topValue = 0f;
         }
+        anv.requestTopRoundness(topValue, sourceType, /* animate = */ false);
+
+        final float bottomValue;
+        if (!mUseRoundnessSourceTypes && anv.isLastInSection()) {
+            bottomValue = 1f;
+        } else if (viewEnd >= cornerAnimationTop) {
+            // Round bottom corners within animation bounds
+            bottomValue = MathUtils.saturate(
+                    (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
+        } else {
+            // Fast scroll skips frames and leaves corners with unfinished rounding.
+            // Reset top and bottom corners outside of animation bounds.
+            bottomValue = 0f;
+        }
+        anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false);
     }
 
     /**
@@ -626,10 +637,11 @@
 
     /**
      * Update the clipping of this view.
+     *
      * @return the amount that our own top should be clipped
      */
     private int updateNotificationClipHeight(ExpandableView view,
-            float notificationClipEnd, int childIndex) {
+                                             float notificationClipEnd, int childIndex) {
         float viewEnd = view.getTranslationY() + view.getActualHeight();
         boolean isPinned = (view.isPinned() || view.isHeadsUpAnimatingAway())
                 && !mAmbientState.isDozingAndNotPulsing(view);
@@ -657,7 +669,7 @@
 
     @Override
     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
-            int outlineTranslation) {
+                                       int outlineTranslation) {
         if (!mHasItemsInStableShelf) {
             shadowIntensity = 0.0f;
         }
@@ -665,18 +677,24 @@
     }
 
     /**
-     * @param i Index of the view in the host layout.
-     * @param view The current ExpandableView.
-     * @param scrollingFast Whether we are scrolling fast.
+     * @param i                 Index of the view in the host layout.
+     * @param view              The current ExpandableView.
+     * @param scrollingFast     Whether we are scrolling fast.
      * @param expandingAnimated Whether we are expanding a notification.
-     * @param isLastChild Whether this is the last view.
-     * @param shelfClipStart The point at which notifications start getting clipped by the shelf.
+     * @param isLastChild       Whether this is the last view.
+     * @param shelfClipStart    The point at which notifications start getting clipped by the shelf.
      * @return The amount how much this notification is in the shelf.
-     *         0f is not in shelf. 1f is completely in shelf.
+     * 0f is not in shelf. 1f is completely in shelf.
      */
     @VisibleForTesting
-    public float getAmountInShelf(int i, ExpandableView view, boolean scrollingFast,
-            boolean expandingAnimated, boolean isLastChild, float shelfClipStart) {
+    public float getAmountInShelf(
+            int i,
+            ExpandableView view,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild,
+            float shelfClipStart
+    ) {
 
         // Let's calculate how much the view is in the shelf
         float viewStart = view.getTranslationY();
@@ -755,8 +773,13 @@
         return start;
     }
 
-    private void updateIconPositioning(ExpandableView view, float iconTransitionAmount,
-            boolean scrollingFast, boolean expandingAnimated, boolean isLastChild) {
+    private void updateIconPositioning(
+            ExpandableView view,
+            float iconTransitionAmount,
+            boolean scrollingFast,
+            boolean expandingAnimated,
+            boolean isLastChild
+    ) {
         StatusBarIconView icon = view.getShelfIcon();
         NotificationIconContainer.IconState iconState = getIconState(icon);
         if (iconState == null) {
@@ -817,7 +840,7 @@
                 || row.showingPulsing()
                 || row.getTranslationZ() > mAmbientState.getBaseZHeight();
 
-        iconState.iconAppearAmount = iconState.hidden? 0f : transitionAmount;
+        iconState.iconAppearAmount = iconState.hidden ? 0f : transitionAmount;
 
         // Fade in icons at shelf start
         // This is important for conversation icons, which are badged and need x reset
@@ -847,7 +870,7 @@
     }
 
     private float getFullyClosedTranslation() {
-        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+        return -(getIntrinsicHeight() - mStatusBarHeight) / 2;
     }
 
     @Override
@@ -904,7 +927,7 @@
 
     /**
      * @return whether the shelf has any icons in it when a potential animation has finished, i.e
-     *         if the current state would be applied right now
+     * if the current state would be applied right now
      */
     public boolean hasItemsInStableShelf() {
         return mHasItemsInStableShelf;
@@ -962,7 +985,7 @@
 
     @Override
     public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
-            int oldTop, int oldRight, int oldBottom) {
+                               int oldTop, int oldRight, int oldBottom) {
         updateRelativeOffset();
     }
 
@@ -981,12 +1004,11 @@
 
     /**
      * This method resets the OnScroll roundness of a view to 0f
-     *
+     * <p>
      * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
      */
-    public static void resetOnScrollRoundness(ExpandableView expandableView) {
-        expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
-        expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+    public static void resetLegacyOnScrollRoundness(ExpandableView expandableView) {
+        expandableView.requestRoundnessReset(LegacySourceType.OnScroll);
     }
 
     public class ShelfState extends ExpandableViewState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
index 3b1fa17..bb84c75 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.java
@@ -18,6 +18,8 @@
 
 import android.view.View;
 
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowScope;
@@ -42,14 +44,17 @@
     private AmbientState mAmbientState;
 
     @Inject
-    public NotificationShelfController(NotificationShelf notificationShelf,
+    public NotificationShelfController(
+            NotificationShelf notificationShelf,
             ActivatableNotificationViewController activatableNotificationViewController,
             KeyguardBypassController keyguardBypassController,
-            SysuiStatusBarStateController statusBarStateController) {
+            SysuiStatusBarStateController statusBarStateController,
+            FeatureFlags featureFlags) {
         mView = notificationShelf;
         mActivatableNotificationViewController = activatableNotificationViewController;
         mKeyguardBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
+        mView.useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
         mOnAttachStateChangeListener = new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -88,7 +93,7 @@
 
     public @View.Visibility int getVisibility() {
         return mView.getVisibility();
-    };
+    }
 
     public void setCollapsedIcons(NotificationIconContainer notificationIcons) {
         mView.setCollapsedIcons(notificationIcons);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index 37d83f8..336356e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -78,6 +78,7 @@
 import com.android.systemui.plugins.log.LogLevel;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DataSaverController;
 import com.android.systemui.statusbar.policy.DataSaverControllerImpl;
@@ -192,6 +193,7 @@
     private final Executor mBgExecutor;
     // Handler that all callbacks are made on.
     private final CallbackHandler mCallbackHandler;
+    private final StatusBarPipelineFlags mStatusBarPipelineFlags;
 
     private int mEmergencySource;
     private boolean mIsEmergency;
@@ -242,6 +244,7 @@
             TelephonyListenerManager telephonyListenerManager,
             @Nullable WifiManager wifiManager,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DemoModeController demoModeController,
             CarrierConfigTracker carrierConfigTracker,
             WifiStatusTrackerFactory trackerFactory,
@@ -260,6 +263,7 @@
                 bgExecutor,
                 callbackHandler,
                 accessPointController,
+                statusBarPipelineFlags,
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController,
@@ -287,6 +291,7 @@
             Executor bgExecutor,
             CallbackHandler callbackHandler,
             AccessPointControllerImpl accessPointController,
+            StatusBarPipelineFlags statusBarPipelineFlags,
             DataUsageController dataUsageController,
             SubscriptionDefaults defaultsHandler,
             DeviceProvisionedController deviceProvisionedController,
@@ -308,6 +313,7 @@
         mBgLooper = bgLooper;
         mBgExecutor = bgExecutor;
         mCallbackHandler = callbackHandler;
+        mStatusBarPipelineFlags = statusBarPipelineFlags;
         mDataSaverController = new DataSaverControllerImpl(context);
         mBroadcastDispatcher = broadcastDispatcher;
         mMobileFactory = mobileFactory;
@@ -1333,7 +1339,7 @@
             mWifiSignalController.notifyListeners();
         }
         String sims = args.getString("sims");
-        if (sims != null) {
+        if (sims != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             int num = MathUtils.constrain(Integer.parseInt(sims), 1, 8);
             List<SubscriptionInfo> subs = new ArrayList<>();
             if (num != mMobileSignalControllers.size()) {
@@ -1356,7 +1362,7 @@
             mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
         String mobile = args.getString("mobile");
-        if (mobile != null) {
+        if (mobile != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = mobile.equals("show");
             String datatype = args.getString("datatype");
             String slotString = args.getString("slot");
@@ -1441,7 +1447,7 @@
             controller.notifyListeners();
         }
         String carrierNetworkChange = args.getString("carriernetworkchange");
-        if (carrierNetworkChange != null) {
+        if (carrierNetworkChange != null && !mStatusBarPipelineFlags.useNewMobileIcons()) {
             boolean show = carrierNetworkChange.equals("show");
             for (int i = 0; i < mMobileSignalControllers.size(); i++) {
                 MobileSignalController controller = mMobileSignalControllers.valueAt(i);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 6bd9502..8bb2d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -102,6 +102,8 @@
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
 
+    // TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
+    //  how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
     private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
 
     // TODO: Move logic into SmartspaceView
@@ -109,16 +111,19 @@
         override fun onViewAttachedToWindow(v: View) {
             smartspaceViews.add(v as SmartspaceView)
 
-            var regionSampler = RegionSampler(
-                    v,
-                    uiExecutor,
-                    bgExecutor,
-                    regionSamplingEnabled,
-                    updateFun
-            )
-            initializeTextColors(regionSampler)
-            regionSampler.startRegionSampler()
-            regionSamplers.put(v, regionSampler)
+            if (regionSamplingEnabled) {
+                var regionSampler = RegionSampler(
+                        v,
+                        uiExecutor,
+                        bgExecutor,
+                        regionSamplingEnabled,
+                        updateFun
+                )
+                initializeTextColors(regionSampler)
+                regionSampler.startRegionSampler()
+                regionSamplers.put(v, regionSampler)
+            }
+
             connectSession()
 
             updateTextColorFromWallpaper()
@@ -128,9 +133,11 @@
         override fun onViewDetachedFromWindow(v: View) {
             smartspaceViews.remove(v as SmartspaceView)
 
-            var regionSampler = regionSamplers.getValue(v)
-            regionSampler.stopRegionSampler()
-            regionSamplers.remove(v)
+            if (regionSamplingEnabled) {
+                var regionSampler = regionSamplers.getValue(v)
+                regionSampler.stopRegionSampler()
+                regionSamplers.remove(v)
+            }
 
             if (smartspaceViews.isEmpty()) {
                 disconnect()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
index ed7f648..0eb0000 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/Roundable.kt
@@ -74,8 +74,8 @@
     @JvmDefault
     fun requestTopRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.topRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -105,6 +105,30 @@
     }
 
     /**
+     * Request the top roundness [value] for a specific [sourceType]. Animate the roundness if the
+     * view is shown.
+     *
+     * The top roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value a value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestTopRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestTopRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
      * Request the bottom roundness [value] for a specific [sourceType].
      *
      * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
@@ -119,8 +143,8 @@
     @JvmDefault
     fun requestBottomRoundness(
         @FloatRange(from = 0.0, to = 1.0) value: Float,
-        animate: Boolean,
         sourceType: SourceType,
+        animate: Boolean,
     ): Boolean {
         val roundnessMap = roundableState.bottomRoundnessMap
         val lastValue = roundnessMap.values.maxOrNull() ?: 0f
@@ -149,9 +173,101 @@
         return false
     }
 
+    /**
+     * Request the bottom roundness [value] for a specific [sourceType]. Animate the roundness if
+     * the view is shown.
+     *
+     * The bottom roundness of a [Roundable] can be defined by different [sourceType]. In case more
+     * origins require different roundness, for the same property, the maximum value will always be
+     * chosen.
+     *
+     * @param value value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestBottomRoundness(
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestBottomRoundness(
+            value = value,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown
+        )
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType].
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @param animate true if it should animate to that value.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+        animate: Boolean,
+    ): Boolean {
+        val hasTopChanged =
+            requestTopRoundness(value = top, sourceType = sourceType, animate = animate)
+        val hasBottomChanged =
+            requestBottomRoundness(value = bottom, sourceType = sourceType, animate = animate)
+        return hasTopChanged || hasBottomChanged
+    }
+
+    /**
+     * Request the roundness [value] for a specific [sourceType]. Animate the roundness if the view
+     * is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param top top value between 0f and 1f.
+     * @param bottom bottom value between 0f and 1f.
+     * @param sourceType the source from which the request for roundness comes.
+     * @return Whether the roundness was changed.
+     */
+    @JvmDefault
+    fun requestRoundness(
+        @FloatRange(from = 0.0, to = 1.0) top: Float,
+        @FloatRange(from = 0.0, to = 1.0) bottom: Float,
+        sourceType: SourceType,
+    ): Boolean {
+        return requestRoundness(
+            top = top,
+            bottom = bottom,
+            sourceType = sourceType,
+            animate = roundableState.targetView.isShown,
+        )
+    }
+
+    /**
+     * Request the roundness 0f for a [SourceType]. Animate the roundness if the view is shown.
+     *
+     * The top/bottom roundness of a [Roundable] can be defined by different [sourceType]. In case
+     * more origins require different roundness, for the same property, the maximum value will
+     * always be chosen.
+     *
+     * @param sourceType the source from which the request for roundness comes.
+     */
+    @JvmDefault
+    fun requestRoundnessReset(sourceType: SourceType) {
+        requestRoundness(top = 0f, bottom = 0f, sourceType = sourceType)
+    }
+
     /** Apply the roundness changes, usually means invalidate the [RoundableState.targetView]. */
     @JvmDefault
-    fun applyRoundness() {
+    fun applyRoundnessAndInvalidate() {
         roundableState.targetView.invalidate()
     }
 
@@ -227,7 +343,7 @@
     /** Set the current top roundness */
     internal fun setTopRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, topAnimatable, value, DURATION, animated)
     }
@@ -235,11 +351,19 @@
     /** Set the current bottom roundness */
     internal fun setBottomRoundness(
         value: Float,
-        animated: Boolean = targetView.isShown,
+        animated: Boolean,
     ) {
         PropertyAnimator.setProperty(targetView, bottomAnimatable, value, DURATION, animated)
     }
 
+    fun debugString() = buildString {
+        append("TargetView: ${targetView.hashCode()} ")
+        append("Top: $topRoundness ")
+        append(topRoundnessMap.map { "${it.key} ${it.value}" })
+        append(" Bottom: $bottomRoundness ")
+        append(bottomRoundnessMap.map { "${it.key} ${it.value}" })
+    }
+
     companion object {
         private val DURATION: AnimationProperties =
             AnimationProperties()
@@ -252,7 +376,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.topRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.top_roundess_animator_tag,
@@ -267,7 +391,7 @@
 
                     override fun setValue(view: View, value: Float) {
                         roundable.roundableState.bottomRoundness = value
-                        roundable.applyRoundness()
+                        roundable.applyRoundnessAndInvalidate()
                     }
                 },
                 R.id.bottom_roundess_animator_tag,
@@ -277,7 +401,31 @@
     }
 }
 
-enum class SourceType {
+/**
+ * Interface used to define the owner of a roundness. Usually the [SourceType] is defined as a
+ * private property of a class.
+ */
+interface SourceType {
+    companion object {
+        /**
+         * This is the most convenient way to define a new [SourceType].
+         *
+         * For example:
+         *
+         * ```kotlin
+         *     private val SECTION = SourceType.from("Section")
+         * ```
+         */
+        @JvmStatic
+        fun from(name: String) =
+            object : SourceType {
+                override fun toString() = name
+            }
+    }
+}
+
+@Deprecated("Use SourceType.from() instead", ReplaceWith("SourceType.from()"))
+enum class LegacySourceType : SourceType {
     DefaultValue,
     OnDismissAnimation,
     OnScroll,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index d29298a..fbe88df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -39,9 +39,13 @@
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
+import java.util.HashSet;
+import java.util.Set;
+
 /**
  * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
  * to implement dimming/activating on Keyguard for the double-tap gesture
@@ -91,6 +95,7 @@
             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
             = new PathInterpolator(0, 0, 0.5f, 1);
+    private final Set<SourceType> mOnDetachResetRoundness = new HashSet<>();
     private int mTintedRippleColor;
     private int mNormalRippleColor;
     private Gefingerpoken mTouchHandler;
@@ -134,6 +139,7 @@
     private boolean mDismissed;
     private boolean mRefocusOnDismiss;
     private AccessibilityManager mAccessibilityManager;
+    protected boolean mUseRoundnessSourceTypes;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -613,9 +619,9 @@
     protected void resetAllContentAlphas() {}
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyBackgroundRoundness(getTopCornerRadius(), getBottomCornerRadius());
+        super.applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -775,6 +781,33 @@
         mAccessibilityManager = accessibilityManager;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (mUseRoundnessSourceTypes && !mOnDetachResetRoundness.isEmpty()) {
+            for (SourceType sourceType : mOnDetachResetRoundness) {
+                requestRoundnessReset(sourceType);
+            }
+            mOnDetachResetRoundness.clear();
+        }
+    }
+
+    /**
+     * SourceType which should be reset when this View is detached
+     * @param sourceType will be reset on View detached
+     */
+    public void addOnDetachResetRoundness(SourceType sourceType) {
+        mOnDetachResetRoundness.add(sourceType);
+    }
+
     public interface OnActivatedListener {
         void onActivated(ActivatableNotificationView view);
         void onActivationReset(ActivatableNotificationView view);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index d7d5ac9..c7c1634 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -91,6 +91,7 @@
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -143,6 +144,9 @@
     private static final int MENU_VIEW_INDEX = 0;
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
+    private static final SourceType BASE_VALUE = SourceType.from("BaseValue");
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(ENR)");
+    private static final SourceType PINNED = SourceType.from("Pinned");
 
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
@@ -150,6 +154,7 @@
     private boolean mNotificationTranslationFinished = false;
     private boolean mIsSnoozed;
     private boolean mIsFaded;
+    private boolean mAnimatePinnedRoundness = false;
 
     /**
      * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -376,7 +381,7 @@
 
     private float mTopRoundnessDuringLaunchAnimation;
     private float mBottomRoundnessDuringLaunchAnimation;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private float mSmallRoundness;
 
     /**
      * Returns whether the given {@code statusBarNotification} is a system notification.
@@ -844,7 +849,9 @@
         }
         onAttachedChildrenCountChanged();
         row.setIsChildInGroup(false, null);
-        row.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+        if (!mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(0.0f, LegacySourceType.DefaultValue, /* animate = */ false);
+        }
     }
 
     /**
@@ -860,7 +867,10 @@
             if (child.keepInParentForDismissAnimation()) {
                 mChildrenContainer.removeNotification(child);
                 child.setIsChildInGroup(false, null);
-                child.requestBottomRoundness(0.0f, /* animate = */ false, SourceType.DefaultValue);
+                if (!mUseRoundnessSourceTypes) {
+                    LegacySourceType sourceType = LegacySourceType.DefaultValue;
+                    child.requestBottomRoundness(0f, sourceType, /* animate = */ false);
+                }
                 child.setKeepInParentForDismissAnimation(false);
                 logKeepInParentChildDetached(child);
                 childCountChanged = true;
@@ -915,6 +925,9 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateBackgroundClipping();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
+        }
     }
 
     @Override
@@ -1033,6 +1046,16 @@
         if (isAboveShelf() != wasAboveShelf) {
             mAboveShelfChangedListener.onAboveShelfStateChanged(!wasAboveShelf);
         }
+        if (mUseRoundnessSourceTypes) {
+            if (pinned) {
+                // Should be animated if someone explicitly set it to 0 and the row is shown.
+                boolean animated = mAnimatePinnedRoundness && isShown();
+                requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PINNED, animated);
+            } else {
+                requestRoundnessReset(PINNED);
+                mAnimatePinnedRoundness = true;
+            }
+        }
     }
 
     @Override
@@ -1607,6 +1630,8 @@
         super(context, attrs);
         mImageResolver = new NotificationInlineImageResolver(context,
                 new NotificationInlineImageCache());
+        float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
+        mSmallRoundness = radius / getMaxRadius();
         initDimens();
     }
 
@@ -1839,7 +1864,7 @@
             mChildrenContainer.setIsLowPriority(mIsLowPriority);
             mChildrenContainer.setContainingNotification(ExpandableNotificationRow.this);
             mChildrenContainer.onNotificationUpdated();
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
 
             mTranslateableViews.add(mChildrenContainer);
         });
@@ -2271,7 +2296,7 @@
 
     @Override
     public float getTopRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mTopRoundnessDuringLaunchAnimation;
         }
 
@@ -2280,7 +2305,7 @@
 
     @Override
     public float getBottomRoundness() {
-        if (mExpandAnimationRunning) {
+        if (!mUseRoundnessSourceTypes && mExpandAnimationRunning) {
             return mBottomRoundnessDuringLaunchAnimation;
         }
 
@@ -3436,17 +3461,24 @@
     }
 
     @Override
-    public void applyRoundness() {
-        super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         applyChildrenRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     private void applyChildrenRoundness() {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.requestBottomRoundness(
-                    getBottomRoundness(),
-                    /* animate = */ false,
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                mChildrenContainer.requestRoundness(
+                        /* top = */ getTopRoundness(),
+                        /* bottom = */ getBottomRoundness(),
+                        FROM_PARENT);
+            } else {
+                mChildrenContainer.requestBottomRoundness(
+                        getBottomRoundness(),
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ false);
+            }
         }
     }
 
@@ -3605,6 +3637,7 @@
             } else {
                 pw.println("no viewState!!!");
             }
+            pw.println("Roundness: " + getRoundableState().debugString());
 
             if (mIsSummaryWithChildren) {
                 pw.println();
@@ -3649,14 +3682,38 @@
         return mTargetPoint;
     }
 
+    /** Update the minimum roundness based on current state */
+    private void updateBaseRoundness() {
+        if (isChildInGroup()) {
+            requestRoundnessReset(BASE_VALUE);
+        } else {
+            requestRoundness(mSmallRoundness, mSmallRoundness, BASE_VALUE);
+        }
+    }
+
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    @Override
+    public void useRoundnessSourceTypes(boolean enabled) {
+        super.useRoundnessSourceTypes(enabled);
         if (mChildrenContainer != null) {
-            mChildrenContainer.enableNotificationGroupCorner(mIsNotificationGroupCornerEnabled);
+            mChildrenContainer.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        }
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "ExpandableNotificationRow:" + hashCode() + " { " + roundableStateDebug + " }";
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (mUseRoundnessSourceTypes) {
+            updateBaseRoundness();
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 8a400d5..d113860 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -255,8 +255,8 @@
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
             }
         });
-        mView.enableNotificationGroupCorner(
-                mFeatureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER));
+        mView.useRoundnessSourceTypes(
+                mFeatureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES));
     }
 
     private final StatusBarStateController.StateListener mStatusBarStateListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
index 2324627..0213b96 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableOutlineView.java
@@ -219,9 +219,9 @@
     }
 
     @Override
-    public void applyRoundness() {
+    public void applyRoundnessAndInvalidate() {
         invalidateOutline();
-        super.applyRoundness();
+        super.applyRoundnessAndInvalidate();
     }
 
     protected void setBackgroundTop(int backgroundTop) {
@@ -233,7 +233,7 @@
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     @Override
@@ -241,7 +241,7 @@
         int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
         if (previousHeight != actualHeight) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -250,7 +250,7 @@
         int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
         if (previousAmount != clipTopAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -259,14 +259,14 @@
         int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
         if (previousAmount != clipBottomAmount) {
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -280,7 +280,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            applyRoundness();
+            applyRoundnessAndInvalidate();
         }
     }
 
@@ -340,7 +340,7 @@
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-        applyRoundness();
+        applyRoundnessAndInvalidate();
     }
 
     public Path getCustomClipPath(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index f13e48d..1f664cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -71,6 +71,8 @@
     private View mFeedbackIcon;
     private boolean mIsLowPriority;
     private boolean mTransformLowPriorityTitle;
+    private boolean mUseRoundnessSourceTypes;
+    private RoundnessChangedListener mRoundnessChangedListener;
 
     protected NotificationHeaderViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -117,6 +119,20 @@
         return mRoundableState;
     }
 
+    @Override
+    public void applyRoundnessAndInvalidate() {
+        if (mUseRoundnessSourceTypes && mRoundnessChangedListener != null) {
+            // We cannot apply the rounded corner to this View, so our parents (in drawChild()) will
+            // clip our canvas. So we should invalidate our parent.
+            mRoundnessChangedListener.applyRoundnessAndInvalidate();
+        }
+        Roundable.super.applyRoundnessAndInvalidate();
+    }
+
+    public void setOnRoundnessChangedListener(RoundnessChangedListener listener) {
+        mRoundnessChangedListener = listener;
+    }
+
     protected void resolveHeaderViews() {
         mIcon = mView.findViewById(com.android.internal.R.id.icon);
         mHeaderText = mView.findViewById(com.android.internal.R.id.header_text);
@@ -343,4 +359,23 @@
             }
         }
     }
+
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     *
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    /**
+     * Interface that handle the Roundness changes
+     */
+    public interface RoundnessChangedListener {
+        /**
+         * This method will be called when this class call applyRoundnessAndInvalidate()
+         */
+        void applyRoundnessAndInvalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index d43ca823..4a8e2db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -47,6 +47,7 @@
 import com.android.systemui.statusbar.NotificationGroupingUtil;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.Roundable;
@@ -83,6 +84,7 @@
             return mAnimationFilter;
         }
     }.setDuration(200);
+    private static final SourceType FROM_PARENT = SourceType.from("FromParent(NCC)");
 
     private final List<View> mDividers = new ArrayList<>();
     private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
@@ -131,7 +133,7 @@
     private int mUntruncatedChildCount;
     private boolean mContainingNotificationIsFaded = false;
     private RoundableState mRoundableState;
-    private boolean mIsNotificationGroupCornerEnabled;
+    private boolean mUseRoundnessSourceTypes;
 
     public NotificationChildrenContainer(Context context) {
         this(context, null);
@@ -313,9 +315,12 @@
         row.setContentTransformationAmount(0, false /* isLastChild */);
         row.setNotificationFaded(mContainingNotificationIsFaded);
 
-        // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
-        // Here we should reset the `OnScroll` roundness only on top-level rows.
-        NotificationShelf.resetOnScrollRoundness(row);
+        if (!mUseRoundnessSourceTypes) {
+            // This is a workaround, the NotificationShelf should be the owner of `OnScroll`
+            // roundness.
+            // Here we should reset the `OnScroll` roundness only on top-level rows.
+            NotificationShelf.resetLegacyOnScrollRoundness(row);
+        }
 
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
@@ -323,6 +328,10 @@
             viewState.cancelAnimations(row);
             row.cancelAppearDrawing();
         }
+
+        if (mUseRoundnessSourceTypes) {
+            applyRoundnessAndInvalidate();
+        }
     }
 
     private void ensureRemovedFromTransientContainer(View v) {
@@ -356,6 +365,11 @@
         if (!row.isRemoved()) {
             mGroupingUtil.restoreChildNotification(row);
         }
+
+        if (mUseRoundnessSourceTypes) {
+            row.requestRoundnessReset(FROM_PARENT);
+            applyRoundnessAndInvalidate();
+        }
     }
 
     /**
@@ -382,6 +396,10 @@
                             getContext(),
                             mNotificationHeader,
                             mContainingNotification);
+            mNotificationHeaderWrapper.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+            if (mUseRoundnessSourceTypes) {
+                mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+            }
             addView(mNotificationHeader, 0);
             invalidate();
         } else {
@@ -419,6 +437,12 @@
                                 getContext(),
                                 mNotificationHeaderLowPriority,
                                 mContainingNotification);
+                mNotificationHeaderWrapperLowPriority.useRoundnessSourceTypes(
+                        mUseRoundnessSourceTypes
+                );
+                if (mUseRoundnessSourceTypes) {
+                    mNotificationHeaderWrapper.setOnRoundnessChangedListener(this::invalidate);
+                }
                 addView(mNotificationHeaderLowPriority, 0);
                 invalidate();
             } else {
@@ -841,7 +865,7 @@
 
             isCanvasChanged = true;
             canvas.save();
-            if (mIsNotificationGroupCornerEnabled && translation != 0f) {
+            if (mUseRoundnessSourceTypes && translation != 0f) {
                 clipPath.offset(translation, 0f);
                 canvas.clipPath(clipPath);
                 clipPath.offset(-translation, 0f);
@@ -1392,24 +1416,28 @@
     }
 
     @Override
-    public void applyRoundness() {
-        Roundable.super.applyRoundness();
+    public void applyRoundnessAndInvalidate() {
         boolean last = true;
         for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mAttachedChildren.get(i);
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
-            child.requestTopRoundness(
-                    /* value = */ 0f,
-                    /* animate = */ isShown(),
-                    SourceType.DefaultValue);
-            child.requestBottomRoundness(
-                    /* value = */ last ? getBottomRoundness() : 0f,
-                    /* animate = */ isShown(),
-                    SourceType.DefaultValue);
+            if (mUseRoundnessSourceTypes) {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        FROM_PARENT);
+            } else {
+                child.requestRoundness(
+                        /* top = */ 0f,
+                        /* bottom = */ last ? getBottomRoundness() : 0f,
+                        LegacySourceType.DefaultValue,
+                        /* animate = */ isShown());
+            }
             last = false;
         }
+        Roundable.super.applyRoundnessAndInvalidate();
     }
 
     public void setHeaderVisibleAmount(float headerVisibleAmount) {
@@ -1467,10 +1495,17 @@
     }
 
     /**
-     * Enable the support for rounded corner in notification group
+     * Enable the support for rounded corner based on the SourceType
+     *
      * @param enabled true if is supported
      */
-    public void enableNotificationGroupCorner(boolean enabled) {
-        mIsNotificationGroupCornerEnabled = enabled;
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
+    @Override
+    public String toString() {
+        String roundableStateDebug = "RoundableState = " + getRoundableState().debugString();
+        return "NotificationChildrenContainer:" + hashCode() + " { " + roundableStateDebug + " }";
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
index 6810055..fde8c4d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManager.java
@@ -25,6 +25,9 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.Roundable;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -45,6 +48,7 @@
 public class NotificationRoundnessManager implements Dumpable {
 
     private static final String TAG = "NotificationRoundnessManager";
+    private static final SourceType DISMISS_ANIMATION = SourceType.from("DismissAnimation");
 
     private final ExpandableView[] mFirstInSectionViews;
     private final ExpandableView[] mLastInSectionViews;
@@ -63,12 +67,14 @@
     private ExpandableView mSwipedView = null;
     private Roundable mViewBeforeSwipedView = null;
     private Roundable mViewAfterSwipedView = null;
+    private boolean mUseRoundnessSourceTypes;
 
     @Inject
     NotificationRoundnessManager(
             NotificationSectionsFeatureManager sectionsFeatureManager,
             NotificationRoundnessLogger notifLogger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         int numberOfSections = sectionsFeatureManager.getNumberOfBuckets();
         mFirstInSectionViews = new ExpandableView[numberOfSections];
         mLastInSectionViews = new ExpandableView[numberOfSections];
@@ -76,6 +82,7 @@
         mTmpLastInSectionViews = new ExpandableView[numberOfSections];
         mNotifLogger = notifLogger;
         mDumpManager = dumpManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
 
         mDumpManager.registerDumpable(TAG, this);
     }
@@ -94,6 +101,7 @@
     }
 
     public void updateView(ExpandableView view, boolean animate) {
+        if (mUseRoundnessSourceTypes) return;
         boolean changed = updateViewWithoutCallback(view, animate);
         if (changed) {
             mRoundingChangedCallback.run();
@@ -110,6 +118,7 @@
     boolean updateViewWithoutCallback(
             ExpandableView view,
             boolean animate) {
+        if (mUseRoundnessSourceTypes) return false;
         if (view == null
                 || view == mViewBeforeSwipedView
                 || view == mViewAfterSwipedView) {
@@ -118,13 +127,13 @@
 
         final boolean isTopChanged = view.requestTopRoundness(
                 getRoundnessDefaultValue(view, true /* top */),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isBottomChanged = view.requestBottomRoundness(
                 getRoundnessDefaultValue(view, /* top = */ false),
-                animate,
-                SourceType.DefaultValue);
+                LegacySourceType.DefaultValue,
+                animate);
 
         final boolean isFirstInSection = isFirstInSection(view);
         final boolean isLastInSection = isLastInSection(view);
@@ -139,6 +148,7 @@
     }
 
     private boolean isFirstInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = 0; i < mFirstInSectionViews.length; i++) {
             if (view == mFirstInSectionViews[i]) {
                 return true;
@@ -148,6 +158,7 @@
     }
 
     private boolean isLastInSection(ExpandableView view) {
+        if (mUseRoundnessSourceTypes) return false;
         for (int i = mLastInSectionViews.length - 1; i >= 0; i--) {
             if (view == mLastInSectionViews[i]) {
                 return true;
@@ -160,9 +171,6 @@
             Roundable viewBefore,
             ExpandableView viewSwiped,
             Roundable viewAfter) {
-        final boolean animate = true;
-        final SourceType source = SourceType.OnDismissAnimation;
-
         // This method requires you to change the roundness of the current View targets and reset
         // the roundness of the old View targets (if any) to 0f.
         // To avoid conflicts, it generates a set of old Views and removes the current Views
@@ -172,31 +180,34 @@
         if (mSwipedView != null) oldViews.add(mSwipedView);
         if (mViewAfterSwipedView != null) oldViews.add(mViewAfterSwipedView);
 
+        final SourceType source;
+        if (mUseRoundnessSourceTypes) {
+            source = DISMISS_ANIMATION;
+        } else {
+            source = LegacySourceType.OnDismissAnimation;
+        }
+
         mViewBeforeSwipedView = viewBefore;
         if (viewBefore != null) {
             oldViews.remove(viewBefore);
-            viewBefore.requestTopRoundness(0f, animate, source);
-            viewBefore.requestBottomRoundness(1f, animate, source);
+            viewBefore.requestRoundness(/* top = */ 0f, /* bottom = */ 1f, source);
         }
 
         mSwipedView = viewSwiped;
         if (viewSwiped != null) {
             oldViews.remove(viewSwiped);
-            viewSwiped.requestTopRoundness(1f, animate, source);
-            viewSwiped.requestBottomRoundness(1f, animate, source);
+            viewSwiped.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, source);
         }
 
         mViewAfterSwipedView = viewAfter;
         if (viewAfter != null) {
             oldViews.remove(viewAfter);
-            viewAfter.requestTopRoundness(1f, animate, source);
-            viewAfter.requestBottomRoundness(0f, animate, source);
+            viewAfter.requestRoundness(/* top = */ 1f, /* bottom = */ 0f, source);
         }
 
         // After setting the current Views, reset the views that are still present in the set.
         for (Roundable oldView : oldViews) {
-            oldView.requestTopRoundness(0f, animate, source);
-            oldView.requestBottomRoundness(0f, animate, source);
+            oldView.requestRoundnessReset(source);
         }
     }
 
@@ -204,7 +215,23 @@
         mIsClearAllInProgress = isClearingAll;
     }
 
+    /**
+     * Check if "Clear all" notifications is in progress.
+     */
+    public boolean isClearAllInProgress() {
+        return mIsClearAllInProgress;
+    }
+
+    /**
+     * Check if we can request the `Pulsing` roundness for notification.
+     */
+    public boolean shouldRoundNotificationPulsing() {
+        return mRoundForPulsingViews;
+    }
+
     private float getRoundnessDefaultValue(Roundable view, boolean top) {
+        if (mUseRoundnessSourceTypes) return 0f;
+
         if (view == null) {
             return 0f;
         }
@@ -250,6 +277,7 @@
     }
 
     public void setExpanded(float expandedHeight, float appearFraction) {
+        if (mUseRoundnessSourceTypes) return;
         mExpanded = expandedHeight != 0.0f;
         mAppearFraction = appearFraction;
         if (mTrackedHeadsUp != null) {
@@ -258,6 +286,7 @@
     }
 
     public void updateRoundedChildren(NotificationSection[] sections) {
+        if (mUseRoundnessSourceTypes) return;
         boolean anyChanged = false;
         for (int i = 0; i < sections.length; i++) {
             mTmpFirstInSectionViews[i] = mFirstInSectionViews[i];
@@ -280,6 +309,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (ExpandableView oldView : oldViews) {
             if (oldView != null) {
@@ -313,6 +343,7 @@
             NotificationSection[] sections,
             ExpandableView[] oldViews,
             boolean first) {
+        if (mUseRoundnessSourceTypes) return false;
         boolean anyChanged = false;
         for (NotificationSection section : sections) {
             ExpandableView newView =
@@ -339,6 +370,15 @@
         mAnimatedChildren = animatedChildren;
     }
 
+    /**
+     * Check if the view should be animated
+     * @param view target view
+     * @return true, if is in the AnimatedChildren set
+     */
+    public boolean isAnimatedChild(ExpandableView view) {
+        return mAnimatedChildren.contains(view);
+    }
+
     public void setOnRoundingChangedCallback(Runnable roundingChangedCallback) {
         mRoundingChangedCallback = roundingChangedCallback;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
index a1b77ac..070b439 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt
@@ -19,8 +19,11 @@
 import android.util.Log
 import android.view.View
 import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.ui.KeyguardMediaController
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.collection.render.MediaContainerController
 import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController
 import com.android.systemui.statusbar.notification.dagger.AlertingHeader
@@ -44,12 +47,16 @@
     private val keyguardMediaController: KeyguardMediaController,
     private val sectionsFeatureManager: NotificationSectionsFeatureManager,
     private val mediaContainerController: MediaContainerController,
+    private val notificationRoundnessManager: NotificationRoundnessManager,
     @IncomingHeader private val incomingHeaderController: SectionHeaderController,
     @PeopleHeader private val peopleHeaderController: SectionHeaderController,
     @AlertingHeader private val alertingHeaderController: SectionHeaderController,
-    @SilentHeader private val silentHeaderController: SectionHeaderController
+    @SilentHeader private val silentHeaderController: SectionHeaderController,
+    featureFlags: FeatureFlags
 ) : SectionProvider {
 
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
+
     private val configurationListener = object : ConfigurationController.ConfigurationListener {
         override fun onLocaleListChanged() {
             reinflateViews()
@@ -177,11 +184,49 @@
                         size = sections.size,
                         operation = SectionBounds::addNotif
                 )
+
+        // Build a set of the old first/last Views of the sections
+        val oldFirstChildren = sections.mapNotNull { it.firstVisibleChild }.toSet().toMutableSet()
+        val oldLastChildren = sections.mapNotNull { it.lastVisibleChild }.toSet().toMutableSet()
+
         // Update each section with the associated boundary, tracking if there was a change
         val changed = sections.fold(false) { changed, section ->
             val bounds = sectionBounds[section.bucket] ?: SectionBounds.None
-            bounds.updateSection(section) || changed
+            val isSectionChanged = bounds.updateSection(section)
+            isSectionChanged || changed
         }
+
+        if (useRoundnessSourceTypes) {
+            val newFirstChildren = sections.mapNotNull { it.firstVisibleChild }
+            val newLastChildren = sections.mapNotNull { it.lastVisibleChild }
+
+            // Update the roundness of Views that weren't already in the first/last position
+            newFirstChildren.forEach { firstChild ->
+                val wasFirstChild = oldFirstChildren.remove(firstChild)
+                if (!wasFirstChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(firstChild)
+                    val animated = firstChild.isShown && notAnimatedChild
+                    firstChild.requestTopRoundness(1f, SECTION, animated)
+                }
+            }
+            newLastChildren.forEach { lastChild ->
+                val wasLastChild = oldLastChildren.remove(lastChild)
+                if (!wasLastChild) {
+                    val notAnimatedChild = !notificationRoundnessManager.isAnimatedChild(lastChild)
+                    val animated = lastChild.isShown && notAnimatedChild
+                    lastChild.requestBottomRoundness(1f, SECTION, animated)
+                }
+            }
+
+            // The Views left in the set are no longer in the first/last position
+            oldFirstChildren.forEach { noMoreFirstChild ->
+                noMoreFirstChild.requestTopRoundness(0f, SECTION)
+            }
+            oldLastChildren.forEach { noMoreLastChild ->
+                noMoreLastChild.requestBottomRoundness(0f, SECTION)
+            }
+        }
+
         if (DEBUG) {
             logSections(sections)
         }
@@ -215,5 +260,6 @@
     companion object {
         private const val TAG = "NotifSectionsManager"
         private const val DEBUG = false
+        private val SECTION = SourceType.from("Section")
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 7c3e52c..21e2bd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -191,10 +191,13 @@
 
     private final boolean mDebugLines;
     private Paint mDebugPaint;
-    /** Used to track the Y positions that were already used to draw debug text labels. */
+    /**
+     * Used to track the Y positions that were already used to draw debug text labels.
+     */
     private Set<Integer> mDebugTextUsedYPositions;
     private final boolean mDebugRemoveAnimation;
     private final boolean mSimplifiedAppearFraction;
+    private final boolean mUseRoundnessSourceTypes;
 
     private int mContentHeight;
     private float mIntrinsicContentHeight;
@@ -355,15 +358,14 @@
     private final Rect mQsHeaderBound = new Rect();
     private boolean mContinuousShadowUpdate;
     private boolean mContinuousBackgroundUpdate;
-    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater
-            = () -> {
-                updateViewShadows();
-                return true;
-            };
+    private final ViewTreeObserver.OnPreDrawListener mShadowUpdater = () -> {
+        updateViewShadows();
+        return true;
+    };
     private final ViewTreeObserver.OnPreDrawListener mBackgroundUpdater = () -> {
-                updateBackground();
-                return true;
-            };
+        updateBackground();
+        return true;
+    };
     private final Comparator<ExpandableView> mViewPositionComparator = (view, otherView) -> {
         float endY = view.getTranslationY() + view.getActualHeight();
         float otherEndY = otherView.getTranslationY() + otherView.getActualHeight();
@@ -419,7 +421,8 @@
      */
     private int mMaxDisplayedNotifications = -1;
     private float mKeyguardBottomPadding = -1;
-    @VisibleForTesting int mStatusBarHeight;
+    @VisibleForTesting
+    int mStatusBarHeight;
     private int mMinInteractionHeight;
     private final Rect mClipRect = new Rect();
     private boolean mIsClipped;
@@ -568,6 +571,8 @@
 
     @Nullable
     private OnClickListener mManageButtonClickListener;
+    @Nullable
+    private OnNotificationRemovedListener mOnNotificationRemovedListener;
 
     public NotificationStackScrollLayout(Context context, AttributeSet attrs) {
         super(context, attrs, 0, 0);
@@ -576,6 +581,7 @@
         mDebugLines = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES);
         mDebugRemoveAnimation = featureFlags.isEnabled(Flags.NSSL_DEBUG_REMOVE_ANIMATION);
         mSimplifiedAppearFraction = featureFlags.isEnabled(Flags.SIMPLIFIED_APPEAR_FRACTION);
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mSectionsManager = Dependency.get(NotificationSectionsManager.class);
         mScreenOffAnimationController =
                 Dependency.get(ScreenOffAnimationController.class);
@@ -741,7 +747,7 @@
     }
 
     private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
-            boolean expected, boolean actual) {
+                                                 boolean expected, boolean actual) {
         if (mLogger == null) return;
         mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
     }
@@ -868,13 +874,13 @@
 
     /**
      * Draws round rects for each background section.
-     *
+     * <p>
      * We want to draw a round rect for each background section as defined by {@link #mSections}.
      * However, if two sections are directly adjacent with no gap between them (e.g. on the
      * lockscreen where the shelf can appear directly below the high priority section, or while
      * scrolling the shade so that the top of the shelf is right at the bottom of the high priority
      * section), we don't want to round the adjacent corners.
-     *
+     * <p>
      * Since {@link Canvas} doesn't provide a way to draw a half-rounded rect, this means that we
      * need to coalesce the backgrounds for adjacent sections and draw them as a single round rect.
      * This method tracks the top of each rect we need to draw, then iterates through the visible
@@ -883,7 +889,7 @@
      * the current section.  When we're done iterating we will always have one rect left to draw.
      */
     private void drawBackgroundRects(Canvas canvas, int left, int right, int top,
-            int animationYOffset) {
+                                     int animationYOffset) {
         int backgroundRectTop = top;
         int lastSectionBottom =
                 mSections[0].getCurrentBounds().bottom + animationYOffset;
@@ -974,7 +980,7 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     void initView(Context context, NotificationSwipeHelper swipeHelper,
-            NotificationStackSizeCalculator notificationStackSizeCalculator) {
+                  NotificationStackSizeCalculator notificationStackSizeCalculator) {
         mScroller = new OverScroller(getContext());
         mSwipeHelper = swipeHelper;
         mNotificationStackSizeCalculator = notificationStackSizeCalculator;
@@ -1304,18 +1310,19 @@
     /**
      * @return Whether we should skip stack height updates.
      * True when
-     *      1) Unlock hint is running
-     *      2) Swiping up on lockscreen or flinging down after swipe up
+     * 1) Unlock hint is running
+     * 2) Swiping up on lockscreen or flinging down after swipe up
      */
     private boolean shouldSkipHeightUpdate() {
         return mAmbientState.isOnKeyguard()
                 && (mAmbientState.isUnlockHintRunning()
-                        || mAmbientState.isSwipingUp()
-                        || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
+                || mAmbientState.isSwipingUp()
+                || mAmbientState.isFlingingAfterSwipeUpOnLockscreen());
     }
 
     /**
      * Apply expansion fraction to the y position and height of the notifications panel.
+     *
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
@@ -1708,7 +1715,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     ExpandableView getChildAtPosition(float touchX, float touchY,
-            boolean requireMinHeight, boolean ignoreDecors) {
+                                      boolean requireMinHeight, boolean ignoreDecors) {
         // find the view under the pointer, accounting for GONE views
         final int count = getChildCount();
         for (int childIdx = 0; childIdx < count; childIdx++) {
@@ -1868,9 +1875,9 @@
     public void dismissViewAnimated(
             View child, Consumer<Boolean> endRunnable, int delay, long duration) {
         if (child instanceof SectionHeaderView) {
-             ((StackScrollerDecorView) child).setContentVisible(
-                     false /* visible */, true /* animate */, endRunnable);
-             return;
+            ((StackScrollerDecorView) child).setContentVisible(
+                    false /* visible */, true /* animate */, endRunnable);
+            return;
         }
         mSwipeHelper.dismissChild(
                 child,
@@ -2032,10 +2039,11 @@
     /**
      * Scrolls by the given delta, overscrolling if needed.  If called during a fling and the delta
      * would cause us to exceed the provided maximum overscroll, springs back instead.
-     *
+     * <p>
      * This method performs the determination of whether we're exceeding the overscroll and clamps
      * the scroll amount if so.  The actual scrolling/overscrolling happens in
      * {@link #onCustomOverScrolled(int, boolean)}
+     *
      * @param deltaY         The (signed) number of pixels to scroll.
      * @param scrollY        The current scroll position (absolute scrolling only).
      * @param scrollRangeY   The maximum allowable scroll position (absolute scrolling only).
@@ -2098,7 +2106,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         setOverScrollAmount(amount, onTop, animate, cancelAnimators, isRubberbanded(onTop));
     }
 
@@ -2114,7 +2122,7 @@
      */
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators, boolean isRubberbanded) {
+                                    boolean cancelAnimators, boolean isRubberbanded) {
         if (cancelAnimators) {
             mStateAnimator.cancelOverScrollAnimators(onTop);
         }
@@ -2123,7 +2131,7 @@
 
     @ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
     private void setOverScrollAmountInternal(float amount, boolean onTop, boolean animate,
-            boolean isRubberbanded) {
+                                             boolean isRubberbanded) {
         amount = Math.max(0, amount);
         if (animate) {
             mStateAnimator.animateOverScrollToAmount(amount, onTop, isRubberbanded);
@@ -2179,7 +2187,7 @@
      * Scrolls to the given position, overscrolling if needed.  If called during a fling and the
      * position exceeds the provided maximum overscroll, springs back instead.
      *
-     * @param scrollY The target scroll position.
+     * @param scrollY  The target scroll position.
      * @param clampedY Whether this value was clamped by the calling method, meaning we've reached
      *                 the overscroll limit.
      */
@@ -2288,8 +2296,8 @@
                 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
                     List<ExpandableNotificationRow> notificationChildren =
                             row.getAttachedChildren();
-                    for (int childIndex = 0; childIndex < notificationChildren.size();
-                            childIndex++) {
+                    int childrenSize = notificationChildren.size();
+                    for (int childIndex = 0; childIndex < childrenSize; childIndex++) {
                         ExpandableNotificationRow rowChild = notificationChildren.get(childIndex);
                         if (rowChild.getTranslationY() + rowTranslation >= translationY) {
                             return rowChild;
@@ -2365,10 +2373,9 @@
     /**
      * Calculate the gap height between two different views
      *
-     * @param previous the previousView
-     * @param current the currentView
+     * @param previous     the previousView
+     * @param current      the currentView
      * @param visibleIndex the visible index in the list
-     *
      * @return the gap height needed before the current view
      */
     public float calculateGapHeight(
@@ -2376,7 +2383,7 @@
             ExpandableView current,
             int visibleIndex
     ) {
-       return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
+        return mStackScrollAlgorithm.getGapHeightForChild(mSectionsManager, visibleIndex, current,
                 previous, mAmbientState.getFractionToShade(), mAmbientState.isOnKeyguard());
     }
 
@@ -2642,15 +2649,15 @@
         return mScrolledToTopOnFirstDown
                 && !mExpandedInThisMotion
                 && (initialVelocity > mMinimumVelocity
-                        || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
+                || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
     }
 
     /**
      * Updates the top padding of the notifications, taking {@link #getIntrinsicPadding()} into
      * account.
      *
-     * @param qsHeight               the top padding imposed by the quick settings panel
-     * @param animate                whether to animate the change
+     * @param qsHeight the top padding imposed by the quick settings panel
+     * @param animate  whether to animate the change
      */
     @ShadeViewRefactor(RefactorComponent.COORDINATOR)
     public void updateTopPadding(float qsHeight, boolean animate) {
@@ -2724,21 +2731,34 @@
     }
 
 
-
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setChildTransferInProgress(boolean childTransferInProgress) {
         Assert.isMainThread();
         mChildTransferInProgress = childTransferInProgress;
     }
 
+    /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(OnNotificationRemovedListener listener) {
+        mOnNotificationRemovedListener = listener;
+    }
+
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     @Override
     public void onViewRemoved(View child) {
         super.onViewRemoved(child);
         // we only call our internal methods if this is actually a removal and not just a
         // notification which becomes a child notification
+        ExpandableView expandableView = (ExpandableView) child;
         if (!mChildTransferInProgress) {
-            onViewRemovedInternal((ExpandableView) child, this);
+            onViewRemovedInternal(expandableView, this);
+        }
+        if (mOnNotificationRemovedListener != null) {
+            mOnNotificationRemovedListener.onNotificationRemoved(
+                    expandableView,
+                    mChildTransferInProgress);
         }
     }
 
@@ -2998,8 +3018,10 @@
             mAnimateNextSectionBoundsChange = false;
         }
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
-        // TODO: Refactor SectionManager and put the RoundnessManager there.
-        mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        if (!mUseRoundnessSourceTypes) {
+            // TODO: Refactor SectionManager and put the RoundnessManager there.
+            mController.getNotificationRoundnessManager().updateRoundedChildren(mSections);
+        }
         mAnimateBottomOnLayout = false;
         invalidate();
     }
@@ -3206,7 +3228,7 @@
                     // Only animate if we still have pinned heads up, otherwise we just have the
                     // regular collapse animation of the lock screen
                     || (mKeyguardBypassEnabled && onKeyguard()
-                            && mInHeadsUpPinnedMode);
+                    && mInHeadsUpPinnedMode);
             if (performDisappearAnimation && !isHeadsUp) {
                 type = row.wasJustClicked()
                         ? AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
@@ -3351,7 +3373,7 @@
             AnimationEvent animEvent = duration == null
                     ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
                     : new AnimationEvent(
-                            child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+                    child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
             mAnimationEvents.add(animEvent);
         }
         mChildrenChangingPositions.clear();
@@ -3442,7 +3464,9 @@
 
     @ShadeViewRefactor(RefactorComponent.LAYOUT_ALGORITHM)
     protected StackScrollAlgorithm createStackScrollAlgorithm(Context context) {
-        return new StackScrollAlgorithm(context, this);
+        StackScrollAlgorithm stackScrollAlgorithm = new StackScrollAlgorithm(context, this);
+        stackScrollAlgorithm.useRoundnessSourceTypes(mUseRoundnessSourceTypes);
+        return stackScrollAlgorithm;
     }
 
     /**
@@ -3772,7 +3796,7 @@
     }
 
     private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
-            int statusBarState, boolean touchIsClick) {
+                                    int statusBarState, boolean touchIsClick) {
         if (mLogger == null) {
             return;
         }
@@ -3957,7 +3981,9 @@
         mOnEmptySpaceClickListener = listener;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.INPUT)
     public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
@@ -4728,7 +4754,9 @@
         return touchY > mTopPadding + mStackTranslation;
     }
 
-    /** @hide */
+    /**
+     * @hide
+     */
     @Override
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
@@ -5353,7 +5381,9 @@
         return canChildBeCleared(row) && matchesSelection(row, selection);
     }
 
-    /** Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked. */
+    /**
+     * Register a {@link View.OnClickListener} to be invoked when the Manage button is clicked.
+     */
     public void setManageButtonClickListener(@Nullable OnClickListener listener) {
         mManageButtonClickListener = listener;
         if (mFooterView != null) {
@@ -5418,6 +5448,7 @@
     /**
      * Set how far the wake up is when waking up from pulsing. This is a height and will adjust the
      * notification positions accordingly.
+     *
      * @param height the new wake up height
      * @return the overflow how much the height is further than he lowest notification
      */
@@ -5649,7 +5680,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         if (mRoundedRectClippingLeft == left && mRoundedRectClippingRight == right
                 && mRoundedRectClippingBottom == bottom && mRoundedRectClippingTop == top
                 && mBgCornerRadii[0] == topRadius && mBgCornerRadii[5] == bottomRadius) {
@@ -5710,7 +5741,7 @@
         mLaunchingNotification = launching;
         mLaunchingNotificationNeedsToBeClipped = mLaunchAnimationParams != null
                 && (mLaunchAnimationParams.getStartRoundedTopClipping() > 0
-                        || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
+                || mLaunchAnimationParams.getParentStartRoundedTopClipping() > 0);
         if (!mLaunchingNotificationNeedsToBeClipped || !mLaunchingNotification) {
             mLaunchedNotificationClipPath.reset();
         }
@@ -5748,7 +5779,7 @@
                 mLaunchAnimationParams.getProgress(0,
                         NotificationLaunchAnimatorController.ANIMATION_DURATION_TOP_ROUNDING));
         int top = (int) Math.min(MathUtils.lerp(mRoundedRectClippingTop,
-                mLaunchAnimationParams.getTop(), expandProgress),
+                        mLaunchAnimationParams.getTop(), expandProgress),
                 mRoundedRectClippingTop);
         float topRadius = mLaunchAnimationParams.getTopCornerRadius();
         float bottomRadius = mLaunchAnimationParams.getBottomCornerRadius();
@@ -5872,25 +5903,25 @@
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public interface OnOverscrollTopChangedListener {
 
-    /**
-     * Notifies a listener that the overscroll has changed.
-     *
-     * @param amount         the amount of overscroll, in pixels
-     * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
-     *                       unrubberbanded motion to directly expand overscroll view (e.g
-     *                       expand
-     *                       QS)
-     */
-    void onOverscrollTopChanged(float amount, boolean isRubberbanded);
+        /**
+         * Notifies a listener that the overscroll has changed.
+         *
+         * @param amount         the amount of overscroll, in pixels
+         * @param isRubberbanded if true, this is a rubberbanded overscroll; if false, this is an
+         *                       unrubberbanded motion to directly expand overscroll view (e.g
+         *                       expand
+         *                       QS)
+         */
+        void onOverscrollTopChanged(float amount, boolean isRubberbanded);
 
-    /**
-     * Notify a listener that the scroller wants to escape from the scrolling motion and
-     * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
-     *
-     * @param velocity The velocity that the Scroller had when over flinging
-     * @param open     Should the fling open or close the overscroll view.
-     */
-    void flingTopOverscroll(float velocity, boolean open);
+        /**
+         * Notify a listener that the scroller wants to escape from the scrolling motion and
+         * start a fling animation to the expanded or collapsed overscroll view (e.g expand the QS)
+         *
+         * @param velocity The velocity that the Scroller had when over flinging
+         * @param open     Should the fling open or close the overscroll view.
+         */
+        void flingTopOverscroll(float velocity, boolean open);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
@@ -6252,7 +6283,9 @@
         }
     };
 
-    public HeadsUpTouchHelper.Callback getHeadsUpCallback() { return mHeadsUpCallback; }
+    public HeadsUpTouchHelper.Callback getHeadsUpCallback() {
+        return mHeadsUpCallback;
+    }
 
     void onGroupExpandChanged(ExpandableNotificationRow changedRow, boolean expanded) {
         boolean animated = mAnimationsEnabled && (mIsExpanded || changedRow.isPinned());
@@ -6357,15 +6390,25 @@
         return mLastSentExpandedHeight;
     }
 
-    /** Enum for selecting some or all notification rows (does not included non-notif views). */
+    /**
+     * Enum for selecting some or all notification rows (does not included non-notif views).
+     */
     @Retention(SOURCE)
     @IntDef({ROWS_ALL, ROWS_HIGH_PRIORITY, ROWS_GENTLE})
-    @interface SelectedRows {}
-    /** All rows representing notifs. */
+    @interface SelectedRows {
+    }
+
+    /**
+     * All rows representing notifs.
+     */
     public static final int ROWS_ALL = 0;
-    /** Only rows where entry.isHighPriority() is true. */
+    /**
+     * Only rows where entry.isHighPriority() is true.
+     */
     public static final int ROWS_HIGH_PRIORITY = 1;
-    /** Only rows where entry.isHighPriority() is false. */
+    /**
+     * Only rows where entry.isHighPriority() is false.
+     */
     public static final int ROWS_GENTLE = 2;
 
     interface ClearAllListener {
@@ -6380,4 +6423,16 @@
         void onAnimationEnd(
                 List<ExpandableNotificationRow> viewsToRemove, @SelectedRows int selectedRows);
     }
+
+    /**
+     *
+     */
+    public interface OnNotificationRemovedListener {
+        /**
+         *
+         * @param child
+         * @param isTransferInProgress
+         */
+        void onNotificationRemoved(ExpandableView child, boolean isTransferInProgress);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 2d6d0a9c..4bcc0b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -182,10 +182,12 @@
     private NotificationStackScrollLayout mView;
     private boolean mFadeNotificationsOnDismiss;
     private NotificationSwipeHelper mSwipeHelper;
-    @Nullable private Boolean mHistoryEnabled;
+    @Nullable
+    private Boolean mHistoryEnabled;
     private int mBarState;
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     private final FeatureFlags mFeatureFlags;
+    private final boolean mUseRoundnessSourceTypes;
     private final NotificationTargetsHelper mNotificationTargetsHelper;
 
     private View mLongPressedView;
@@ -391,7 +393,7 @@
                     if (item != null) {
                         Point origin = provider.getRevealAnimationOrigin();
                         mNotificationGutsManager.openGuts(row, origin.x, origin.y, item);
-                    } else  {
+                    } else {
                         Log.e(TAG, "Provider has shouldShowGutsOnSnapOpen, but provided no "
                                 + "menu item in menuItemtoExposeOnSnap. Skipping.");
                     }
@@ -420,7 +422,7 @@
 
                 @Override
                 public void onSnooze(StatusBarNotification sbn,
-                        NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
+                                     NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
                     mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
                 }
 
@@ -544,7 +546,7 @@
 
                 @Override
                 public boolean updateSwipeProgress(View animView, boolean dismissable,
-                        float swipeProgress) {
+                                                   float swipeProgress) {
                     // Returning true prevents alpha fading.
                     return !mFadeNotificationsOnDismiss;
                 }
@@ -584,16 +586,22 @@
 
                 @Override
                 public void onHeadsUpPinned(NotificationEntry entry) {
-                    mNotificationRoundnessManager.updateView(entry.getRow(), false /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        mNotificationRoundnessManager.updateView(
+                                entry.getRow(),
+                                /* animate = */ false);
+                    }
                 }
 
                 @Override
                 public void onHeadsUpUnPinned(NotificationEntry entry) {
-                    ExpandableNotificationRow row = entry.getRow();
-                    // update the roundedness posted, because we might be animating away the
-                    // headsup soon, so no need to set the roundedness to 0 and then back to 1.
-                    row.post(() -> mNotificationRoundnessManager.updateView(row,
-                            true /* animate */));
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        // update the roundedness posted, because we might be animating away the
+                        // headsup soon, so no need to set the roundedness to 0 and then back to 1.
+                        row.post(() -> mNotificationRoundnessManager.updateView(row,
+                                true /* animate */));
+                    }
                 }
 
                 @Override
@@ -603,8 +611,10 @@
                     mView.setNumHeadsUp(numEntries);
                     mView.setTopHeadsUpEntry(topEntry);
                     generateHeadsUpAnimation(entry, isHeadsUp);
-                    ExpandableNotificationRow row = entry.getRow();
-                    mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    if (!mUseRoundnessSourceTypes) {
+                        ExpandableNotificationRow row = entry.getRow();
+                        mNotificationRoundnessManager.updateView(row, true /* animate */);
+                    }
                 }
             };
 
@@ -697,6 +707,7 @@
         mSeenNotificationsProvider = seenNotificationsProvider;
         mShadeController = shadeController;
         mFeatureFlags = featureFlags;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mNotificationTargetsHelper = notificationTargetsHelper;
         updateResources();
     }
@@ -763,8 +774,10 @@
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
         mFadeNotificationsOnDismiss = mFeatureFlags.isEnabled(Flags.NOTIFICATION_DISMISSAL_FADE);
-        mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
-        mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        if (!mUseRoundnessSourceTypes) {
+            mNotificationRoundnessManager.setOnRoundingChangedCallback(mView::invalidate);
+            mView.addOnExpandedHeightChangedListener(mNotificationRoundnessManager::setExpanded);
+        }
 
         mVisibilityLocationProviderDelegator.setDelegate(this::isInVisibleLocation);
 
@@ -997,9 +1010,11 @@
                 Log.wtf(TAG, "isHistoryEnabled failed to initialize its value");
                 return false;
             }
-            mHistoryEnabled = historyEnabled =
-                    Settings.Secure.getIntForUser(mView.getContext().getContentResolver(),
-                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED, 0, UserHandle.USER_CURRENT) == 1;
+            mHistoryEnabled = historyEnabled = Settings.Secure.getIntForUser(
+                    mView.getContext().getContentResolver(),
+                    Settings.Secure.NOTIFICATION_HISTORY_ENABLED,
+                    0,
+                    UserHandle.USER_CURRENT) == 1;
         }
         return historyEnabled;
     }
@@ -1029,7 +1044,7 @@
     }
 
     public void setOverScrollAmount(float amount, boolean onTop, boolean animate,
-            boolean cancelAnimators) {
+                                    boolean cancelAnimators) {
         mView.setOverScrollAmount(amount, onTop, animate, cancelAnimators);
     }
 
@@ -1207,7 +1222,7 @@
 
     /**
      * Update whether we should show the empty shade view ("no notifications" in the shade).
-     *
+     * <p>
      * When in split mode, notifications are always visible regardless of the state of the
      * QuickSettings panel. That being the case, empty view is always shown if the other conditions
      * are true.
@@ -1233,7 +1248,7 @@
 
     /**
      * @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
-     *         and false otherwise.
+     * and false otherwise.
      */
     private boolean isInTransitionToKeyguard() {
         final int currentState = mStatusBarStateController.getState();
@@ -1265,7 +1280,9 @@
         mView.setExpandedHeight(expandedHeight);
     }
 
-    /** Sets the QS header. Used to check if a touch is within its bounds. */
+    /**
+     * Sets the QS header. Used to check if a touch is within its bounds.
+     */
     public void setQsHeader(ViewGroup view) {
         mView.setQsHeader(view);
     }
@@ -1328,7 +1345,7 @@
     public RemoteInputController.Delegate createDelegate() {
         return new RemoteInputController.Delegate() {
             public void setRemoteInputActive(NotificationEntry entry,
-                    boolean remoteInputActive) {
+                                             boolean remoteInputActive) {
                 mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
                 entry.notifyHeightChanged(true /* needsAnimation */);
                 updateFooter();
@@ -1459,7 +1476,7 @@
     }
 
     private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
-            @SelectedRows int selectedRows) {
+                                @SelectedRows int selectedRows) {
         if (selectedRows == ROWS_ALL) {
             mNotifCollection.dismissAllNotifications(
                     mLockscreenUserManager.getCurrentUserId());
@@ -1502,8 +1519,8 @@
 
     /**
      * @return the inset during the full shade transition, that needs to be added to the position
-     *         of the quick settings edge. This is relevant for media, that is transitioning
-     *         from the keyguard host to the quick settings one.
+     * of the quick settings edge. This is relevant for media, that is transitioning
+     * from the keyguard host to the quick settings one.
      */
     public int getFullShadeTransitionInset() {
         MediaContainerView view = mKeyguardMediaController.getSinglePaneContainer();
@@ -1517,10 +1534,10 @@
     /**
      * @param fraction The fraction of lockscreen to shade transition.
      *                 0f for all other states.
-     *
-     * Once the lockscreen to shade transition completes and the shade is 100% open,
-     * LockscreenShadeTransitionController resets amount and fraction to 0, where they remain
-     * until the next lockscreen-to-shade transition.
+     *                 <p>
+     *                 Once the lockscreen to shade transition completes and the shade is 100% open,
+     *                 LockscreenShadeTransitionController resets amount and fraction to 0, where
+     *                 they remain until the next lockscreen-to-shade transition.
      */
     public void setTransitionToFullShadeAmount(float fraction) {
         mView.setFractionToShade(fraction);
@@ -1533,7 +1550,9 @@
         mView.setExtraTopInsetForFullShadeTransition(overScrollAmount);
     }
 
-    /** */
+    /**
+     *
+     */
     public void setWillExpand(boolean willExpand) {
         mView.setWillExpand(willExpand);
     }
@@ -1549,7 +1568,7 @@
      * Set rounded rect clipping bounds on this view.
      */
     public void setRoundedClippingBounds(int left, int top, int right, int bottom, int topRadius,
-            int bottomRadius) {
+                                         int bottomRadius) {
         mView.setRoundedClippingBounds(left, top, right, bottom, topRadius, bottomRadius);
     }
 
@@ -1569,6 +1588,15 @@
     }
 
     /**
+     * Set the remove notification listener
+     * @param listener callback for notification removed
+     */
+    public void setOnNotificationRemovedListener(
+            NotificationStackScrollLayout.OnNotificationRemovedListener listener) {
+        mView.setOnNotificationRemovedListener(listener);
+    }
+
+    /**
      * Enum for UiEvent logged from this class
      */
     enum NotificationPanelEvent implements UiEventLogger.UiEventEnum {
@@ -1578,10 +1606,13 @@
         @UiEvent(doc = "User dismissed all silent notifications from notification panel.")
         DISMISS_SILENT_NOTIFICATIONS_PANEL(314);
         private final int mId;
+
         NotificationPanelEvent(int id) {
             mId = id;
         }
-        @Override public int getId() {
+
+        @Override
+        public int getId() {
             return mId;
         }
 
@@ -1722,8 +1753,12 @@
         @Override
         public void bindRow(ExpandableNotificationRow row) {
             row.setHeadsUpAnimatingAwayListener(animatingAway -> {
-                mNotificationRoundnessManager.updateView(row, false);
-                mHeadsUpAppearanceController.updateHeader(row.getEntry());
+                if (!mUseRoundnessSourceTypes) {
+                    mNotificationRoundnessManager.updateView(row, false);
+                }
+                NotificationEntry entry = row.getEntry();
+                mHeadsUpAppearanceController.updateHeader(entry);
+                mHeadsUpAppearanceController.updateHeadsUpAndPulsingRoundness(entry);
             });
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index ee57411..aaf9300 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -20,6 +20,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.res.Resources;
 import android.graphics.Rect;
@@ -34,9 +35,11 @@
 import com.android.systemui.SwipeHelper;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
 
@@ -49,6 +52,7 @@
     @VisibleForTesting
     protected static final long COVER_MENU_DELAY = 4000;
     private static final String TAG = "NotificationSwipeHelper";
+    private static final SourceType SWIPE_DISMISS = SourceType.from("SwipeDismiss");
     private final Runnable mFalsingCheck;
     private View mTranslatingParentView;
     private View mMenuExposedView;
@@ -64,13 +68,21 @@
     private WeakReference<NotificationMenuRowPlugin> mCurrMenuRowRef;
     private boolean mIsExpanded;
     private boolean mPulsing;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
 
     NotificationSwipeHelper(
-            Resources resources, ViewConfiguration viewConfiguration,
-            FalsingManager falsingManager, FeatureFlags featureFlags,
-            int swipeDirection, NotificationCallback callback,
-            NotificationMenuRowPlugin.OnMenuEventListener menuListener) {
+            Resources resources,
+            ViewConfiguration viewConfiguration,
+            FalsingManager falsingManager,
+            FeatureFlags featureFlags,
+            int swipeDirection,
+            NotificationCallback callback,
+            NotificationMenuRowPlugin.OnMenuEventListener menuListener,
+            NotificationRoundnessManager notificationRoundnessManager) {
         super(swipeDirection, callback, resources, viewConfiguration, falsingManager, featureFlags);
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mMenuListener = menuListener;
         mCallback = callback;
         mFalsingCheck = () -> resetExposedMenuView(true /* animate */, true /* force */);
@@ -304,6 +316,33 @@
         handleMenuCoveredOrDismissed();
     }
 
+    @Override
+    protected void prepareDismissAnimation(View view, Animator anim) {
+        super.prepareDismissAnimation(view, anim);
+
+        if (mUseRoundnessSourceTypes
+                && view instanceof ExpandableNotificationRow
+                && mNotificationRoundnessManager.isClearAllInProgress()) {
+            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            anim.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationCancel(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    row.requestRoundnessReset(SWIPE_DISMISS);
+                }
+            });
+        }
+    }
+
     @VisibleForTesting
     protected void superDismissChild(final View view, float velocity, boolean useAccelerateInterpolator) {
         super.dismissChild(view, velocity, useAccelerateInterpolator);
@@ -521,14 +560,17 @@
         private int mSwipeDirection;
         private NotificationCallback mNotificationCallback;
         private NotificationMenuRowPlugin.OnMenuEventListener mOnMenuEventListener;
+        private NotificationRoundnessManager mNotificationRoundnessManager;
 
         @Inject
         Builder(@Main Resources resources, ViewConfiguration viewConfiguration,
-                FalsingManager falsingManager, FeatureFlags featureFlags) {
+                FalsingManager falsingManager, FeatureFlags featureFlags,
+                NotificationRoundnessManager notificationRoundnessManager) {
             mResources = resources;
             mViewConfiguration = viewConfiguration;
             mFalsingManager = falsingManager;
             mFeatureFlags = featureFlags;
+            mNotificationRoundnessManager = notificationRoundnessManager;
         }
 
         Builder setSwipeDirection(int swipeDirection) {
@@ -549,7 +591,8 @@
 
         NotificationSwipeHelper build() {
             return new NotificationSwipeHelper(mResources, mViewConfiguration, mFalsingManager,
-                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener);
+                    mFeatureFlags, mSwipeDirection, mNotificationCallback, mOnMenuEventListener,
+                    mNotificationRoundnessManager);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
index 991a14b..548d1a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelper.kt
@@ -20,8 +20,7 @@
 constructor(
     featureFlags: FeatureFlags,
 ) {
-    private val isNotificationGroupCornerEnabled =
-        featureFlags.isEnabled(Flags.NOTIFICATION_GROUP_CORNER)
+    private val useRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)
 
     /**
      * This method looks for views that can be rounded (and implement [Roundable]) during a
@@ -48,7 +47,7 @@
         if (notificationParent != null && childrenContainer != null) {
             // We are inside a notification group
 
-            if (!isNotificationGroupCornerEnabled) {
+            if (!useRoundnessSourceTypes) {
                 return RoundableTargets(null, null, null)
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d8c6878..aff7b4c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -31,6 +31,7 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,6 +52,7 @@
 
     private static final String TAG = "StackScrollAlgorithm";
     private static final Boolean DEBUG = false;
+    private static final SourceType STACK_SCROLL_ALGO = SourceType.from("StackScrollAlgorithm");
 
     private final ViewGroup mHostView;
     private float mPaddingBetweenElements;
@@ -70,6 +72,7 @@
     private float mQuickQsOffsetHeight;
     private float mSmallCornerRadius;
     private float mLargeCornerRadius;
+    private boolean mUseRoundnessSourceTypes;
 
     public StackScrollAlgorithm(
             Context context,
@@ -129,7 +132,7 @@
     }
 
     private void updateAlphaState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                  AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
 
@@ -229,7 +232,7 @@
     }
 
     private void getNotificationChildrenStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                               AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = algorithmState.visibleChildren.get(i);
@@ -241,7 +244,7 @@
     }
 
     private void updateSpeedBumpState(StackScrollAlgorithmState algorithmState,
-            int speedBumpIndex) {
+                                      int speedBumpIndex) {
         int childCount = algorithmState.visibleChildren.size();
         int belowSpeedBump = speedBumpIndex;
         for (int i = 0; i < childCount; i++) {
@@ -268,7 +271,7 @@
     }
 
     private void updateClipping(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                AmbientState ambientState) {
         float drawStart = ambientState.isOnKeyguard() ? 0
                 : ambientState.getStackY() - ambientState.getScrollY();
         float clipStart = 0;
@@ -314,7 +317,7 @@
      * Updates the dimmed, activated and hiding sensitive states of the children.
      */
     private void updateDimmedActivatedHideSensitive(AmbientState ambientState,
-            StackScrollAlgorithmState algorithmState) {
+                                                    StackScrollAlgorithmState algorithmState) {
         boolean dimmed = ambientState.isDimmed();
         boolean hideSensitive = ambientState.isHideSensitive();
         View activatedChild = ambientState.getActivatedChild();
@@ -408,7 +411,7 @@
     }
 
     private int updateNotGoneIndex(StackScrollAlgorithmState state, int notGoneIndex,
-            ExpandableView v) {
+                                   ExpandableView v) {
         ExpandableViewState viewState = v.getViewState();
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
@@ -434,7 +437,7 @@
      * @param ambientState   The current ambient state
      */
     protected void updatePositionsForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                           AmbientState ambientState) {
         if (!ambientState.isOnKeyguard()
                 || (ambientState.isBypassEnabled() && ambientState.isPulseExpanding())) {
             algorithmState.mCurrentYPosition += mNotificationScrimPadding;
@@ -448,7 +451,7 @@
     }
 
     private void setLocation(ExpandableViewState expandableViewState, float currentYPosition,
-            int i) {
+                             int i) {
         expandableViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         if (currentYPosition <= 0) {
             expandableViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
@@ -496,9 +499,13 @@
     }
 
     @VisibleForTesting
-    void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
-            boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
-
+    void maybeUpdateHeadsUpIsVisible(
+            ExpandableViewState viewState,
+            boolean isShadeExpanded,
+            boolean mustStayOnScreen,
+            boolean topVisible,
+            float viewEnd,
+            float hunMax) {
         if (isShadeExpanded && mustStayOnScreen && topVisible) {
             viewState.headsUpIsVisible = viewEnd < hunMax;
         }
@@ -676,7 +683,7 @@
     }
 
     private void updatePulsingStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
@@ -693,7 +700,7 @@
     }
 
     private void updateHeadsUpStates(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                     AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
 
         // Move the tracked heads up into position during the appear animation, by interpolating
@@ -777,7 +784,7 @@
      */
     @VisibleForTesting
     void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
-            ExpandableViewState viewState) {
+                       ExpandableViewState viewState) {
 
         final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
                 viewState.getYTranslation());
@@ -792,7 +799,7 @@
     // Pin HUN to bottom of expanded QS
     // while the rest of notifications are scrolled offscreen.
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-            ExpandableViewState childState) {
+                                          ExpandableViewState childState) {
         float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
         final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
                 + ambientState.getStackTranslation();
@@ -807,14 +814,19 @@
         // Animate pinned HUN bottom corners to and from original roundness.
         final float originalCornerRadius =
                 row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
-        final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+        final float bottomValue = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
                 ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
-        row.requestBottomRoundness(roundness, /* animate = */ false, SourceType.OnScroll);
+        if (mUseRoundnessSourceTypes) {
+            row.requestBottomRoundness(bottomValue, STACK_SCROLL_ALGO);
+            row.addOnDetachResetRoundness(STACK_SCROLL_ALGO);
+        } else {
+            row.requestBottomRoundness(bottomValue, LegacySourceType.OnScroll);
+        }
     }
 
     @VisibleForTesting
     float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
-            float viewMaxHeight, float originalCornerRadius) {
+                                             float viewMaxHeight, float originalCornerRadius) {
 
         // Compute y where corner roundness should be in its original unpinned state.
         // We use view max height because the pinned collapsed HUN expands to max height
@@ -844,7 +856,7 @@
      * @param ambientState   The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState) {
+                                       AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
 
@@ -876,9 +888,9 @@
      *                      previous HUNs whose Z positions are greater than 0.
      */
     protected float updateChildZValue(int i, float childrenOnTop,
-            StackScrollAlgorithmState algorithmState,
-            AmbientState ambientState,
-            boolean isTopHun) {
+                                      StackScrollAlgorithmState algorithmState,
+                                      AmbientState ambientState,
+                                      boolean isTopHun) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
         ExpandableViewState childViewState = child.getViewState();
         float baseZ = ambientState.getBaseZHeight();
@@ -950,6 +962,14 @@
         this.mIsExpanded = isExpanded;
     }
 
+    /**
+     * Enable the support for rounded corner based on the SourceType
+     * @param enabled true if is supported
+     */
+    public void useRoundnessSourceTypes(boolean enabled) {
+        mUseRoundnessSourceTypes = enabled;
+    }
+
     public static class StackScrollAlgorithmState {
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
index 1169d3f..c7be219 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -40,6 +40,8 @@
 import com.android.systemui.statusbar.connectivity.ui.MobileContextProvider;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.MobileIconState;
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
+import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -50,20 +52,25 @@
 
     private final LinearLayout mStatusIcons;
     private final ArrayList<StatusBarMobileView> mMobileViews = new ArrayList<>();
+    private final ArrayList<ModernStatusBarMobileView> mModernMobileViews = new ArrayList<>();
     private final int mIconSize;
 
     private StatusBarWifiView mWifiView;
     private boolean mDemoMode;
     private int mColor;
 
+    private final MobileIconsViewModel mMobileIconsViewModel;
+
     public DemoStatusIcons(
             LinearLayout statusIcons,
+            MobileIconsViewModel mobileIconsViewModel,
             int iconSize
     ) {
         super(statusIcons.getContext());
         mStatusIcons = statusIcons;
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
+        mMobileIconsViewModel = mobileIconsViewModel;
 
         if (statusIcons instanceof StatusIconContainer) {
             setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -71,7 +78,7 @@
             setShouldRestrictIcons(false);
         }
         setLayoutParams(mStatusIcons.getLayoutParams());
-        setPadding(mStatusIcons.getPaddingLeft(),mStatusIcons.getPaddingTop(),
+        setPadding(mStatusIcons.getPaddingLeft(), mStatusIcons.getPaddingTop(),
                 mStatusIcons.getPaddingRight(), mStatusIcons.getPaddingBottom());
         setOrientation(mStatusIcons.getOrientation());
         setGravity(Gravity.CENTER_VERTICAL); // no LL.getGravity()
@@ -115,6 +122,8 @@
     public void onDemoModeFinished() {
         mDemoMode = false;
         mStatusIcons.setVisibility(View.VISIBLE);
+        mModernMobileViews.clear();
+        mMobileViews.clear();
         setVisibility(View.GONE);
     }
 
@@ -269,6 +278,24 @@
     }
 
     /**
+     * Add a {@link ModernStatusBarMobileView}
+     * @param mobileContext possibly mcc/mnc overridden mobile context
+     * @param subId the subscriptionId for this mobile view
+     */
+    public void addModernMobileView(Context mobileContext, int subId) {
+        Log.d(TAG, "addModernMobileView (subId=" + subId + ")");
+        ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
+                mobileContext,
+                "mobile",
+                mMobileIconsViewModel.viewModelForSub(subId)
+        );
+
+        // mobile always goes at the end
+        mModernMobileViews.add(view);
+        addView(view, getChildCount(), createLayoutParams());
+    }
+
+    /**
      * Apply an update to a mobile icon view for the given {@link MobileIconState}. For
      * compatibility with {@link MobileContextProvider}, we have to recreate the view every time we
      * update it, since the context (and thus the {@link Configuration}) may have changed
@@ -292,12 +319,19 @@
         if (view.getSlot().equals("wifi")) {
             removeView(mWifiView);
             mWifiView = null;
-        } else {
+        } else if (view instanceof StatusBarMobileView) {
             StatusBarMobileView mobileView = matchingMobileView(view);
             if (mobileView != null) {
                 removeView(mobileView);
                 mMobileViews.remove(mobileView);
             }
+        } else if (view instanceof ModernStatusBarMobileView) {
+            ModernStatusBarMobileView mobileView = matchingModernMobileView(
+                    (ModernStatusBarMobileView) view);
+            if (mobileView != null) {
+                removeView(mobileView);
+                mModernMobileViews.remove(mobileView);
+            }
         }
     }
 
@@ -316,6 +350,16 @@
         return null;
     }
 
+    private ModernStatusBarMobileView matchingModernMobileView(ModernStatusBarMobileView other) {
+        for (ModernStatusBarMobileView v : mModernMobileViews) {
+            if (v.getSubId() == other.getSubId()) {
+                return v;
+            }
+        }
+
+        return null;
+    }
+
     private LayoutParams createLayoutParams() {
         return new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 484441a..c217ab3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -19,11 +19,14 @@
 import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_FRAME_VIEW;
 
 import android.graphics.Rect;
+import android.util.MathUtils;
 import android.view.View;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -32,8 +35,10 @@
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope;
 import com.android.systemui.statusbar.policy.Clock;
@@ -51,6 +56,7 @@
 
 /**
  * Controls the appearance of heads up notifications in the icon area and the header itself.
+ * It also controls the roundness of the heads up notifications and the pulsing notifications.
  */
 @StatusBarFragmentScope
 public class HeadsUpAppearanceController extends ViewController<HeadsUpStatusBarView>
@@ -59,12 +65,17 @@
         NotificationWakeUpCoordinator.WakeUpListener {
     public static final int CONTENT_FADE_DURATION = 110;
     public static final int CONTENT_FADE_DELAY = 100;
+
+    private static final SourceType HEADS_UP = SourceType.from("HeadsUp");
+    private static final SourceType PULSING = SourceType.from("Pulsing");
     private final NotificationIconAreaController mNotificationIconAreaController;
     private final HeadsUpManagerPhone mHeadsUpManager;
     private final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final DarkIconDispatcher mDarkIconDispatcher;
     private final NotificationPanelViewController mNotificationPanelViewController;
+    private final NotificationRoundnessManager mNotificationRoundnessManager;
+    private final boolean mUseRoundnessSourceTypes;
     private final Consumer<ExpandableNotificationRow>
             mSetTrackingHeadsUp = this::setTrackingHeadsUp;
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
@@ -105,11 +116,15 @@
             CommandQueue commandQueue,
             NotificationStackScrollLayoutController stackScrollerController,
             NotificationPanelViewController notificationPanelViewController,
+            NotificationRoundnessManager notificationRoundnessManager,
+            FeatureFlags featureFlags,
             HeadsUpStatusBarView headsUpStatusBarView,
             Clock clockView,
             @Named(OPERATOR_NAME_FRAME_VIEW) Optional<View> operatorNameViewOptional) {
         super(headsUpStatusBarView);
         mNotificationIconAreaController = notificationIconAreaController;
+        mNotificationRoundnessManager = notificationRoundnessManager;
+        mUseRoundnessSourceTypes = featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES);
         mHeadsUpManager = headsUpManager;
 
         // We may be mid-HUN-expansion when this controller is re-created (for example, if the user
@@ -179,6 +194,7 @@
     public void onHeadsUpPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     private void updateTopEntry() {
@@ -316,6 +332,7 @@
     public void onHeadsUpUnPinned(NotificationEntry entry) {
         updateTopEntry();
         updateHeader(entry);
+        updateHeadsUpAndPulsingRoundness(entry);
     }
 
     public void setAppearFraction(float expandedHeight, float appearFraction) {
@@ -346,7 +363,9 @@
         ExpandableNotificationRow previousTracked = mTrackedChild;
         mTrackedChild = trackedChild;
         if (previousTracked != null) {
-            updateHeader(previousTracked.getEntry());
+            NotificationEntry entry = previousTracked.getEntry();
+            updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         }
     }
 
@@ -357,6 +376,7 @@
     private void updateHeadsUpHeaders() {
         mHeadsUpManager.getAllEntries().forEach(entry -> {
             updateHeader(entry);
+            updateHeadsUpAndPulsingRoundness(entry);
         });
     }
 
@@ -370,6 +390,31 @@
         row.setHeaderVisibleAmount(headerVisibleAmount);
     }
 
+    /**
+     * Update the HeadsUp and the Pulsing roundness based on current state
+     * @param entry target notification
+     */
+    public void updateHeadsUpAndPulsingRoundness(NotificationEntry entry) {
+        if (mUseRoundnessSourceTypes) {
+            ExpandableNotificationRow row = entry.getRow();
+            boolean isTrackedChild = row == mTrackedChild;
+            if (row.isPinned() || row.isHeadsUpAnimatingAway() || isTrackedChild) {
+                float roundness = MathUtils.saturate(1f - mAppearFraction);
+                row.requestRoundness(roundness, roundness, HEADS_UP);
+            } else {
+                row.requestRoundnessReset(HEADS_UP);
+            }
+            if (mNotificationRoundnessManager.shouldRoundNotificationPulsing()) {
+                if (row.showingPulsing()) {
+                    row.requestRoundness(/* top = */ 1f, /* bottom = */ 1f, PULSING);
+                } else {
+                    row.requestRoundnessReset(PULSING);
+                }
+            }
+        }
+    }
+
+
     @Override
     public void onDarkChanged(ArrayList<Rect> areas, float darkIntensity, int tint) {
         mView.onDarkChanged(areas, darkIntensity, tint);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
index 4969a1c..6811bf6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileController.java
@@ -14,6 +14,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import androidx.annotation.MainThread;
+
 import com.android.systemui.statusbar.phone.ManagedProfileController.Callback;
 import com.android.systemui.statusbar.policy.CallbackController;
 
@@ -25,8 +27,20 @@
 
     boolean isWorkModeEnabled();
 
-    public interface Callback {
+    /**
+     * Callback to get updates about work profile status.
+     */
+    interface Callback {
+        /**
+         * Called when managed profile change is detected. This always runs on the main thread.
+         */
+        @MainThread
         void onManagedProfileChanged();
+
+        /**
+         * Called when managed profile removal is detected. This always runs on the main thread.
+         */
+        @MainThread
         void onManagedProfileRemoved();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 4beb87d..abdf827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -33,31 +33,28 @@
 
 import javax.inject.Inject;
 
-/**
- */
 @SysUISingleton
 public class ManagedProfileControllerImpl implements ManagedProfileController {
 
     private final List<Callback> mCallbacks = new ArrayList<>();
-
+    private final UserTrackerCallback mUserTrackerCallback = new UserTrackerCallback();
     private final Context mContext;
     private final Executor mMainExecutor;
     private final UserManager mUserManager;
     private final UserTracker mUserTracker;
     private final LinkedList<UserInfo> mProfiles;
+
     private boolean mListening;
     private int mCurrentUser;
 
-    /**
-     */
     @Inject
     public ManagedProfileControllerImpl(Context context, @Main Executor mainExecutor,
-            UserTracker userTracker) {
+            UserTracker userTracker, UserManager userManager) {
         mContext = context;
         mMainExecutor = mainExecutor;
-        mUserManager = UserManager.get(mContext);
+        mUserManager = userManager;
         mUserTracker = userTracker;
-        mProfiles = new LinkedList<UserInfo>();
+        mProfiles = new LinkedList<>();
     }
 
     @Override
@@ -100,16 +97,22 @@
                 }
             }
             if (mProfiles.size() == 0 && hadProfile && (user == mCurrentUser)) {
-                for (Callback callback : mCallbacks) {
-                    callback.onManagedProfileRemoved();
-                }
+                mMainExecutor.execute(this::notifyManagedProfileRemoved);
             }
             mCurrentUser = user;
         }
     }
 
+    private void notifyManagedProfileRemoved() {
+        for (Callback callback : mCallbacks) {
+            callback.onManagedProfileRemoved();
+        }
+    }
+
     public boolean hasActiveProfile() {
-        if (!mListening) reloadManagedProfiles();
+        if (!mListening || mUserTracker.getUserId() != mCurrentUser) {
+            reloadManagedProfiles();
+        }
         synchronized (mProfiles) {
             return mProfiles.size() > 0;
         }
@@ -134,28 +137,28 @@
         mListening = listening;
         if (listening) {
             reloadManagedProfiles();
-            mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+            mUserTracker.addCallback(mUserTrackerCallback, mMainExecutor);
         } else {
-            mUserTracker.removeCallback(mUserChangedCallback);
+            mUserTracker.removeCallback(mUserTrackerCallback);
         }
     }
 
-    private final UserTracker.Callback mUserChangedCallback =
-            new UserTracker.Callback() {
-                @Override
-                public void onUserChanged(int newUser, @NonNull Context userContext) {
-                    reloadManagedProfiles();
-                    for (Callback callback : mCallbacks) {
-                        callback.onManagedProfileChanged();
-                    }
-                }
+    private final class UserTrackerCallback implements UserTracker.Callback {
 
-                @Override
-                public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
-                    reloadManagedProfiles();
-                    for (Callback callback : mCallbacks) {
-                        callback.onManagedProfileChanged();
-                    }
-                }
-            };
+        @Override
+        public void onUserChanged(int newUser, @NonNull Context userContext) {
+            reloadManagedProfiles();
+            for (Callback callback : mCallbacks) {
+                callback.onManagedProfileChanged();
+            }
+        }
+
+        @Override
+        public void onProfilesChanged(@NonNull List<UserInfo> profiles) {
+            reloadManagedProfiles();
+            for (Callback callback : mCallbacks) {
+                callback.onManagedProfileChanged();
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
deleted file mode 100644
index 5e2a7c8..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitchController.java
+++ /dev/null
@@ -1,171 +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.statusbar.phone;
-
-import static com.android.systemui.DejankUtils.whitelistIpcs;
-
-import android.content.Intent;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.view.View;
-import android.view.ViewGroup;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.qs.FooterActionsView;
-import com.android.systemui.qs.dagger.QSScope;
-import com.android.systemui.qs.user.UserSwitchDialogController;
-import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
-import com.android.systemui.statusbar.policy.UserSwitcherController;
-import com.android.systemui.user.UserSwitcherActivity;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-
-/** View Controller for {@link MultiUserSwitch}. */
-// TODO(b/242040009): Remove this file.
-public class MultiUserSwitchController extends ViewController<MultiUserSwitch> {
-    private final UserManager mUserManager;
-    private final UserSwitcherController mUserSwitcherController;
-    private final FalsingManager mFalsingManager;
-    private final UserSwitchDialogController mUserSwitchDialogController;
-    private final ActivityStarter mActivityStarter;
-    private final FeatureFlags mFeatureFlags;
-
-    private BaseUserSwitcherAdapter mUserListener;
-
-    private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
-        @Override
-        public void onClick(View v) {
-            if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                return;
-            }
-
-            if (mFeatureFlags.isEnabled(Flags.FULL_SCREEN_USER_SWITCHER)) {
-                Intent intent = new Intent(v.getContext(), UserSwitcherActivity.class);
-                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
-
-                mActivityStarter.startActivity(intent, true /* dismissShade */,
-                        ActivityLaunchAnimator.Controller.fromView(v, null),
-                        true /* showOverlockscreenwhenlocked */, UserHandle.SYSTEM);
-            } else {
-                mUserSwitchDialogController.showDialog(v.getContext(), Expandable.fromView(v));
-            }
-        }
-    };
-
-    @QSScope
-    public static class Factory {
-        private final UserManager mUserManager;
-        private final UserSwitcherController mUserSwitcherController;
-        private final FalsingManager mFalsingManager;
-        private final UserSwitchDialogController mUserSwitchDialogController;
-        private final ActivityStarter mActivityStarter;
-        private final FeatureFlags mFeatureFlags;
-
-        @Inject
-        public Factory(UserManager userManager, UserSwitcherController userSwitcherController,
-                FalsingManager falsingManager,
-                UserSwitchDialogController userSwitchDialogController, FeatureFlags featureFlags,
-                ActivityStarter activityStarter) {
-            mUserManager = userManager;
-            mUserSwitcherController = userSwitcherController;
-            mFalsingManager = falsingManager;
-            mUserSwitchDialogController = userSwitchDialogController;
-            mActivityStarter = activityStarter;
-            mFeatureFlags = featureFlags;
-        }
-
-        public MultiUserSwitchController create(FooterActionsView view) {
-            return new MultiUserSwitchController(view.findViewById(R.id.multi_user_switch),
-                    mUserManager, mUserSwitcherController,
-                    mFalsingManager, mUserSwitchDialogController, mFeatureFlags,
-                    mActivityStarter);
-        }
-    }
-
-    private MultiUserSwitchController(MultiUserSwitch view, UserManager userManager,
-            UserSwitcherController userSwitcherController,
-            FalsingManager falsingManager, UserSwitchDialogController userSwitchDialogController,
-            FeatureFlags featureFlags, ActivityStarter activityStarter) {
-        super(view);
-        mUserManager = userManager;
-        mUserSwitcherController = userSwitcherController;
-        mFalsingManager = falsingManager;
-        mUserSwitchDialogController = userSwitchDialogController;
-        mFeatureFlags = featureFlags;
-        mActivityStarter = activityStarter;
-    }
-
-    @Override
-    protected void onInit() {
-        registerListener();
-        mView.refreshContentDescription(getCurrentUser());
-    }
-
-    @Override
-    protected void onViewAttached() {
-        mView.setOnClickListener(mOnClickListener);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        mView.setOnClickListener(null);
-    }
-
-    private void registerListener() {
-        if (mUserManager.isUserSwitcherEnabled() && mUserListener == null) {
-
-            final UserSwitcherController controller = mUserSwitcherController;
-            if (controller != null) {
-                mUserListener = new BaseUserSwitcherAdapter(controller) {
-                    @Override
-                    public void notifyDataSetChanged() {
-                        mView.refreshContentDescription(getCurrentUser());
-                    }
-
-                    @Override
-                    public View getView(int position, View convertView, ViewGroup parent) {
-                        return null;
-                    }
-                };
-                mView.refreshContentDescription(getCurrentUser());
-            }
-        }
-    }
-
-    private String getCurrentUser() {
-        // TODO(b/138661450)
-        if (whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled())) {
-            return mUserSwitcherController.getCurrentUserName();
-        }
-
-        return null;
-    }
-
-    /** Returns true if view should be made visible. */
-    public boolean isMultiUserEnabled() {
-        // TODO(b/138661450) Move IPC calls to background
-        return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled(
-                getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user)));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 0a0ded2..df3ab49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -536,8 +536,7 @@
             mGroup.addView(view, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
-                // TODO (b/249790009): demo mode should be handled at the data layer in the
-                //  new pipeline
+                mDemoStatusIcons.addModernMobileView(mContext, subId);
             }
 
             return view;
@@ -565,11 +564,13 @@
 
         private ModernStatusBarMobileView onCreateModernStatusBarMobileView(
                 String slot, int subId) {
+            Context mobileContext = mMobileContextProvider.getMobileContextForSub(subId, mContext);
             return ModernStatusBarMobileView
                     .constructAndBind(
-                            mContext,
+                            mobileContext,
                             slot,
-                            mMobileIconsViewModel.viewModelForSub(subId));
+                            mMobileIconsViewModel.viewModelForSub(subId)
+                        );
         }
 
         protected LinearLayout.LayoutParams onCreateLayoutParams() {
@@ -704,7 +705,7 @@
         }
 
         protected DemoStatusIcons createDemoStatusIcons() {
-            return new DemoStatusIcons((LinearLayout) mGroup, mIconSize);
+            return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
         }
     }
 }
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 674e574..9fbe6cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -276,6 +276,11 @@
         String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
         Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
+        // 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);
+
         Collections.reverse(subIds);
 
         for (Integer subId : subIds) {
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 fb67f1a..c350c78 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.dagger
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
@@ -24,11 +25,12 @@
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel
 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileRepositorySwitcher
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepositoryImpl
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl
+import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
@@ -40,6 +42,8 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 
 @Module
 abstract class StatusBarPipelineModule {
@@ -52,26 +56,28 @@
     @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
-    @Binds
-    abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+    @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
 
     @Binds
     abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
 
     @Binds
     abstract fun mobileConnectionsRepository(
-        impl: MobileConnectionsRepositoryImpl
+        impl: MobileRepositorySwitcher
     ): MobileConnectionsRepository
 
-    @Binds
-    abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
+    @Binds abstract fun userSetupRepository(impl: UserSetupRepositoryImpl): UserSetupRepository
 
-    @Binds
-    abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
+    @Binds abstract fun mobileMappingsProxy(impl: MobileMappingsProxyImpl): MobileMappingsProxy
 
     @Binds
     abstract fun mobileIconsInteractor(impl: MobileIconsInteractorImpl): MobileIconsInteractor
 
+    @Binds
+    @IntoMap
+    @ClassKey(MobileUiAdapter::class)
+    abstract fun bindFeature(impl: MobileUiAdapter): CoreStartable
+
     @Module
     companion object {
         @JvmStatic
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index da87f73..5479b92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -20,6 +20,7 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.DataState
 
 /** Internal enum representation of the telephony data connection states */
@@ -28,6 +29,7 @@
     Connecting(DATA_CONNECTING),
     Disconnected(DATA_DISCONNECTED),
     Disconnecting(DATA_DISCONNECTING),
+    Unknown(DATA_UNKNOWN),
 }
 
 fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
@@ -36,5 +38,6 @@
         DATA_CONNECTING -> DataConnectionState.Connecting
         DATA_DISCONNECTED -> DataConnectionState.Disconnected
         DATA_DISCONNECTING -> DataConnectionState.Disconnecting
-        else -> throw IllegalArgumentException("unknown data state received")
+        DATA_UNKNOWN -> DataConnectionState.Unknown
+        else -> throw IllegalArgumentException("unknown data state received $this")
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 6341a11..1d00c33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -27,7 +27,6 @@
 import android.telephony.TelephonyCallback.SignalStrengthsListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
-import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
 
 /**
@@ -39,7 +38,7 @@
  * any new field that needs to be tracked should be copied into this data class rather than
  * threading complex system objects through the pipeline.
  */
-data class MobileSubscriptionModel(
+data class MobileConnectionModel(
     /** From [ServiceStateListener.onServiceStateChanged] */
     val isEmergencyOnly: Boolean = false,
 
@@ -65,5 +64,5 @@
      * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
      * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
      */
-    val resolvedNetworkType: ResolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+    val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index f385806..dd93541 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.model
 
 import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 
 /**
@@ -26,8 +27,20 @@
  */
 sealed interface ResolvedNetworkType {
     @NetworkType val type: Int
+    val lookupKey: String
+
+    object UnknownNetworkType : ResolvedNetworkType {
+        override val type: Int = NETWORK_TYPE_UNKNOWN
+        override val lookupKey: String = "unknown"
+    }
+
+    data class DefaultNetworkType(
+        @NetworkType override val type: Int,
+        override val lookupKey: String,
+    ) : ResolvedNetworkType
+
+    data class OverrideNetworkType(
+        @NetworkType override val type: Int,
+        override val lookupKey: String,
+    ) : ResolvedNetworkType
 }
-
-data class DefaultNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
-
-data class OverrideNetworkType(@NetworkType override val type: Int) : ResolvedNetworkType
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
new file mode 100644
index 0000000..2f34516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/SubscriptionModel.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.model
+
+/**
+ * SystemUI representation of [SubscriptionInfo]. Currently we only use two fields on the
+ * subscriptions themselves: subscriptionId and isOpportunistic. Any new fields that we need can be
+ * added below and provided in the repository classes
+ */
+data class SubscriptionModel(
+    val subscriptionId: Int,
+    /**
+     * True if the subscription that this model represents has [SubscriptionInfo.isOpportunistic].
+     * Opportunistic networks are networks with limited coverage, and we use this bit to determine
+     * filtering in certain cases. See [MobileIconsInteractor] for the filtering logic
+     */
+    val isOpportunistic: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 581842b..2621f997 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,44 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import android.content.Context
-import android.database.ContentObserver
-import android.provider.Settings.Global
-import android.telephony.CellSignalStrength
-import android.telephony.CellSignalStrengthCdma
-import android.telephony.ServiceState
-import android.telephony.SignalStrength
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
-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.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
-import com.android.systemui.util.settings.GlobalSettings
-import java.lang.IllegalStateException
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
 
 /**
  * Every mobile line of service can be identified via a [SubscriptionInfo] object. We set up a
@@ -67,11 +36,13 @@
  * eventually becomes a single icon in the status bar.
  */
 interface MobileConnectionRepository {
+    /** The subscriptionId that this connection represents */
+    val subId: Int
     /**
      * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
      * listener + model.
      */
-    val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+    val connectionInfo: Flow<MobileConnectionModel>
     /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
     val dataEnabled: StateFlow<Boolean>
     /**
@@ -80,183 +51,3 @@
      */
     val isDefaultDataSubscription: StateFlow<Boolean>
 }
-
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-class MobileConnectionRepositoryImpl(
-    private val context: Context,
-    private val subId: Int,
-    private val telephonyManager: TelephonyManager,
-    private val globalSettings: GlobalSettings,
-    defaultDataSubId: StateFlow<Int>,
-    globalMobileDataSettingChangedEvent: Flow<Unit>,
-    bgDispatcher: CoroutineDispatcher,
-    logger: ConnectivityPipelineLogger,
-    scope: CoroutineScope,
-) : MobileConnectionRepository {
-    init {
-        if (telephonyManager.subscriptionId != subId) {
-            throw IllegalStateException(
-                "TelephonyManager should be created with subId($subId). " +
-                    "Found ${telephonyManager.subscriptionId} instead."
-            )
-        }
-    }
-
-    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
-
-    override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
-        var state = MobileSubscriptionModel()
-        conflatedCallbackFlow {
-                // TODO (b/240569788): log all of these into the connectivity logger
-                val callback =
-                    object :
-                        TelephonyCallback(),
-                        TelephonyCallback.ServiceStateListener,
-                        TelephonyCallback.SignalStrengthsListener,
-                        TelephonyCallback.DataConnectionStateListener,
-                        TelephonyCallback.DataActivityListener,
-                        TelephonyCallback.CarrierNetworkListener,
-                        TelephonyCallback.DisplayInfoListener {
-                        override fun onServiceStateChanged(serviceState: ServiceState) {
-                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
-                            trySend(state)
-                        }
-
-                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
-                            val cdmaLevel =
-                                signalStrength
-                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
-                                    .let { strengths ->
-                                        if (!strengths.isEmpty()) {
-                                            strengths[0].level
-                                        } else {
-                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
-                                        }
-                                    }
-
-                            val primaryLevel = signalStrength.level
-
-                            state =
-                                state.copy(
-                                    cdmaLevel = cdmaLevel,
-                                    primaryLevel = primaryLevel,
-                                    isGsm = signalStrength.isGsm,
-                                )
-                            trySend(state)
-                        }
-
-                        override fun onDataConnectionStateChanged(
-                            dataState: Int,
-                            networkType: Int
-                        ) {
-                            state =
-                                state.copy(dataConnectionState = dataState.toDataConnectionType())
-                            trySend(state)
-                        }
-
-                        override fun onDataActivity(direction: Int) {
-                            state = state.copy(dataActivityDirection = direction)
-                            trySend(state)
-                        }
-
-                        override fun onCarrierNetworkChange(active: Boolean) {
-                            state = state.copy(carrierNetworkChangeActive = active)
-                            trySend(state)
-                        }
-
-                        override fun onDisplayInfoChanged(
-                            telephonyDisplayInfo: TelephonyDisplayInfo
-                        ) {
-                            val networkType =
-                                if (
-                                    telephonyDisplayInfo.overrideNetworkType ==
-                                        OVERRIDE_NETWORK_TYPE_NONE
-                                ) {
-                                    DefaultNetworkType(telephonyDisplayInfo.networkType)
-                                } else {
-                                    OverrideNetworkType(telephonyDisplayInfo.overrideNetworkType)
-                                }
-                            state = state.copy(resolvedNetworkType = networkType)
-                            trySend(state)
-                        }
-                    }
-                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
-                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
-            }
-            .onEach { telephonyCallbackEvent.tryEmit(Unit) }
-            .logOutputChange(logger, "MobileSubscriptionModel")
-            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
-    }
-
-    /** Produces whenever the mobile data setting changes for this subId */
-    private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
-        val observer =
-            object : ContentObserver(null) {
-                override fun onChange(selfChange: Boolean) {
-                    trySend(Unit)
-                }
-            }
-
-        globalSettings.registerContentObserver(
-            globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
-            /* notifyForDescendants */ true,
-            observer
-        )
-
-        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-    }
-
-    /**
-     * There are a few cases where we will need to poll [TelephonyManager] so we can update some
-     * internal state where callbacks aren't provided. Any of those events should be merged into
-     * this flow, which can be used to trigger the polling.
-     */
-    private val telephonyPollingEvent: Flow<Unit> =
-        merge(
-            telephonyCallbackEvent,
-            localMobileDataSettingChangedEvent,
-            globalMobileDataSettingChangedEvent,
-        )
-
-    override val dataEnabled: StateFlow<Boolean> =
-        telephonyPollingEvent
-            .mapLatest { dataConnectionAllowed() }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
-
-    private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
-
-    override val isDefaultDataSubscription: StateFlow<Boolean> =
-        defaultDataSubId
-            .mapLatest { it == subId }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
-
-    class Factory
-    @Inject
-    constructor(
-        private val context: Context,
-        private val telephonyManager: TelephonyManager,
-        private val logger: ConnectivityPipelineLogger,
-        private val globalSettings: GlobalSettings,
-        @Background private val bgDispatcher: CoroutineDispatcher,
-        @Application private val scope: CoroutineScope,
-    ) {
-        fun build(
-            subId: Int,
-            defaultDataSubId: StateFlow<Int>,
-            globalMobileDataSettingChangedEvent: Flow<Unit>,
-        ): MobileConnectionRepository {
-            return MobileConnectionRepositoryImpl(
-                context,
-                subId,
-                telephonyManager.createForSubscriptionId(subId),
-                globalSettings,
-                defaultDataSubId,
-                globalMobileDataSettingChangedEvent,
-                bgDispatcher,
-                logger,
-                scope,
-            )
-        }
-    }
-}
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 c3c1f14..aea85eb 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
@@ -16,53 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.IntentFilter
-import android.database.ContentObserver
-import android.net.ConnectivityManager
-import android.net.ConnectivityManager.NetworkCallback
-import android.net.Network
-import android.net.NetworkCapabilities
-import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
-import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
 import android.provider.Settings
-import android.provider.Settings.Global.MOBILE_DATA
-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
-import androidx.annotation.VisibleForTesting
-import com.android.internal.telephony.PhoneConstants
-import com.android.settingslib.mobile.MobileMappings
-import com.android.settingslib.mobile.MobileMappings.Config
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
+import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.util.settings.GlobalSettings
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.asExecutor
-import kotlinx.coroutines.channels.awaitClose
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.withContext
 
 /**
  * Repo for monitoring the complete active subscription info list, to be consumed and filtered based
@@ -70,14 +30,11 @@
  */
 interface MobileConnectionsRepository {
     /** Observable list of current mobile subscriptions */
-    val subscriptionsFlow: Flow<List<SubscriptionInfo>>
+    val subscriptions: StateFlow<List<SubscriptionModel>>
 
     /** Observable for the subscriptionId of the current mobile data connection */
     val activeMobileDataSubscriptionId: StateFlow<Int>
 
-    /** Observable for [MobileMappings.Config] tracking the defaults */
-    val defaultDataSubRatConfig: StateFlow<Config>
-
     /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
     val defaultDataSubId: StateFlow<Int>
 
@@ -89,203 +46,10 @@
 
     /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
     val globalMobileDataSettingChangedEvent: Flow<Unit>
-}
 
-@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
-@OptIn(ExperimentalCoroutinesApi::class)
-@SysUISingleton
-class MobileConnectionsRepositoryImpl
-@Inject
-constructor(
-    private val connectivityManager: ConnectivityManager,
-    private val subscriptionManager: SubscriptionManager,
-    private val telephonyManager: TelephonyManager,
-    private val logger: ConnectivityPipelineLogger,
-    broadcastDispatcher: BroadcastDispatcher,
-    private val globalSettings: GlobalSettings,
-    private val context: Context,
-    @Background private val bgDispatcher: CoroutineDispatcher,
-    @Application private val scope: CoroutineScope,
-    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
-) : MobileConnectionsRepository {
-    private val subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+    /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+    val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
 
-    /**
-     * State flow that emits the set of mobile data subscriptions, each represented by its own
-     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
-     * info object, but for now we keep track of the infos themselves.
-     */
-    override val subscriptionsFlow: StateFlow<List<SubscriptionInfo>> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
-                        override fun onSubscriptionsChanged() {
-                            trySend(Unit)
-                        }
-                    }
-
-                subscriptionManager.addOnSubscriptionsChangedListener(
-                    bgDispatcher.asExecutor(),
-                    callback,
-                )
-
-                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
-            }
-            .mapLatest { fetchSubscriptionsList() }
-            .onEach { infos -> dropUnusedReposFromCache(infos) }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
-
-    /** StateFlow that keeps track of the current active mobile data subscription */
-    override val activeMobileDataSubscriptionId: StateFlow<Int> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
-                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
-                            trySend(subId)
-                        }
-                    }
-
-                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
-                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
-            }
-            .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
-
-    private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
-        MutableSharedFlow(extraBufferCapacity = 1)
-
-    override val defaultDataSubId: StateFlow<Int> =
-        broadcastDispatcher
-            .broadcastFlow(
-                IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-            ) { intent, _ ->
-                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
-            }
-            .distinctUntilChanged()
-            .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                SubscriptionManager.getDefaultDataSubscriptionId()
-            )
-
-    private val carrierConfigChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-        )
-
-    /**
-     * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
-     * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
-     * config, so this will apply to every icon that we care about.
-     *
-     * Relevant bits in the config are things like
-     * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
-     *
-     * This flow will produce whenever the default data subscription or the carrier config changes.
-     */
-    override val defaultDataSubRatConfig: StateFlow<Config> =
-        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
-            .mapLatest { Config.readConfig(context) }
-            .stateIn(
-                scope,
-                SharingStarted.WhileSubscribed(),
-                initialValue = Config.readConfig(context)
-            )
-
-    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
-        if (!isValidSubId(subId)) {
-            throw IllegalArgumentException(
-                "subscriptionId $subId is not in the list of valid subscriptions"
-            )
-        }
-
-        return subIdRepositoryCache[subId]
-            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
-    }
-
-    /**
-     * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
-     * connection repositories also observe the URI for [MOBILE_DATA] + subId.
-     */
-    override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
-        val observer =
-            object : ContentObserver(null) {
-                override fun onChange(selfChange: Boolean) {
-                    trySend(Unit)
-                }
-            }
-
-        globalSettings.registerContentObserver(
-            globalSettings.getUriFor(MOBILE_DATA),
-            true,
-            observer
-        )
-
-        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-    }
-
-    @SuppressLint("MissingPermission")
-    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
-                        override fun onLost(network: Network) {
-                            // Send a disconnected model when lost. Maybe should create a sealed
-                            // type or null here?
-                            trySend(MobileConnectivityModel())
-                        }
-
-                        override fun onCapabilitiesChanged(
-                            network: Network,
-                            caps: NetworkCapabilities
-                        ) {
-                            trySend(
-                                MobileConnectivityModel(
-                                    isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
-                                    isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
-                                )
-                            )
-                        }
-                    }
-
-                connectivityManager.registerDefaultNetworkCallback(callback)
-
-                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
-
-    private fun isValidSubId(subId: Int): Boolean {
-        subscriptionsFlow.value.forEach {
-            if (it.subscriptionId == subId) {
-                return true
-            }
-        }
-
-        return false
-    }
-
-    @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
-
-    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
-        return mobileConnectionRepositoryFactory.build(
-            subId,
-            defaultDataSubId,
-            globalMobileDataSettingChangedEvent,
-        )
-    }
-
-    private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
-        // 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.keys.forEach {
-            if (!currentValidSubscriptionIds.contains(it)) {
-                subIdRepositoryCache.remove(it)
-            }
-        }
-    }
-
-    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
-        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+    /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+    val defaultMobileIconGroup: Flow<MobileIconGroup>
 }
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
new file mode 100644
index 0000000..d8e0e81
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcher.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+import com.android.settingslib.SignalIcon
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+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.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * A provider for the [MobileConnectionsRepository] interface that can choose between the Demo and
+ * Prod concrete implementations at runtime. It works by defining a base flow, [activeRepo], which
+ * switches based on the latest information from [DemoModeController], and switches every flow in
+ * the interface to point to the currently-active provider. This allows us to put the demo mode
+ * 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
+ *                 │
+ *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ *                 │
+ * DemoRepository
+ * ```
+ *
+ * NOTE: because the UI layer for mobile icons relies on a nested-repository structure, it is likely
+ * that we will have to drain the subscription list whenever demo mode changes. Otherwise if a real
+ * subscription list [1] is replaced with a demo subscription list [1], the view models will not see
+ * a change (due to `distinctUntilChanged`) and will not refresh their data providers to the demo
+ * implementation.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileRepositorySwitcher
+@Inject
+constructor(
+    @Application scope: CoroutineScope,
+    val realRepository: MobileConnectionsRepositoryImpl,
+    val demoMobileConnectionsRepository: DemoMobileConnectionsRepository,
+    demoModeController: DemoModeController,
+) : MobileConnectionsRepository {
+
+    val isDemoMode: StateFlow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DemoMode {
+                        override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+                            // Nothing, we just care about on/off
+                        }
+
+                        override fun onDemoModeStarted() {
+                            demoMobileConnectionsRepository.startProcessingCommands()
+                            trySend(true)
+                        }
+
+                        override fun onDemoModeFinished() {
+                            demoMobileConnectionsRepository.stopProcessingCommands()
+                            trySend(false)
+                        }
+                    }
+
+                demoModeController.addCallback(callback)
+                awaitClose { demoModeController.removeCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+    // Convenient definition flow for the currently active repo (based on demo mode or not)
+    @VisibleForTesting
+    internal val activeRepo: StateFlow<MobileConnectionsRepository> =
+        isDemoMode
+            .mapLatest { demoMode ->
+                if (demoMode) {
+                    demoMobileConnectionsRepository
+                } else {
+                    realRepository
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository)
+
+    override val subscriptions: StateFlow<List<SubscriptionModel>> =
+        activeRepo
+            .flatMapLatest { it.subscriptions }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.subscriptions.value)
+
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.activeMobileDataSubscriptionId }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.activeMobileDataSubscriptionId.value
+            )
+
+    override val defaultMobileIconMapping: Flow<Map<String, SignalIcon.MobileIconGroup>> =
+        activeRepo.flatMapLatest { it.defaultMobileIconMapping }
+
+    override val defaultMobileIconGroup: Flow<SignalIcon.MobileIconGroup> =
+        activeRepo.flatMapLatest { it.defaultMobileIconGroup }
+
+    override val defaultDataSubId: StateFlow<Int> =
+        activeRepo
+            .flatMapLatest { it.defaultDataSubId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realRepository.defaultDataSubId.value)
+
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        activeRepo
+            .flatMapLatest { it.defaultMobileNetworkConnectivity }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                realRepository.defaultMobileNetworkConnectivity.value
+            )
+
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> =
+        activeRepo.flatMapLatest { it.globalMobileDataSettingChangedEvent }
+
+    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+        if (isDemoMode.value) {
+            return demoMobileConnectionsRepository.getRepoForSubId(subId)
+        }
+        return realRepository.getRepoForSubId(subId)
+    }
+}
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
new file mode 100644
index 0000000..1e7fae7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -0,0 +1,262 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.demo
+
+import android.content.Context
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.util.Log
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+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.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** This repository vends out data based on demo mode commands */
+@OptIn(ExperimentalCoroutinesApi::class)
+class DemoMobileConnectionsRepository
+@Inject
+constructor(
+    private val dataSource: DemoModeMobileConnectionDataSource,
+    @Application private val scope: CoroutineScope,
+    context: Context,
+) : MobileConnectionsRepository {
+
+    private var demoCommandJob: Job? = null
+
+    private var connectionRepoCache = mutableMapOf<Int, DemoMobileConnectionRepository>()
+    private val subscriptionInfoCache = mutableMapOf<Int, SubscriptionModel>()
+    val demoModeFinishedEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+    private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+    override val subscriptions =
+        _subscriptions
+            .onEach { infos -> dropUnusedReposFromCache(infos) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _subscriptions.value)
+
+    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 }
+
+        connectionRepoCache =
+            connectionRepoCache
+                .filter { currentValidSubscriptionIds.contains(it.key) }
+                .toMutableMap()
+    }
+
+    private fun maybeCreateSubscription(subId: Int) {
+        if (!subscriptionInfoCache.containsKey(subId)) {
+            SubscriptionModel(subscriptionId = subId, isOpportunistic = false).also {
+                subscriptionInfoCache[subId] = it
+            }
+
+            _subscriptions.value = subscriptionInfoCache.values.toList()
+        }
+    }
+
+    // TODO(b/261029387): add a command for this value
+    override val activeMobileDataSubscriptionId =
+        subscriptions
+            .mapLatest { infos ->
+                // For now, active is just the first in the list
+                infos.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                subscriptions.value.firstOrNull()?.subscriptionId ?: INVALID_SUBSCRIPTION_ID
+            )
+
+    /** Demo mode doesn't currently support modifications to the mobile mappings */
+    val defaultDataSubRatConfig = MutableStateFlow(MobileMappings.Config.readConfig(context))
+
+    override val defaultMobileIconGroup = flowOf(TelephonyIcons.THREE_G)
+
+    override val defaultMobileIconMapping = MutableStateFlow(TelephonyIcons.ICON_NAME_TO_ICON)
+
+    /**
+     * In order to maintain compatibility with the old demo mode shell command API, reverse the
+     * [MobileMappings] lookup from (NetworkType: String -> Icon: MobileIconGroup), so that we can
+     * parse the string from the command line into a preferred icon group, and send _a_ valid
+     * network type for that icon through the pipeline.
+     *
+     * Note: collisions don't matter here, because the data source (the command line) only cares
+     * about the resulting icon, not the underlying network type.
+     */
+    private val mobileMappingsReverseLookup: StateFlow<Map<SignalIcon.MobileIconGroup, String>> =
+        defaultMobileIconMapping
+            .mapLatest { networkToIconMap -> networkToIconMap.reverse() }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                defaultMobileIconMapping.value.reverse()
+            )
+
+    private fun <K, V> Map<K, V>.reverse() = entries.associateBy({ it.value }) { it.key }
+
+    // TODO(b/261029387): add a command for this value
+    override val defaultDataSubId =
+        activeMobileDataSubscriptionId.stateIn(
+            scope,
+            SharingStarted.WhileSubscribed(),
+            INVALID_SUBSCRIPTION_ID
+        )
+
+    // TODO(b/261029387): not yet supported
+    override val defaultMobileNetworkConnectivity = MutableStateFlow(MobileConnectivityModel())
+
+    override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
+        return connectionRepoCache[subId]
+            ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+    }
+
+    override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+
+    fun startProcessingCommands() {
+        demoCommandJob =
+            scope.launch {
+                dataSource.mobileEvents.filterNotNull().collect { event -> processEvent(event) }
+            }
+    }
+
+    fun stopProcessingCommands() {
+        demoCommandJob?.cancel()
+        _subscriptions.value = listOf()
+        connectionRepoCache.clear()
+        subscriptionInfoCache.clear()
+    }
+
+    private fun processEvent(event: FakeNetworkEventModel) {
+        when (event) {
+            is Mobile -> {
+                processEnabledMobileState(event)
+            }
+            is MobileDisabled -> {
+                processDisabledMobileState(event)
+            }
+        }
+    }
+
+    private fun processEnabledMobileState(state: Mobile) {
+        // get or create the connection repo, and set its values
+        val subId = state.subId ?: DEFAULT_SUB_ID
+        maybeCreateSubscription(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.isDefaultDataSubscription.value = state.dataType != null
+
+        connection.connectionInfo.value = state.toMobileConnectionModel()
+    }
+
+    private fun processDisabledMobileState(state: MobileDisabled) {
+        if (_subscriptions.value.isEmpty()) {
+            // Nothing to do here
+            return
+        }
+
+        val subId =
+            state.subId
+                ?: run {
+                    // For sake of usability, we can allow for no subId arg if there is only one
+                    // subscription
+                    if (_subscriptions.value.size > 1) {
+                        Log.d(
+                            TAG,
+                            "processDisabledMobileState: Unable to infer subscription to " +
+                                "disable. Specify subId using '-e slot <subId>'" +
+                                "Known subIds: [${subIdsString()}]"
+                        )
+                        return
+                    }
+
+                    // Use the only existing subscription as our arg, since there is only one
+                    _subscriptions.value[0].subscriptionId
+                }
+
+        removeSubscription(subId)
+    }
+
+    private fun removeSubscription(subId: Int) {
+        val currentSubscriptions = _subscriptions.value
+        subscriptionInfoCache.remove(subId)
+        _subscriptions.value = currentSubscriptions.filter { it.subscriptionId != subId }
+    }
+
+    private fun subIdsString(): String =
+        _subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
+
+    private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
+        return MobileConnectionModel(
+            isEmergencyOnly = false, // TODO(b/261029387): not yet supported
+            isGsm = false, // TODO(b/261029387): not yet supported
+            cdmaLevel = level ?: 0,
+            primaryLevel = level ?: 0,
+            dataConnectionState =
+                DataConnectionState.Connected, // TODO(b/261029387): not yet supported
+            dataActivityDirection = activity,
+            carrierNetworkChangeActive = carrierNetworkChange,
+            resolvedNetworkType = dataType.toResolvedNetworkType()
+        )
+    }
+
+    private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
+        val key = mobileMappingsReverseLookup.value[this] ?: "dis"
+        return DefaultNetworkType(DEMO_NET_TYPE, key)
+    }
+
+    companion object {
+        private const val TAG = "DemoMobileConnectionsRepo"
+
+        private const val DEFAULT_SUB_ID = 1
+
+        private const val DEMO_NET_TYPE = 1234
+    }
+}
+
+class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+    override val connectionInfo = MutableStateFlow(MobileConnectionModel())
+
+    override val dataEnabled = MutableStateFlow(true)
+
+    override val isDefaultDataSubscription = MutableStateFlow(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
new file mode 100644
index 0000000..da55787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoModeMobileConnectionDataSource.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.os.Bundle
+import android.telephony.Annotation.DataActivityType
+import android.telephony.TelephonyManager.DATA_ACTIVITY_IN
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
+import android.telephony.TelephonyManager.DATA_ACTIVITY_OUT
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/**
+ * Data source that can map from demo mode commands to inputs into the
+ * [DemoMobileConnectionsRepository]'s flows
+ */
+@SysUISingleton
+class DemoModeMobileConnectionDataSource
+@Inject
+constructor(
+    demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) {
+    private val demoCommandStream: Flow<Bundle> = conflatedCallbackFlow {
+        val callback =
+            object : DemoMode {
+                override fun demoCommands(): List<String> = listOf(COMMAND_NETWORK)
+
+                override fun dispatchDemoCommand(command: String, args: Bundle) {
+                    trySend(args)
+                }
+
+                override fun onDemoModeFinished() {
+                    // Handled elsewhere
+                }
+
+                override fun onDemoModeStarted() {
+                    // Handled elsewhere
+                }
+            }
+
+        demoModeController.addCallback(callback)
+        awaitClose { demoModeController.removeCallback(callback) }
+    }
+
+    // If the args contains "mobile", then all of the args are relevant. It's just the way demo mode
+    // commands work and it's a little silly
+    private val _mobileCommands = demoCommandStream.map { args -> args.toMobileEvent() }
+    val mobileEvents = _mobileCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+    private fun Bundle.toMobileEvent(): FakeNetworkEventModel? {
+        val mobile = getString("mobile") ?: return null
+        return if (mobile == "show") {
+            activeMobileEvent()
+        } else {
+            MobileDisabled(subId = getString("slot")?.toInt())
+        }
+    }
+
+    /** Parse a valid mobile command string into a network event */
+    private fun Bundle.activeMobileEvent(): Mobile {
+        // There are many key/value pairs supported by mobile demo mode. Bear with me here
+        val level = getString("level")?.toInt()
+        val dataType = getString("datatype")?.toDataType()
+        val slot = getString("slot")?.toInt()
+        val carrierId = getString("carrierid")?.toInt()
+        val inflateStrength = getString("inflate")?.toBoolean()
+        val activity = getString("activity")?.toActivity()
+        val carrierNetworkChange = getString("carriernetworkchange") == "show"
+
+        return Mobile(
+            level = level,
+            dataType = dataType,
+            subId = slot,
+            carrierId = carrierId,
+            inflateStrength = inflateStrength,
+            activity = activity,
+            carrierNetworkChange = carrierNetworkChange,
+        )
+    }
+}
+
+private fun String.toDataType(): MobileIconGroup =
+    when (this) {
+        "1x" -> TelephonyIcons.ONE_X
+        "3g" -> TelephonyIcons.THREE_G
+        "4g" -> TelephonyIcons.FOUR_G
+        "4g+" -> TelephonyIcons.FOUR_G_PLUS
+        "5g" -> TelephonyIcons.NR_5G
+        "5ge" -> TelephonyIcons.LTE_CA_5G_E
+        "5g+" -> TelephonyIcons.NR_5G_PLUS
+        "e" -> TelephonyIcons.E
+        "g" -> TelephonyIcons.G
+        "h" -> TelephonyIcons.H
+        "h+" -> TelephonyIcons.H_PLUS
+        "lte" -> TelephonyIcons.LTE
+        "lte+" -> TelephonyIcons.LTE_PLUS
+        "dis" -> TelephonyIcons.DATA_DISABLED
+        "not" -> TelephonyIcons.NOT_DEFAULT_DATA
+        else -> TelephonyIcons.UNKNOWN
+    }
+
+@DataActivityType
+private fun String.toActivity(): Int =
+    when (this) {
+        "inout" -> DATA_ACTIVITY_INOUT
+        "in" -> DATA_ACTIVITY_IN
+        "out" -> DATA_ACTIVITY_OUT
+        else -> DATA_ACTIVITY_NONE
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
new file mode 100644
index 0000000..3f3acaf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/model/FakeNetworkEventModel.kt
@@ -0,0 +1,43 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.demo.model
+
+import android.telephony.Annotation.DataActivityType
+import com.android.settingslib.SignalIcon
+
+/**
+ * Model for the demo commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeNetworkEventModel {
+    data class Mobile(
+        val level: Int?,
+        val dataType: SignalIcon.MobileIconGroup?,
+        // Null means the default (chosen by the repository)
+        val subId: Int?,
+        val carrierId: Int?,
+        val inflateStrength: Boolean?,
+        @DataActivityType val activity: Int?,
+        val carrierNetworkChange: Boolean,
+    ) : FakeNetworkEventModel
+
+    data class MobileDisabled(
+        // Null means the default (chosen by the repository)
+        val subId: Int?
+    ) : FakeNetworkEventModel
+}
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
new file mode 100644
index 0000000..15505fd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -0,0 +1,253 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
+import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrengthCdma
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyCallback
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
+import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+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.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class MobileConnectionRepositoryImpl(
+    private val context: Context,
+    override val subId: Int,
+    private val telephonyManager: TelephonyManager,
+    private val globalSettings: GlobalSettings,
+    defaultDataSubId: StateFlow<Int>,
+    globalMobileDataSettingChangedEvent: Flow<Unit>,
+    mobileMappingsProxy: MobileMappingsProxy,
+    bgDispatcher: CoroutineDispatcher,
+    logger: ConnectivityPipelineLogger,
+    scope: CoroutineScope,
+) : MobileConnectionRepository {
+    init {
+        if (telephonyManager.subscriptionId != subId) {
+            throw IllegalStateException(
+                "TelephonyManager should be created with subId($subId). " +
+                    "Found ${telephonyManager.subscriptionId} instead."
+            )
+        }
+    }
+
+    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
+    override val connectionInfo: StateFlow<MobileConnectionModel> = run {
+        var state = MobileConnectionModel()
+        conflatedCallbackFlow {
+                // TODO (b/240569788): log all of these into the connectivity logger
+                val callback =
+                    object :
+                        TelephonyCallback(),
+                        TelephonyCallback.ServiceStateListener,
+                        TelephonyCallback.SignalStrengthsListener,
+                        TelephonyCallback.DataConnectionStateListener,
+                        TelephonyCallback.DataActivityListener,
+                        TelephonyCallback.CarrierNetworkListener,
+                        TelephonyCallback.DisplayInfoListener {
+                        override fun onServiceStateChanged(serviceState: ServiceState) {
+                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            trySend(state)
+                        }
+
+                        override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            val cdmaLevel =
+                                signalStrength
+                                    .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
+                                    .let { strengths ->
+                                        if (!strengths.isEmpty()) {
+                                            strengths[0].level
+                                        } else {
+                                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                                        }
+                                    }
+
+                            val primaryLevel = signalStrength.level
+
+                            state =
+                                state.copy(
+                                    cdmaLevel = cdmaLevel,
+                                    primaryLevel = primaryLevel,
+                                    isGsm = signalStrength.isGsm,
+                                )
+                            trySend(state)
+                        }
+
+                        override fun onDataConnectionStateChanged(
+                            dataState: Int,
+                            networkType: Int
+                        ) {
+                            state =
+                                state.copy(dataConnectionState = dataState.toDataConnectionType())
+                            trySend(state)
+                        }
+
+                        override fun onDataActivity(direction: Int) {
+                            state = state.copy(dataActivityDirection = direction)
+                            trySend(state)
+                        }
+
+                        override fun onCarrierNetworkChange(active: Boolean) {
+                            state = state.copy(carrierNetworkChangeActive = active)
+                            trySend(state)
+                        }
+
+                        override fun onDisplayInfoChanged(
+                            telephonyDisplayInfo: TelephonyDisplayInfo
+                        ) {
+
+                            val networkType =
+                                if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+                                    UnknownNetworkType
+                                } else if (
+                                    telephonyDisplayInfo.overrideNetworkType ==
+                                        OVERRIDE_NETWORK_TYPE_NONE
+                                ) {
+                                    DefaultNetworkType(
+                                        telephonyDisplayInfo.networkType,
+                                        mobileMappingsProxy.toIconKey(
+                                            telephonyDisplayInfo.networkType
+                                        )
+                                    )
+                                } else {
+                                    OverrideNetworkType(
+                                        telephonyDisplayInfo.overrideNetworkType,
+                                        mobileMappingsProxy.toIconKeyOverride(
+                                            telephonyDisplayInfo.overrideNetworkType
+                                        )
+                                    )
+                                }
+                            state = state.copy(resolvedNetworkType = networkType)
+                            trySend(state)
+                        }
+                    }
+                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+            }
+            .onEach { telephonyCallbackEvent.tryEmit(Unit) }
+            .logOutputChange(logger, "MobileSubscriptionModel")
+            .stateIn(scope, SharingStarted.WhileSubscribed(), state)
+    }
+
+    /** Produces whenever the mobile data setting changes for this subId */
+    private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+            /* notifyForDescendants */ true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
+    /**
+     * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+     * internal state where callbacks aren't provided. Any of those events should be merged into
+     * this flow, which can be used to trigger the polling.
+     */
+    private val telephonyPollingEvent: Flow<Unit> =
+        merge(
+            telephonyCallbackEvent,
+            localMobileDataSettingChangedEvent,
+            globalMobileDataSettingChangedEvent,
+        )
+
+    override val dataEnabled: StateFlow<Boolean> =
+        telephonyPollingEvent
+            .mapLatest { dataConnectionAllowed() }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+
+    private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+
+    override val isDefaultDataSubscription: StateFlow<Boolean> =
+        defaultDataSubId
+            .mapLatest { it == subId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
+    class Factory
+    @Inject
+    constructor(
+        private val context: Context,
+        private val telephonyManager: TelephonyManager,
+        private val logger: ConnectivityPipelineLogger,
+        private val globalSettings: GlobalSettings,
+        private val mobileMappingsProxy: MobileMappingsProxy,
+        @Background private val bgDispatcher: CoroutineDispatcher,
+        @Application private val scope: CoroutineScope,
+    ) {
+        fun build(
+            subId: Int,
+            defaultDataSubId: StateFlow<Int>,
+            globalMobileDataSettingChangedEvent: Flow<Unit>,
+        ): MobileConnectionRepository {
+            return MobileConnectionRepositoryImpl(
+                context,
+                subId,
+                telephonyManager.createForSubscriptionId(subId),
+                globalSettings,
+                defaultDataSubId,
+                globalMobileDataSettingChangedEvent,
+                mobileMappingsProxy,
+                bgDispatcher,
+                logger,
+                scope,
+            )
+        }
+    }
+}
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
new file mode 100644
index 0000000..f27a9c9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -0,0 +1,281 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.prod
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings.Global.MOBILE_DATA
+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
+import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
+import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.settingslib.mobile.MobileMappings
+import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.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.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class MobileConnectionsRepositoryImpl
+@Inject
+constructor(
+    private val connectivityManager: ConnectivityManager,
+    private val subscriptionManager: SubscriptionManager,
+    private val telephonyManager: TelephonyManager,
+    private val logger: ConnectivityPipelineLogger,
+    mobileMappingsProxy: MobileMappingsProxy,
+    broadcastDispatcher: BroadcastDispatcher,
+    private val globalSettings: GlobalSettings,
+    private val context: Context,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Application private val scope: CoroutineScope,
+    private val mobileConnectionRepositoryFactory: MobileConnectionRepositoryImpl.Factory
+) : MobileConnectionsRepository {
+    private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
+
+    /**
+     * State flow that emits the set of mobile data subscriptions, each represented by its own
+     * [SubscriptionInfo]. We probably only need the [SubscriptionInfo.getSubscriptionId] of each
+     * info object, but for now we keep track of the infos themselves.
+     */
+    override val subscriptions: StateFlow<List<SubscriptionModel>> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : SubscriptionManager.OnSubscriptionsChangedListener() {
+                        override fun onSubscriptionsChanged() {
+                            trySend(Unit)
+                        }
+                    }
+
+                subscriptionManager.addOnSubscriptionsChangedListener(
+                    bgDispatcher.asExecutor(),
+                    callback,
+                )
+
+                awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
+            }
+            .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
+            .onEach { infos -> dropUnusedReposFromCache(infos) }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
+
+    /** StateFlow that keeps track of the current active mobile data subscription */
+    override val activeMobileDataSubscriptionId: StateFlow<Int> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : TelephonyCallback(), ActiveDataSubscriptionIdListener {
+                        override fun onActiveDataSubscriptionIdChanged(subId: Int) {
+                            trySend(subId)
+                        }
+                    }
+
+                telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
+                awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
+            }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+    private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+
+    override val defaultDataSubId: StateFlow<Int> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+            ) { intent, _ ->
+                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+            }
+            .distinctUntilChanged()
+            .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                SubscriptionManager.getDefaultDataSubscriptionId()
+            )
+
+    private val carrierConfigChangedEvent =
+        broadcastDispatcher.broadcastFlow(
+            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
+        )
+
+    /**
+     * [Config] is an object that tracks relevant configuration flags for a given subscription ID.
+     * In the case of [MobileMappings], it's hard-coded to check the default data subscription's
+     * config, so this will apply to every icon that we care about.
+     *
+     * Relevant bits in the config are things like
+     * [CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL]
+     *
+     * This flow will produce whenever the default data subscription or the carrier config changes.
+     */
+    private val defaultDataSubRatConfig: StateFlow<Config> =
+        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+            .mapLatest { Config.readConfig(context) }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                initialValue = Config.readConfig(context)
+            )
+
+    override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
+        defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
+
+    override val defaultMobileIconGroup: Flow<MobileIconGroup> =
+        defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
+
+    override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
+        if (!isValidSubId(subId)) {
+            throw IllegalArgumentException(
+                "subscriptionId $subId is not in the list of valid subscriptions"
+            )
+        }
+
+        return subIdRepositoryCache[subId]
+            ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
+    }
+
+    /**
+     * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+     * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+     */
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor(MOBILE_DATA),
+            true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
+    @SuppressLint("MissingPermission")
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+                        override fun onLost(network: Network) {
+                            // Send a disconnected model when lost. Maybe should create a sealed
+                            // type or null here?
+                            trySend(MobileConnectivityModel())
+                        }
+
+                        override fun onCapabilitiesChanged(
+                            network: Network,
+                            caps: NetworkCapabilities
+                        ) {
+                            trySend(
+                                MobileConnectivityModel(
+                                    isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+                                    isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+                                )
+                            )
+                        }
+                    }
+
+                connectivityManager.registerDefaultNetworkCallback(callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
+    private fun isValidSubId(subId: Int): Boolean {
+        subscriptions.value.forEach {
+            if (it.subscriptionId == subId) {
+                return true
+            }
+        }
+
+        return false
+    }
+
+    @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
+
+    private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
+        return mobileConnectionRepositoryFactory.build(
+            subId,
+            defaultDataSubId,
+            globalMobileDataSettingChangedEvent,
+        )
+    }
+
+    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()
+    }
+
+    private suspend fun fetchSubscriptionsList(): List<SubscriptionInfo> =
+        withContext(bgDispatcher) { subscriptionManager.completeActiveSubscriptionInfoList }
+
+    private fun SubscriptionInfo.toSubscriptionModel(): SubscriptionModel =
+        SubscriptionModel(
+            subscriptionId = subscriptionId,
+            isOpportunistic = isOpportunistic,
+        )
+}
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 0da84f0..8e1197c 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
@@ -20,10 +20,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -70,10 +67,9 @@
     defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
     defaultMobileIconGroup: StateFlow<MobileIconGroup>,
     override val isDefaultConnectionFailed: StateFlow<Boolean>,
-    mobileMappingsProxy: MobileMappingsProxy,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
-    private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
+    private val connectionInfo = connectionRepository.connectionInfo
 
     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
 
@@ -82,33 +78,27 @@
     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
     override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
         combine(
-                mobileStatusInfo,
+                connectionInfo,
                 defaultMobileIconMapping,
                 defaultMobileIconGroup,
             ) { info, mapping, defaultGroup ->
-                val lookupKey =
-                    when (val resolved = info.resolvedNetworkType) {
-                        is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
-                        is OverrideNetworkType ->
-                            mobileMappingsProxy.toIconKeyOverride(resolved.type)
-                    }
-                mapping[lookupKey] ?: defaultGroup
+                mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
 
     override val isEmergencyOnly: StateFlow<Boolean> =
-        mobileStatusInfo
+        connectionInfo
             .mapLatest { it.isEmergencyOnly }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val level: StateFlow<Int> =
-        mobileStatusInfo
-            .mapLatest { mobileModel ->
+        connectionInfo
+            .mapLatest { connection ->
                 // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
-                if (mobileModel.isGsm) {
-                    mobileModel.primaryLevel
+                if (connection.isGsm) {
+                    connection.primaryLevel
                 } else {
-                    mobileModel.cdmaLevel
+                    connection.cdmaLevel
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -120,7 +110,7 @@
     override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
 
     override val isDataConnected: StateFlow<Boolean> =
-        mobileStatusInfo
-            .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+        connectionInfo
+            .mapLatest { connection -> connection.dataConnectionState == Connected }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
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 a4175c3..6f8fb2e 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
@@ -17,17 +17,16 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.telephony.CarrierConfigManager
-import android.telephony.SubscriptionInfo
 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.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.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -53,7 +52,7 @@
  */
 interface MobileIconsInteractor {
     /** List of subscriptions, potentially filtered for CBRS */
-    val filteredSubscriptions: Flow<List<SubscriptionInfo>>
+    val filteredSubscriptions: Flow<List<SubscriptionModel>>
     /** True if the active mobile data subscription has data enabled */
     val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
     /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
@@ -79,7 +78,6 @@
 constructor(
     private val mobileConnectionsRepo: MobileConnectionsRepository,
     private val carrierConfigTracker: CarrierConfigTracker,
-    private val mobileMappingsProxy: MobileMappingsProxy,
     userSetupRepo: UserSetupRepository,
     @Application private val scope: CoroutineScope,
 ) : MobileIconsInteractor {
@@ -102,8 +100,8 @@
             .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
-    private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
-        mobileConnectionsRepo.subscriptionsFlow
+    private val unfilteredSubscriptions: Flow<List<SubscriptionModel>> =
+        mobileConnectionsRepo.subscriptions
 
     /**
      * Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -118,7 +116,7 @@
      * [CarrierConfigManager.KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN],
      * and by checking which subscription is opportunistic, or which one is active.
      */
-    override val filteredSubscriptions: Flow<List<SubscriptionInfo>> =
+    override val filteredSubscriptions: Flow<List<SubscriptionModel>> =
         combine(unfilteredSubscriptions, activeMobileDataSubscriptionId) { unfilteredSubs, activeId
             ->
             // Based on the old logic,
@@ -154,15 +152,19 @@
      * subscription Id. This mapping is the same for every subscription.
      */
     override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
-        mobileConnectionsRepo.defaultDataSubRatConfig
-            .mapLatest { mobileMappingsProxy.mapIconSets(it) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
+        mobileConnectionsRepo.defaultMobileIconMapping.stateIn(
+            scope,
+            SharingStarted.WhileSubscribed(),
+            initialValue = mapOf()
+        )
 
     /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
     override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
-        mobileConnectionsRepo.defaultDataSubRatConfig
-            .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
+        mobileConnectionsRepo.defaultMobileIconGroup.stateIn(
+            scope,
+            SharingStarted.WhileSubscribed(),
+            initialValue = TelephonyIcons.G
+        )
 
     /**
      * We want to show an error state when cellular has actually failed to validate, but not if some
@@ -189,7 +191,6 @@
             defaultMobileIconMapping,
             defaultMobileIconGroup,
             isDefaultConnectionFailed,
-            mobileMappingsProxy,
             mobileConnectionsRepo.getRepoForSubId(subId),
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index c7e0ce1..62fa723 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui
 
+import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
@@ -29,9 +30,10 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.mapLatest
-import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
 
 /**
  * This class is intended to provide a context to collect on the
@@ -50,12 +52,12 @@
     interactor: MobileIconsInteractor,
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
-    @Application scope: CoroutineScope,
+    @Application private val scope: CoroutineScope,
     private val statusBarPipelineFlags: StatusBarPipelineFlags,
-) {
+) : CoreStartable {
     private val mobileSubIds: Flow<List<Int>> =
-        interactor.filteredSubscriptions.mapLatest { infos ->
-            infos.map { subscriptionInfo -> subscriptionInfo.subscriptionId }
+        interactor.filteredSubscriptions.mapLatest { subscriptions ->
+            subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId }
         }
 
     /**
@@ -66,18 +68,19 @@
      * NOTE: this should go away as the view presenter learns more about this data pipeline
      */
     private val mobileSubIdsState: StateFlow<List<Int>> =
-        mobileSubIds
-            .onEach {
-                // Only notify the icon controller if we want to *render* the new icons.
-                // Note that this flow may still run if
-                // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
-                // get the logging data without rendering.
-                if (statusBarPipelineFlags.useNewMobileIcons()) {
-                    // Notify the icon controller here so that it knows to add icons
-                    iconController.setNewMobileIconSubIds(it)
-                }
+        mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+
+    override fun start() {
+        // Only notify the icon controller if we want to *render* the new icons.
+        // Note that this flow may still run if
+        // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+        // get the logging data without rendering.
+        if (statusBarPipelineFlags.useNewMobileIcons()) {
+            scope.launch {
+                mobileSubIds.collectLatest { iconController.setNewMobileIconSubIds(it) }
             }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
+        }
+    }
 
     /**
      * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
index ec4fa9c..0ab7bcd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/view/ModernStatusBarMobileView.kt
@@ -32,6 +32,8 @@
     attrs: AttributeSet?,
 ) : BaseStatusBarFrameLayout(context, attrs) {
 
+    var subId: Int = -1
+
     private lateinit var slot: String
     override fun getSlot() = slot
 
@@ -76,6 +78,7 @@
                     as ModernStatusBarMobileView)
                 .also {
                     it.slot = slot
+                    it.subId = viewModel.subscriptionId
                     MobileIconBinder.bind(it, viewModel)
                 }
         }
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 24c1db9..2349cb7 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
@@ -23,7 +23,7 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import javax.inject.Inject
 import kotlinx.coroutines.InternalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
@@ -33,7 +33,7 @@
 class MobileIconsViewModel
 @Inject
 constructor(
-    val subscriptionIdsFlow: Flow<List<Int>>,
+    val subscriptionIdsFlow: StateFlow<List<Int>>,
     private val interactor: MobileIconsInteractor,
     private val logger: ConnectivityPipelineLogger,
 ) {
@@ -51,7 +51,7 @@
         private val interactor: MobileIconsInteractor,
         private val logger: ConnectivityPipelineLogger,
     ) {
-        fun create(subscriptionIdsFlow: Flow<List<Int>>): MobileIconsViewModel {
+        fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
             return MobileIconsViewModel(
                 subscriptionIdsFlow,
                 interactor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
index b816364..5223760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import javax.inject.Inject
@@ -73,7 +74,9 @@
                         // Note that this flow may still run if
                         // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
                         // want to get the logging data without rendering.
-                        if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+                        if (
+                            wifiIcon is WifiIcon.Visible && statusBarPipelineFlags.useNewWifiIcon()
+                        ) {
                             iconController.setNewWifiIcon()
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 345f8cb..f5b5950 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -92,8 +93,10 @@
 
                 launch {
                     viewModel.wifiIcon.collect { wifiIcon ->
-                        view.isVisible = wifiIcon != null
-                        wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
+                        view.isVisible = wifiIcon is WifiIcon.Visible
+                        if (wifiIcon is WifiIcon.Visible) {
+                            IconViewBinder.bind(wifiIcon.icon, iconView)
+                        }
                     }
                 }
 
@@ -135,7 +138,7 @@
 
         return object : Binding {
             override fun getShouldIconBeVisible(): Boolean {
-                return viewModel.wifiIcon.value != null
+                return viewModel.wifiIcon.value is WifiIcon.Visible
             }
 
             override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
new file mode 100644
index 0000000..e491d2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/model/WifiIcon.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.statusbar.pipeline.wifi.ui.model
+
+import android.annotation.DrawableRes
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Represents the various states of the wifi icon. */
+sealed interface WifiIcon : Diffable<WifiIcon> {
+    /** Represents a wifi icon that should be hidden (not visible). */
+    object Hidden : WifiIcon {
+        override fun toString() = "hidden"
+    }
+
+    /**
+     * Represents a visible wifi icon that uses [res] as its image and [contentDescription] as its
+     * description.
+     */
+    class Visible(
+        @DrawableRes res: Int,
+        val contentDescription: ContentDescription.Loaded,
+    ) : WifiIcon {
+        val icon = Icon.Resource(res, contentDescription)
+
+        override fun toString() = contentDescription.description.toString()
+    }
+
+    override fun logDiffs(prevVal: WifiIcon, row: TableRowLogger) {
+        if (prevVal.toString() != toString()) {
+            row.logChange(COL_ICON, toString())
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ICON, toString())
+    }
+}
+
+private const val COL_ICON = "wifiIcon"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
index 95ab251..a29c9b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -28,7 +28,7 @@
  */
 class HomeWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
index 86535d6..1e190fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown on keyguard (lockscreen). */
 class KeyguardWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
index 7cbdf5d..e35a8fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flowOf
@@ -33,8 +33,8 @@
     statusBarPipelineFlags: StatusBarPipelineFlags,
     debugTint: Int,
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    val wifiIcon: StateFlow<Icon.Resource?>,
+    /** The wifi icon that should be displayed. */
+    val wifiIcon: StateFlow<WifiIcon>,
 
     /** True if the activity in view should be visible. */
     val isActivityInViewVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
index fd54c5f..18e62b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
@@ -17,15 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.graphics.Color
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
 /** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
 class QsWifiViewModel(
     statusBarPipelineFlags: StatusBarPipelineFlags,
-    wifiIcon: StateFlow<Icon.Resource?>,
+    wifiIcon: StateFlow<WifiIcon>,
     isActivityInViewVisible: Flow<Boolean>,
     isActivityOutViewVisible: Flow<Boolean>,
     isActivityContainerVisible: Flow<Boolean>,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 0782bbb..ec7ba65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -17,16 +17,18 @@
 package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
 
 import android.content.Context
-import androidx.annotation.DrawableRes
 import androidx.annotation.StringRes
 import androidx.annotation.VisibleForTesting
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.R
 import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -71,50 +73,39 @@
     connectivityConstants: ConnectivityConstants,
     private val context: Context,
     logger: ConnectivityPipelineLogger,
+    @WifiTableLog wifiTableLogBuffer: TableLogBuffer,
     interactor: WifiInteractor,
     @Application private val scope: CoroutineScope,
     statusBarPipelineFlags: StatusBarPipelineFlags,
     wifiConstants: WifiConstants,
 ) {
-    /**
-     * Returns the drawable resource ID to use for the wifi icon based on the given network.
-     * Null if we can't compute the icon.
-     */
-    @DrawableRes
-    private fun WifiNetworkModel.iconResId(): Int? {
+    /** Returns the icon to use based on the given network. */
+    private fun WifiNetworkModel.icon(): WifiIcon {
         return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
-            is WifiNetworkModel.Active ->
-                when {
-                    this.level == null -> null
-                    this.isValidated -> WIFI_FULL_ICONS[this.level]
-                    else -> WIFI_NO_INTERNET_ICONS[this.level]
-                }
-        }
-    }
-
-    /**
-     * Returns the content description for the wifi icon based on the given network.
-     * Null if we can't compute the content description.
-     */
-    private fun WifiNetworkModel.contentDescription(): ContentDescription? {
-        return when (this) {
-            is WifiNetworkModel.CarrierMerged -> null
-            is WifiNetworkModel.Inactive ->
+            is WifiNetworkModel.CarrierMerged -> WifiIcon.Hidden
+            is WifiNetworkModel.Inactive -> WifiIcon.Visible(
+                res = WIFI_NO_NETWORK,
                 ContentDescription.Loaded(
                     "${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
                 )
+            )
             is WifiNetworkModel.Active ->
                 when (this.level) {
-                    null -> null
+                    null -> WifiIcon.Hidden
                     else -> {
                         val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
                         when {
-                            this.isValidated -> ContentDescription.Loaded(levelDesc)
+                            this.isValidated ->
+                                WifiIcon.Visible(
+                                    WIFI_FULL_ICONS[this.level],
+                                    ContentDescription.Loaded(levelDesc)
+                                )
                             else ->
-                                ContentDescription.Loaded(
-                                    "$levelDesc,${context.getString(NO_INTERNET)}"
+                                WifiIcon.Visible(
+                                    WIFI_NO_INTERNET_ICONS[this.level],
+                                    ContentDescription.Loaded(
+                                        "$levelDesc,${context.getString(NO_INTERNET)}"
+                                    )
                                 )
                         }
                     }
@@ -122,8 +113,8 @@
         }
     }
 
-    /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
-    private val wifiIcon: StateFlow<Icon.Resource?> =
+    /** The wifi icon that should be displayed. */
+    private val wifiIcon: StateFlow<WifiIcon> =
         combine(
             interactor.isEnabled,
             interactor.isDefault,
@@ -131,22 +122,29 @@
             interactor.wifiNetwork,
         ) { isEnabled, isDefault, isForceHidden, wifiNetwork ->
             if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
-                return@combine null
+                return@combine WifiIcon.Hidden
             }
 
-            val iconResId = wifiNetwork.iconResId() ?: return@combine null
-            val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+            val icon = wifiNetwork.icon()
 
             return@combine when {
                 isDefault -> icon
                 wifiConstants.alwaysShowIconIfEnabled -> icon
                 !connectivityConstants.hasDataCapabilities -> icon
                 wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
-                else -> null
+                else -> WifiIcon.Hidden
             }
         }
-                .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
-                .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+            .logDiffsForTable(
+                wifiTableLogBuffer,
+                columnPrefix = "",
+                initialValue = WifiIcon.Hidden,
+            )
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = WifiIcon.Hidden
+            )
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
     private val activity: Flow<WifiActivityModel?> =
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 6c66f0b..341eb3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -68,7 +68,6 @@
 
         internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
         const val PREFS_CONTROLS_FILE = "controls_prefs"
-        internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
         private const val SEEDING_MAX = 2
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index a9d05d1..ea40208 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -105,8 +105,9 @@
      *
      * This method handles inflating and attaching the view, then delegates to [updateView] to
      * display the correct information in the view.
+     * @param onViewTimeout a runnable that runs after the view timeout.
      */
-    fun displayView(newInfo: T) {
+    fun displayView(newInfo: T, onViewTimeout: Runnable? = null) {
         val currentDisplayInfo = displayInfo
 
         // Update our list of active devices by removing it if necessary, then adding back at the
@@ -173,7 +174,10 @@
             cancelViewTimeout?.run()
         }
         cancelViewTimeout = mainExecutor.executeDelayed(
-            { removeView(id, REMOVAL_REASON_TIMEOUT) },
+            {
+                removeView(id, REMOVAL_REASON_TIMEOUT)
+                onViewTimeout?.run()
+            },
             timeout.toLong()
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index fb17b69..4d91e35 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -34,8 +34,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Text.Companion.loadText
-import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.common.ui.binder.TextViewBinder
+import com.android.systemui.common.ui.binder.TintedIconViewBinder
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.FalsingManager
@@ -121,7 +121,7 @@
 
         // ---- Start icon ----
         val iconView = currentView.requireViewById<CachingIconView>(R.id.start_icon)
-        IconViewBinder.bind(newInfo.startIcon, iconView)
+        TintedIconViewBinder.bind(newInfo.startIcon, iconView)
 
         // ---- Text ----
         val textView = currentView.requireViewById<TextView>(R.id.text)
@@ -156,11 +156,14 @@
         }
 
         // ---- Overall accessibility ----
-        currentView.requireViewById<ViewGroup>(
-                R.id.chipbar_inner
-        ).contentDescription =
-            "${newInfo.startIcon.contentDescription.loadContentDescription(context)} " +
-                "${newInfo.text.loadText(context)}"
+        val iconDesc = newInfo.startIcon.icon.contentDescription
+        val loadedIconDesc = if (iconDesc != null) {
+            "${iconDesc.loadContentDescription(context)} "
+        } else {
+            ""
+        }
+        currentView.requireViewById<ViewGroup>(R.id.chipbar_inner).contentDescription =
+            "$loadedIconDesc${newInfo.text.loadText(context)}"
 
         // ---- Haptics ----
         newInfo.vibrationEffect?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index b92e0ec..a3eef80 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -18,8 +18,9 @@
 
 import android.os.VibrationEffect
 import android.view.View
-import com.android.systemui.common.shared.model.Icon
+import androidx.annotation.AttrRes
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 
 /**
@@ -33,7 +34,7 @@
  * @property vibrationEffect an optional vibration effect when the chipbar is displayed
  */
 data class ChipbarInfo(
-    val startIcon: Icon,
+    val startIcon: TintedIcon,
     val text: Text,
     val endItem: ChipbarEndItem?,
     val vibrationEffect: VibrationEffect? = null,
@@ -41,7 +42,11 @@
     override val wakeReason: String,
     override val timeoutMs: Int,
     override val id: String,
-) : TemporaryViewInfo()
+) : TemporaryViewInfo() {
+    companion object {
+        @AttrRes const val DEFAULT_ICON_TINT_ATTR = android.R.attr.textColorPrimary
+    }
+}
 
 /** The possible items to display at the end of the chipbar. */
 sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 162c915..b2ec27c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -40,6 +40,8 @@
 import com.android.systemui.statusbar.LightRevealEffect
 import com.android.systemui.statusbar.LightRevealScrim
 import com.android.systemui.statusbar.LinearLightRevealEffect
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.FOLD
+import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation.AddOverlayReason.UNFOLD
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.util.ScaleAwareTransitionProgressProvider.Companion.areAnimationsEnabled
@@ -125,7 +127,7 @@
         try {
             // Add the view only if we are unfolding and this is the first screen on
             if (!isFolded && !isUnfoldHandled && contentResolver.areAnimationsEnabled()) {
-                executeInBackground { addView(onOverlayReady) }
+                executeInBackground { addOverlay(onOverlayReady, reason = UNFOLD) }
                 isUnfoldHandled = true
             } else {
                 // No unfold transition, immediately report that overlay is ready
@@ -137,7 +139,7 @@
         }
     }
 
-    private fun addView(onOverlayReady: Runnable? = null) {
+    private fun addOverlay(onOverlayReady: Runnable? = null, reason: AddOverlayReason) {
         if (!::wwm.isInitialized) {
             // Surface overlay is not created yet on the first SysUI launch
             onOverlayReady?.run()
@@ -152,7 +154,10 @@
             LightRevealScrim(context, null).apply {
                 revealEffect = createLightRevealEffect()
                 isScrimOpaqueChangedListener = Consumer {}
-                revealAmount = 0f
+                revealAmount = when (reason) {
+                    FOLD -> TRANSPARENT
+                    UNFOLD -> BLACK
+                }
             }
 
         val params = getLayoutParams()
@@ -228,7 +233,7 @@
     }
 
     private fun getUnfoldedDisplayInfo(): DisplayInfo =
-        displayManager.displays
+        displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED)
             .asSequence()
             .map { DisplayInfo().apply { it.getDisplayInfo(this) } }
             .filter { it.type == Display.TYPE_INTERNAL }
@@ -247,7 +252,7 @@
         override fun onTransitionStarted() {
             // Add view for folding case (when unfolding the view is added earlier)
             if (scrimView == null) {
-                executeInBackground { addView() }
+                executeInBackground { addOverlay(reason = FOLD) }
             }
             // Disable input dispatching during transition.
             InputManager.getInstance().cancelCurrentTouch()
@@ -294,11 +299,17 @@
             }
         )
 
+    private enum class AddOverlayReason { FOLD, UNFOLD }
+
     private companion object {
-        private const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
+        const val ROTATION_ANIMATION_OVERLAY_Z_INDEX = Integer.MAX_VALUE
 
         // Put the unfold overlay below the rotation animation screenshot to hide the moment
         // when it is rotated but the rotation of the other windows hasn't happen yet
-        private const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+        const val UNFOLD_OVERLAY_LAYER_Z_INDEX = ROTATION_ANIMATION_OVERLAY_Z_INDEX - 1
+
+        // constants for revealAmount.
+        const val TRANSPARENT = 1f
+        const val BLACK = 0f
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 512fadf..74295f0 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -31,6 +31,7 @@
 import android.os.UserManager
 import android.provider.Settings
 import android.util.Log
+import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.UserIcons
 import com.android.systemui.R
 import com.android.systemui.SystemUISecondaryUserService
@@ -54,6 +55,7 @@
 import com.android.systemui.user.legacyhelper.data.LegacyUserDataHelper
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.user.utils.MultiUserActionsEvent
 import com.android.systemui.util.kotlin.pairwise
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -93,6 +95,7 @@
     private val activityManager: ActivityManager,
     private val refreshUsersScheduler: RefreshUsersScheduler,
     private val guestUserInteractor: GuestUserInteractor,
+    private val uiEventLogger: UiEventLogger,
 ) {
     /**
      * Defines interface for classes that can be notified when the state of users on the device is
@@ -115,9 +118,7 @@
     private val callbackMutex = Mutex()
     private val callbacks = mutableSetOf<UserCallback>()
     private val userInfos: Flow<List<UserInfo>> =
-        repository.userInfos.map { userInfos ->
-            userInfos.filter { it.isFull }
-        }
+        repository.userInfos.map { userInfos -> userInfos.filter { it.isFull } }
 
     /** List of current on-device users to select from. */
     val users: Flow<List<UserModel>>
@@ -427,14 +428,17 @@
         dialogShower: UserSwitchDialogController.DialogShower? = null,
     ) {
         when (action) {
-            UserActionModel.ENTER_GUEST_MODE ->
+            UserActionModel.ENTER_GUEST_MODE -> {
+                uiEventLogger.log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
                 guestUserInteractor.createAndSwitchTo(
                     this::showDialog,
                     this::dismissDialog,
                 ) { userId ->
                     selectUser(userId, dialogShower)
                 }
+            }
             UserActionModel.ADD_USER -> {
+                uiEventLogger.log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
                 val currentUser = repository.getSelectedUserInfo()
                 showDialog(
                     ShowDialogRequestModel.ShowAddUserDialog(
@@ -445,7 +449,8 @@
                     )
                 )
             }
-            UserActionModel.ADD_SUPERVISED_USER ->
+            UserActionModel.ADD_SUPERVISED_USER -> {
+                uiEventLogger.log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER)
                 activityStarter.startActivity(
                     Intent()
                         .setAction(UserManager.ACTION_CREATE_SUPERVISED_USER)
@@ -453,6 +458,7 @@
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
                     /* dismissShade= */ true,
                 )
+            }
             UserActionModel.NAVIGATE_TO_USER_MANAGEMENT ->
                 activityStarter.startActivity(
                     Intent(Settings.ACTION_USER_SETTINGS),
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
new file mode 100644
index 0000000..bfedac9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.user.utils
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class MultiUserActionsEvent(val value: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "Add User tap from User Switcher.") CREATE_USER_FROM_USER_SWITCHER(1257),
+    @UiEvent(doc = "Add Guest tap from User Switcher.") CREATE_GUEST_FROM_USER_SWITCHER(1258),
+    @UiEvent(doc = "Add Restricted User tap from User Switcher.")
+    CREATE_RESTRICTED_USER_FROM_USER_SWITCHER(1259);
+
+    override fun getId(): Int {
+        return value
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
index 0f3eddf..bff6132d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/util/DeviceConfigProxy.java
@@ -50,13 +50,6 @@
     }
 
     /**
-     * Wrapped version of {@link DeviceConfig#enforceReadPermission}.
-     */
-    public void enforceReadPermission(String namespace) {
-        DeviceConfig.enforceReadPermission(namespace);
-    }
-
-    /**
      * Wrapped version of {@link DeviceConfig#getBoolean}.
      */
     public boolean getBoolean(
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 5df4a5b..aa48900 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -46,7 +46,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.wallpapers.canvas.WallpaperLocalColorExtractor;
@@ -58,7 +57,6 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
@@ -89,17 +87,12 @@
     private final DelayableExecutor mBackgroundExecutor;
     private static final int DELAY_UNLOAD_BITMAP = 2000;
 
-    @Main
-    private final Executor mMainExecutor;
-
     @Inject
     public ImageWallpaper(FeatureFlags featureFlags,
-            @Background DelayableExecutor backgroundExecutor,
-            @Main Executor mainExecutor) {
+            @Background DelayableExecutor backgroundExecutor) {
         super();
         mFeatureFlags = featureFlags;
         mBackgroundExecutor = backgroundExecutor;
-        mMainExecutor = mainExecutor;
     }
 
     @Override
@@ -672,13 +665,9 @@
                 loadWallpaperAndDrawFrameInternal();
             } else {
                 mBitmapUsages++;
-
-                // drawing is done on the main thread
-                mMainExecutor.execute(() -> {
-                    drawFrameOnCanvas(mBitmap);
-                    reportEngineShown(false);
-                    unloadBitmapIfNotUsed();
-                });
+                drawFrameOnCanvas(mBitmap);
+                reportEngineShown(false);
+                unloadBitmapIfNotUsedInternal();
             }
         }
 
@@ -716,11 +705,15 @@
 
         private void unloadBitmapIfNotUsedSynchronized() {
             synchronized (mLock) {
-                mBitmapUsages -= 1;
-                if (mBitmapUsages <= 0) {
-                    mBitmapUsages = 0;
-                    unloadBitmapInternal();
-                }
+                unloadBitmapIfNotUsedInternal();
+            }
+        }
+
+        private void unloadBitmapIfNotUsedInternal() {
+            mBitmapUsages -= 1;
+            if (mBitmapUsages <= 0) {
+                mBitmapUsages = 0;
+                unloadBitmapInternal();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index 02738d5..8ef98f0 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -253,6 +253,12 @@
                 splitScreen.onFinishedWakingUp();
             }
         });
+        mCommandQueue.addCallback(new CommandQueue.Callbacks() {
+            @Override
+            public void goToFullscreenFromSplit() {
+                splitScreen.goToFullscreenFromSplit();
+            }
+        });
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index 8bbaf3d..4903d31 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
@@ -87,6 +88,7 @@
         when(mAbsKeyInputView.isAttachedToWindow()).thenReturn(true);
         when(mAbsKeyInputView.requireViewById(R.id.bouncer_message_area))
                 .thenReturn(mKeyguardMessageArea);
+        when(mAbsKeyInputView.getResources()).thenReturn(getContext().getResources());
         mKeyguardAbsKeyInputViewController = new KeyguardAbsKeyInputViewController(mAbsKeyInputView,
                 mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
                 mKeyguardMessageAreaControllerFactory, mLatencyTracker, mFalsingCollector,
@@ -125,4 +127,22 @@
         verifyZeroInteractions(mKeyguardSecurityCallback);
         verifyZeroInteractions(mKeyguardMessageAreaController);
     }
+
+    @Test
+    public void onPromptReasonNone_doesNotSetMessage() {
+        mKeyguardAbsKeyInputViewController.showPromptReason(0);
+        verify(mKeyguardMessageAreaController, never()).setMessage(
+                getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+                false);
+    }
+
+    @Test
+    public void onPromptReason_setsMessage() {
+        when(mAbsKeyInputView.getPromptReasonStringRes(1)).thenReturn(
+                R.string.kg_prompt_reason_restart_password);
+        mKeyguardAbsKeyInputViewController.showPromptReason(1);
+        verify(mKeyguardMessageAreaController).setMessage(
+                getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
+                false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index ffd95f4..d912793 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -19,6 +19,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
 import androidx.test.filters.SmallTest
 import com.android.internal.util.LatencyTracker
 import com.android.internal.widget.LockPatternUtils
@@ -29,59 +30,54 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPasswordViewControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var keyguardPasswordView: KeyguardPasswordView
-    @Mock
-    lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
-    @Mock
-    lateinit var securityMode: KeyguardSecurityModel.SecurityMode
-    @Mock
-    lateinit var lockPatternUtils: LockPatternUtils
-    @Mock
-    lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
-    @Mock
-    lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
-    @Mock
-    lateinit var latencyTracker: LatencyTracker
-    @Mock
-    lateinit var inputMethodManager: InputMethodManager
-    @Mock
-    lateinit var emergencyButtonController: EmergencyButtonController
-    @Mock
-    lateinit var mainExecutor: DelayableExecutor
-    @Mock
-    lateinit var falsingCollector: FalsingCollector
-    @Mock
-    lateinit var keyguardViewController: KeyguardViewController
-    @Mock
-    private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
-    @Mock
-    private lateinit var mKeyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+  @Mock private lateinit var keyguardPasswordView: KeyguardPasswordView
+  @Mock private lateinit var passwordEntry: EditText
+  @Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+  @Mock lateinit var securityMode: KeyguardSecurityModel.SecurityMode
+  @Mock lateinit var lockPatternUtils: LockPatternUtils
+  @Mock lateinit var keyguardSecurityCallback: KeyguardSecurityCallback
+  @Mock lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory
+  @Mock lateinit var latencyTracker: LatencyTracker
+  @Mock lateinit var inputMethodManager: InputMethodManager
+  @Mock lateinit var emergencyButtonController: EmergencyButtonController
+  @Mock lateinit var mainExecutor: DelayableExecutor
+  @Mock lateinit var falsingCollector: FalsingCollector
+  @Mock lateinit var keyguardViewController: KeyguardViewController
+  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+  @Mock
+  private lateinit var mKeyguardMessageAreaController:
+      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-    private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
+  private lateinit var keyguardPasswordViewController: KeyguardPasswordViewController
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        Mockito.`when`(
-            keyguardPasswordView
-                .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area)
-        ).thenReturn(mKeyguardMessageArea)
-        Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
-            .thenReturn(mKeyguardMessageAreaController)
-        keyguardPasswordViewController = KeyguardPasswordViewController(
+  @Before
+  fun setup() {
+    MockitoAnnotations.initMocks(this)
+    Mockito.`when`(
+            keyguardPasswordView.requireViewById<BouncerKeyguardMessageArea>(
+                R.id.bouncer_message_area))
+        .thenReturn(mKeyguardMessageArea)
+    Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
+        .thenReturn(mKeyguardMessageAreaController)
+    Mockito.`when`(keyguardPasswordView.passwordTextViewId).thenReturn(R.id.passwordEntry)
+    Mockito.`when`(keyguardPasswordView.findViewById<EditText>(R.id.passwordEntry))
+        .thenReturn(passwordEntry)
+    `when`(keyguardPasswordView.resources).thenReturn(context.resources)
+    keyguardPasswordViewController =
+        KeyguardPasswordViewController(
             keyguardPasswordView,
             keyguardUpdateMonitor,
             securityMode,
@@ -94,39 +90,48 @@
             mainExecutor,
             mContext.resources,
             falsingCollector,
-            keyguardViewController
-        )
-    }
+            keyguardViewController)
+  }
 
-    @Test
-    fun testFocusWhenBouncerIsShown() {
-        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
-        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() }
+  @Test
+  fun testFocusWhenBouncerIsShown() {
+    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true)
+    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    keyguardPasswordView.post {
+      verify(keyguardPasswordView).requestFocus()
+      verify(keyguardPasswordView).showKeyboard()
     }
+  }
 
-    @Test
-    fun testDoNotFocusWhenBouncerIsHidden() {
-        Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
-        Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
-        keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
-        verify(keyguardPasswordView, never()).requestFocus()
-    }
+  @Test
+  fun testDoNotFocusWhenBouncerIsHidden() {
+    Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false)
+    Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true)
+    keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
+    verify(keyguardPasswordView, never()).requestFocus()
+  }
 
-    @Test
-    fun startAppearAnimation() {
-        keyguardPasswordViewController.startAppearAnimation()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_password)
+  @Test
+  fun testHideKeyboardWhenOnPause() {
+    keyguardPasswordViewController.onPause()
+    keyguardPasswordView.post {
+      verify(keyguardPasswordView).clearFocus()
+      verify(keyguardPasswordView).hideKeyboard()
     }
+  }
 
-    @Test
-    fun startAppearAnimation_withExistingMessage() {
-        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-        keyguardPasswordViewController.startAppearAnimation()
-        verify(
-            mKeyguardMessageAreaController,
-            never()
-        ).setMessage(R.string.keyguard_enter_your_password)
-    }
+  @Test
+  fun startAppearAnimation() {
+    keyguardPasswordViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController)
+        .setMessage(context.resources.getString(R.string.keyguard_enter_your_password), false)
+  }
+
+  @Test
+  fun startAppearAnimation_withExistingMessage() {
+    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+    keyguardPasswordViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+  }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index b3d1c8f..85dbdb8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -30,97 +30,93 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
-import org.mockito.Mockito.never
 import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper
 class KeyguardPatternViewControllerTest : SysuiTestCase() {
-    @Mock
-    private lateinit var mKeyguardPatternView: KeyguardPatternView
+  @Mock private lateinit var mKeyguardPatternView: KeyguardPatternView
 
-    @Mock
-    private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+  @Mock private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
 
-    @Mock
-    private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+  @Mock private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
 
-    @Mock
-    private lateinit var mLockPatternUtils: LockPatternUtils
+  @Mock private lateinit var mLockPatternUtils: LockPatternUtils
 
-    @Mock
-    private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+  @Mock private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
 
-    @Mock
-    private lateinit var mLatencyTracker: LatencyTracker
-    private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+  @Mock private lateinit var mLatencyTracker: LatencyTracker
+  private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
 
-    @Mock
-    private lateinit var mEmergencyButtonController: EmergencyButtonController
+  @Mock private lateinit var mEmergencyButtonController: EmergencyButtonController
 
-    @Mock
-    private lateinit
-    var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+  @Mock
+  private lateinit var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
 
-    @Mock
-    private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
+  @Mock private lateinit var mKeyguardMessageArea: BouncerKeyguardMessageArea
 
-    @Mock
-    private lateinit var mKeyguardMessageAreaController:
-        KeyguardMessageAreaController<BouncerKeyguardMessageArea>
+  @Mock
+  private lateinit var mKeyguardMessageAreaController:
+      KeyguardMessageAreaController<BouncerKeyguardMessageArea>
 
-    @Mock
-    private lateinit var mLockPatternView: LockPatternView
+  @Mock private lateinit var mLockPatternView: LockPatternView
 
-    @Mock
-    private lateinit var mPostureController: DevicePostureController
+  @Mock private lateinit var mPostureController: DevicePostureController
 
-    private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
+  private lateinit var mKeyguardPatternViewController: KeyguardPatternViewController
 
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
-        `when`(mKeyguardPatternView
-            .requireViewById<BouncerKeyguardMessageArea>(R.id.bouncer_message_area))
-            .thenReturn(mKeyguardMessageArea)
-        `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
-            .thenReturn(mLockPatternView)
-        `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
-            .thenReturn(mKeyguardMessageAreaController)
-        mKeyguardPatternViewController = KeyguardPatternViewController(
+  @Before
+  fun setup() {
+    MockitoAnnotations.initMocks(this)
+    `when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
+    `when`(
+            mKeyguardPatternView.requireViewById<BouncerKeyguardMessageArea>(
+                R.id.bouncer_message_area))
+        .thenReturn(mKeyguardMessageArea)
+    `when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
+        .thenReturn(mLockPatternView)
+    `when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
+        .thenReturn(mKeyguardMessageAreaController)
+    `when`(mKeyguardPatternView.resources).thenReturn(context.resources)
+    mKeyguardPatternViewController =
+        KeyguardPatternViewController(
             mKeyguardPatternView,
-            mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
-            mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
-            mKeyguardMessageAreaControllerFactory, mPostureController
-        )
-    }
+            mKeyguardUpdateMonitor,
+            mSecurityMode,
+            mLockPatternUtils,
+            mKeyguardSecurityCallback,
+            mLatencyTracker,
+            mFalsingCollector,
+            mEmergencyButtonController,
+            mKeyguardMessageAreaControllerFactory,
+            mPostureController)
+  }
 
-    @Test
-    fun onPause_resetsText() {
-        mKeyguardPatternViewController.init()
-        mKeyguardPatternViewController.onPause()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
-    }
+  @Test
+  fun onPause_resetsText() {
+    mKeyguardPatternViewController.init()
+    mKeyguardPatternViewController.onPause()
+    verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
+  }
 
+  @Test
+  fun startAppearAnimation() {
+    mKeyguardPatternViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController)
+        .setMessage(context.resources.getString(R.string.keyguard_enter_your_pattern), false)
+  }
 
-    @Test
-    fun startAppearAnimation() {
-        mKeyguardPatternViewController.startAppearAnimation()
-        verify(mKeyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pattern)
-    }
-
-    @Test
-    fun startAppearAnimation_withExistingMessage() {
-        `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
-        mKeyguardPatternViewController.startAppearAnimation()
-        verify(
-            mKeyguardMessageAreaController,
-            never()
-        ).setMessage(R.string.keyguard_enter_your_password)
-    }
+  @Test
+  fun startAppearAnimation_withExistingMessage() {
+    `when`(mKeyguardMessageAreaController.message).thenReturn("Unlock to continue.")
+    mKeyguardPatternViewController.startAppearAnimation()
+    verify(mKeyguardMessageAreaController, never()).setMessage(anyString(), anyBoolean())
+  }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 8bcfe6f..cdb7bbb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -31,10 +31,13 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito
 import org.mockito.Mockito.any
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -79,6 +82,7 @@
                 keyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea::class.java))
             )
             .thenReturn(keyguardMessageAreaController)
+        `when`(keyguardPinView.resources).thenReturn(context.resources)
         pinViewController =
             KeyguardPinViewController(
                 keyguardPinView,
@@ -98,14 +102,14 @@
     @Test
     fun startAppearAnimation() {
         pinViewController.startAppearAnimation()
-        verify(keyguardMessageAreaController).setMessage(R.string.keyguard_enter_your_pin)
+        verify(keyguardMessageAreaController)
+            .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false)
     }
 
     @Test
     fun startAppearAnimation_withExistingMessage() {
         Mockito.`when`(keyguardMessageAreaController.message).thenReturn("Unlock to continue.")
         pinViewController.startAppearAnimation()
-        verify(keyguardMessageAreaController, Mockito.never())
-            .setMessage(R.string.keyguard_enter_your_password)
+        verify(keyguardMessageAreaController, Mockito.never()).setMessage(anyString(), anyBoolean())
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 002da55..bdd29aa 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -31,6 +31,7 @@
 import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
 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;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -855,12 +856,21 @@
     }
 
     @Test
-    public void testFingerprintPowerPressed_restartsFingerprintListeningStateImmediately() {
+    public void testFingerprintPowerPressed_restartsFingerprintListeningStateWithDelay() {
         mKeyguardUpdateMonitor.mFingerprintAuthenticationCallback
                 .onAuthenticationError(FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "");
 
-        verify(mFingerprintManager).authenticate(any(), any(), any(), any(), anyInt(), anyInt(),
-                anyInt());
+        // THEN doesn't authenticate immediately
+        verify(mFingerprintManager, never()).authenticate(any(),
+                any(), any(), any(), anyInt(), anyInt(), anyInt());
+
+        // WHEN all messages (with delays) are processed
+        mTestableLooper.moveTimeForward(HAL_POWER_PRESS_TIMEOUT);
+        mTestableLooper.processAllMessages();
+
+        // THEN fingerprint manager attempts to authenticate again
+        verify(mFingerprintManager).authenticate(any(),
+                any(), any(), any(), anyInt(), anyInt(), anyInt());
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
index b2c5266..0cdd6e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java
@@ -20,8 +20,10 @@
 
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.graphics.PointF;
 import android.testing.AndroidTestingRunner;
@@ -30,6 +32,10 @@
 import android.view.ViewPropertyAnimator;
 import android.view.WindowManager;
 
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FlingAnimation;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.Prefs;
@@ -39,6 +45,9 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+
+import java.util.Optional;
 
 /** Tests for {@link MenuAnimationController}. */
 @RunWith(AndroidTestingRunner.class)
@@ -47,9 +56,10 @@
 public class MenuAnimationControllerTest extends SysuiTestCase {
 
     private boolean mLastIsMoveToTucked;
+    private ArgumentCaptor<DynamicAnimation.OnAnimationEndListener> mEndListenerCaptor;
     private ViewPropertyAnimator mViewPropertyAnimator;
     private MenuView mMenuView;
-    private MenuAnimationController mMenuAnimationController;
+    private TestMenuAnimationController mMenuAnimationController;
 
     @Before
     public void setUp() throws Exception {
@@ -62,15 +72,17 @@
         mViewPropertyAnimator = spy(mMenuView.animate());
         doReturn(mViewPropertyAnimator).when(mMenuView).animate();
 
-        mMenuAnimationController = new MenuAnimationController(mMenuView);
+        mMenuAnimationController = new TestMenuAnimationController(mMenuView);
         mLastIsMoveToTucked = Prefs.getBoolean(mContext,
                 Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
+        mEndListenerCaptor = ArgumentCaptor.forClass(DynamicAnimation.OnAnimationEndListener.class);
     }
 
     @After
     public void tearDown() throws Exception {
         Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
                 mLastIsMoveToTucked);
+        mEndListenerCaptor.getAllValues().clear();
     }
 
     @Test
@@ -122,4 +134,115 @@
 
         assertThat(isMoveToTucked).isFalse();
     }
+
+    @Test
+    public void startTuckedAnimationPreview_hasAnimation() {
+        mMenuView.clearAnimation();
+
+        mMenuAnimationController.startTuckedAnimationPreview();
+
+        assertThat(mMenuView.getAnimation()).isNotNull();
+    }
+
+    @Test
+    public void startSpringAnimationsAndEndOneAnimation_notTriggerEndAction() {
+        final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
+        mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
+
+        setupAndRunSpringAnimations();
+        final Optional<DynamicAnimation> anyAnimation =
+                mMenuAnimationController.mPositionAnimations.values().stream().findAny();
+        anyAnimation.ifPresent(this::skipAnimationToEnd);
+
+        verifyZeroInteractions(onSpringAnimationsEndCallback);
+    }
+
+    @Test
+    public void startAndEndSpringAnimations_triggerEndAction() {
+        final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
+        mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
+
+        setupAndRunSpringAnimations();
+        mMenuAnimationController.mPositionAnimations.values().forEach(this::skipAnimationToEnd);
+
+        verify(onSpringAnimationsEndCallback).run();
+    }
+
+    @Test
+    public void flingThenSpringAnimationsAreEnded_triggerEndAction() {
+        final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
+        mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
+
+        mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+                100, /* velocityY= */ 100);
+        mMenuAnimationController.mPositionAnimations.values()
+                .forEach(animation -> verify((FlingAnimation) animation).addEndListener(
+                        mEndListenerCaptor.capture()));
+        mEndListenerCaptor.getAllValues()
+                .forEach(listener -> listener.onAnimationEnd(mock(DynamicAnimation.class),
+                        /* canceled */ false, /* endValue */ 0, /* endVelocity */ 0));
+        mMenuAnimationController.mPositionAnimations.values().forEach(this::skipAnimationToEnd);
+
+        verify(onSpringAnimationsEndCallback).run();
+    }
+
+    @Test
+    public void existFlingIsRunningAndTheOtherAreEnd_notTriggerEndAction() {
+        final Runnable onSpringAnimationsEndCallback = mock(Runnable.class);
+        mMenuAnimationController.setSpringAnimationsEndAction(onSpringAnimationsEndCallback);
+
+        mMenuAnimationController.flingMenuThenSpringToEdge(/* x= */ 0, /* velocityX= */
+                200, /* velocityY= */ 200);
+        mMenuAnimationController.mPositionAnimations.values()
+                .forEach(animation -> verify((FlingAnimation) animation).addEndListener(
+                        mEndListenerCaptor.capture()));
+        final Optional<DynamicAnimation.OnAnimationEndListener> anyAnimation =
+                mEndListenerCaptor.getAllValues().stream().findAny();
+        anyAnimation.ifPresent(
+                listener -> listener.onAnimationEnd(mock(DynamicAnimation.class), /* canceled */
+                        false, /* endValue */ 0, /* endVelocity */ 0));
+        mMenuAnimationController.mPositionAnimations.values()
+                .stream()
+                .filter(animation -> animation instanceof SpringAnimation)
+                .forEach(this::skipAnimationToEnd);
+
+        verifyZeroInteractions(onSpringAnimationsEndCallback);
+    }
+
+    private void setupAndRunSpringAnimations() {
+        final float stiffness = 700f;
+        final float dampingRatio = 0.85f;
+        final float velocity = 100f;
+        final float finalPosition = 300f;
+
+        mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_X, new SpringForce()
+                .setStiffness(stiffness)
+                .setDampingRatio(dampingRatio), velocity, finalPosition);
+        mMenuAnimationController.springMenuWith(DynamicAnimation.TRANSLATION_Y, new SpringForce()
+                .setStiffness(stiffness)
+                .setDampingRatio(dampingRatio), velocity, finalPosition);
+    }
+
+    private void skipAnimationToEnd(DynamicAnimation animation) {
+        final SpringAnimation springAnimation = ((SpringAnimation) animation);
+        // The doAnimationFrame function is used for skipping animation to the end.
+        springAnimation.doAnimationFrame(100);
+        springAnimation.skipToEnd();
+        springAnimation.doAnimationFrame(200);
+    }
+
+    /**
+     * Wrapper class for testing.
+     */
+    private static class TestMenuAnimationController extends MenuAnimationController {
+        TestMenuAnimationController(MenuView menuView) {
+            super(menuView);
+        }
+
+        @Override
+        FlingAnimation createFlingAnimation(MenuView menuView,
+                MenuPositionProperty menuPositionProperty) {
+            return spy(super.createFlingAnimation(menuView, menuPositionProperty));
+        }
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
new file mode 100644
index 0000000..42b610a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuEduTooltipViewTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.accessibility.floatingmenu;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/** Tests for {@link MenuEduTooltipView}. */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class MenuEduTooltipViewTest extends SysuiTestCase {
+    private MenuViewAppearance mMenuViewAppearance;
+    private MenuEduTooltipView mMenuEduTooltipView;
+
+    @Before
+    public void setUp() throws Exception {
+        final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
+        mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
+        mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
+    }
+
+    @Test
+    public void show_matchMessageText() {
+        final CharSequence text = "Message";
+
+        mMenuEduTooltipView.show(text);
+
+        final TextView messageView = mMenuEduTooltipView.findViewById(R.id.text);
+        assertThat(messageView.getText().toString().contentEquals(text)).isTrue();
+    }
+
+    @Test
+    public void show_menuOnLeft_onRightOfAnchor() {
+        mMenuViewAppearance.setPercentagePosition(
+                new Position(/* percentageX= */ 0.0f, /* percentageY= */ 0.0f));
+        final CharSequence text = "Message";
+
+        mMenuEduTooltipView.show(text);
+        final int tooltipViewX = (int) (mMenuViewAppearance.getMenuPosition().x
+                + mMenuViewAppearance.getMenuWidth());
+
+        assertThat(mMenuEduTooltipView.getX()).isEqualTo(tooltipViewX);
+    }
+
+    @Test
+    public void show_menuCloseToLeftOfCenter_onLeftOfAnchor() {
+        mMenuViewAppearance.setPercentagePosition(
+                new Position(/* percentageX= */ 0.4f, /* percentageY= */ 0.0f));
+        final CharSequence text = "Message";
+
+        mMenuEduTooltipView.show(text);
+        final int tooltipViewX = (int) (mMenuViewAppearance.getMenuPosition().x
+                + mMenuViewAppearance.getMenuWidth());
+
+        assertThat(mMenuEduTooltipView.getX()).isEqualTo(tooltipViewX);
+    }
+
+    @Test
+    public void show_menuOnRight_onLeftOfAnchor() {
+        mMenuViewAppearance.setPercentagePosition(
+                new Position(/* percentageX= */ 1.0f, /* percentageY= */ 0.0f));
+        final Resources res = getContext().getResources();
+        final int arrowWidth =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
+        final int arrowMargin =
+                res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_margin);
+        final CharSequence text = "Message";
+
+        mMenuEduTooltipView.show(text);
+        final TextView messageView = mMenuEduTooltipView.findViewById(R.id.text);
+        final int layoutWidth = messageView.getMeasuredWidth() + arrowWidth + arrowMargin;
+
+        assertThat(mMenuEduTooltipView.getX()).isEqualTo(
+                mMenuViewAppearance.getMenuPosition().x - layoutWidth);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
index 4acb394..d29ebb8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandlerTest.java
@@ -181,6 +181,20 @@
         verify(mMenuAnimationController).moveToEdgeAndHide();
     }
 
+    @Test
+    public void receiveActionDownMotionEvent_verifyOnActionDownEnd() {
+        final Runnable onActionDownEnd = mock(Runnable.class);
+        mTouchHandler.setOnActionDownEndListener(onActionDownEnd);
+
+        final MotionEvent stubDownEvent =
+                mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
+                        MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
+                        mStubMenuView.getTranslationY());
+        mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
+
+        verify(onActionDownEnd).run();
+    }
+
     @After
     public void tearDown() {
         mMotionEventHelper.recycleEvents();
diff --git a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
similarity index 94%
rename from packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
index 389eed0..2c680be 100644
--- a/packages/SystemUI/animation/tests/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/InterpolatorsAndroidXTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.animation
 
 import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
 import java.lang.reflect.Modifier
 import junit.framework.Assert.assertEquals
 import org.junit.Test
@@ -25,7 +26,7 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class InterpolatorsAndroidXTest {
+class InterpolatorsAndroidXTest : SysuiTestCase() {
 
     @Test
     fun testInterpolatorsAndInterpolatorsAndroidXPublicMethodsAreEqual() {
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 b267a5c..a94f342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -110,6 +110,8 @@
 import java.util.List;
 import java.util.Optional;
 
+import javax.inject.Provider;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -261,6 +263,7 @@
         initUdfpsController(true /* hasAlternateTouchProvider */);
     }
 
+
     private void initUdfpsController(boolean hasAlternateTouchProvider) {
         initUdfpsController(mOpticalProps, hasAlternateTouchProvider);
     }
@@ -270,8 +273,10 @@
         reset(mFingerprintManager);
         reset(mScreenLifecycle);
 
-        final Optional<AlternateUdfpsTouchProvider> alternateTouchProvider =
-                hasAlternateTouchProvider ? Optional.of(mAlternateTouchProvider) : Optional.empty();
+        final Optional<Provider<AlternateUdfpsTouchProvider>> alternateTouchProvider =
+                hasAlternateTouchProvider ? Optional.of(
+                        (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
+                        : Optional.empty();
 
         mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
                 mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
@@ -1140,7 +1145,7 @@
     }
 
     @Test
-    public void onTouch_withNewTouchDetection_shouldCallOldFingerprintManagerPath()
+    public void onTouch_withNewTouchDetection_shouldCallNewFingerprintManagerPath()
             throws RemoteException {
         final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L,
                 0L);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
index 1d00d6b..16fb50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt
@@ -16,21 +16,19 @@
 
 package com.android.systemui.controls.ui
 
-import android.content.Context
-import android.content.SharedPreferences
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastSender
 import com.android.systemui.controls.ControlsMetricsLogger
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.VibratorHelper
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
-import com.android.systemui.util.settings.SecureSettings
 import com.android.wm.shell.TaskViewFactory
 import org.junit.Before
 import org.junit.Test
@@ -40,8 +38,8 @@
 import org.mockito.Mockito
 import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.spy
@@ -71,9 +69,9 @@
     @Mock
     private lateinit var metricsLogger: ControlsMetricsLogger
     @Mock
-    private lateinit var secureSettings: SecureSettings
+    private lateinit var featureFlags: FeatureFlags
     @Mock
-    private lateinit var userContextProvider: UserContextProvider
+    private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
 
     companion object {
         fun <T> any(): T = Mockito.any<T>()
@@ -103,23 +101,16 @@
                 taskViewFactory,
                 metricsLogger,
                 vibratorHelper,
-                secureSettings,
-                userContextProvider,
-                controlsSettingsRepository
+                controlsSettingsRepository,
+                controlsSettingsDialogManager,
+                featureFlags
         ))
-
-        val userContext = mock(Context::class.java)
-        val pref = mock(SharedPreferences::class.java)
-        `when`(userContextProvider.userContext).thenReturn(userContext)
-        `when`(userContext.getSharedPreferences(
-                DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE))
-                .thenReturn(pref)
-        // Just return 2 so we don't test any Dialog logic which requires a launched activity.
-        `when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
-                .thenReturn(2)
+        coordinator.activityContext = mContext
 
         `when`(cvh.cws.ci.controlId).thenReturn(ID)
         `when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
+        `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
+
         action = spy(coordinator.Action(ID, {}, false, true))
         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
     }
@@ -160,15 +151,31 @@
         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
 
         `when`(keyguardStateController.isShowing()).thenReturn(true)
-        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
 
         coordinator.toggle(cvh, "", true)
 
         verify(coordinator).bouncerOrRun(action)
+        verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
         verify(action).invoke()
     }
 
     @Test
+    fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
+        `when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
+        action = spy(coordinator.Action(ID, {}, false, false))
+        doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
+
+        `when`(keyguardStateController.isShowing()).thenReturn(true)
+        `when`(keyguardStateController.isUnlocked()).thenReturn(false)
+        doNothing().`when`(controlsSettingsDialogManager).maybeShowDialog(any(), any())
+
+        coordinator.toggle(cvh, "", true)
+
+        verify(coordinator).bouncerOrRun(action)
+        verify(controlsSettingsDialogManager, never()).maybeShowDialog(any(), any())
+    }
+
+    @Test
     fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
         action = spy(coordinator.Action(ID, {}, false, true))
         doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
index 48fc46b..9144b13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -22,7 +22,7 @@
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
 import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.controls.FakeControlsSettingsRepository
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
 import com.android.systemui.controls.management.ControlsListingController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
new file mode 100644
index 0000000..0c9986d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt
@@ -0,0 +1,328 @@
+/*
+ * 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.controls.settings
+
+import android.content.DialogInterface
+import android.content.SharedPreferences
+import android.database.ContentObserver
+import android.provider.Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+import android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.TestableAlertDialog
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsSettingsDialogManagerImplTest : SysuiTestCase() {
+
+    companion object {
+        private const val SETTING_SHOW = LOCKSCREEN_SHOW_CONTROLS
+        private const val SETTING_ACTION = LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
+        private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
+    }
+
+    @Mock private lateinit var userFileManager: UserFileManager
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var completedRunnable: () -> Unit
+
+    private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
+    private lateinit var sharedPreferences: FakeSharedPreferences
+    private lateinit var secureSettings: FakeSettings
+
+    private lateinit var underTest: ControlsSettingsDialogManagerImpl
+
+    private var dialog: TestableAlertDialog? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        controlsSettingsRepository = FakeControlsSettingsRepository()
+        sharedPreferences = FakeSharedPreferences()
+        secureSettings = FakeSettings()
+
+        `when`(userTracker.userId).thenReturn(0)
+        secureSettings.userId = userTracker.userId
+        `when`(
+                userFileManager.getSharedPreferences(
+                    eq(DeviceControlsControllerImpl.PREFS_CONTROLS_FILE),
+                    anyInt(),
+                    anyInt()
+                )
+            )
+            .thenReturn(sharedPreferences)
+
+        `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
+            .thenAnswer { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
+
+        attachRepositoryToSettings()
+        underTest =
+            ControlsSettingsDialogManagerImpl(
+                secureSettings,
+                userFileManager,
+                controlsSettingsRepository,
+                userTracker,
+                activityStarter
+            ) { context, _ -> TestableAlertDialog(context).also { dialog = it } }
+    }
+
+    @After
+    fun tearDown() {
+        underTest.closeDialog()
+    }
+
+    @Test
+    fun dialogNotShownIfPrefsAtMaximum() {
+        sharedPreferences.putAttempts(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+
+        assertThat(dialog?.isShowing ?: false).isFalse()
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogNotShownIfSettingsAreTrue() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, true)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+
+        assertThat(dialog?.isShowing ?: false).isFalse()
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogShownIfAllowTrivialControlsFalse() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+
+        assertThat(dialog?.isShowing ?: false).isTrue()
+    }
+
+    @Test
+    fun dialogDispossedAfterClosing() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        underTest.closeDialog()
+
+        assertThat(dialog?.isShowing ?: false).isFalse()
+    }
+
+    @Test
+    fun dialogNeutralButtonDoesntChangeSetting() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+        assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+    }
+
+    @Test
+    fun dialogNeutralButtonPutsMaxAttempts() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+        assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+            .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+    }
+
+    @Test
+    fun dialogNeutralButtonCallsOnComplete() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_NEUTRAL)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogPositiveButtonChangesSetting() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue()
+    }
+
+    @Test
+    fun dialogPositiveButtonPutsMaxAttempts() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+            .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
+    }
+
+    @Test
+    fun dialogPositiveButtonCallsOnComplete() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun dialogCancelDoesntChangeSetting() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        dialog?.cancel()
+
+        assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
+    }
+
+    @Test
+    fun dialogCancelPutsOneExtraAttempt() {
+        val attempts = 0
+        sharedPreferences.putAttempts(attempts)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        dialog?.cancel()
+
+        assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
+            .isEqualTo(attempts + 1)
+    }
+
+    @Test
+    fun dialogCancelCallsOnComplete() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        dialog?.cancel()
+
+        verify(completedRunnable).invoke()
+    }
+
+    @Test
+    fun closeDialogDoesNotCallOnComplete() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, true)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        underTest.closeDialog()
+
+        verify(completedRunnable, never()).invoke()
+    }
+
+    @Test
+    fun dialogPositiveWithBothSettingsFalseTogglesBothSettings() {
+        sharedPreferences.putAttempts(0)
+        secureSettings.putBool(SETTING_SHOW, false)
+        secureSettings.putBool(SETTING_ACTION, false)
+
+        underTest.maybeShowDialog(context, completedRunnable)
+        clickButton(DialogInterface.BUTTON_POSITIVE)
+
+        assertThat(secureSettings.getBool(SETTING_SHOW)).isTrue()
+        assertThat(secureSettings.getBool(SETTING_ACTION)).isTrue()
+    }
+
+    private fun clickButton(which: Int) {
+        dialog?.clickButton(which)
+    }
+
+    private fun attachRepositoryToSettings() {
+        secureSettings.registerContentObserver(
+            SETTING_SHOW,
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    controlsSettingsRepository.setCanShowControlsInLockscreen(
+                        secureSettings.getBool(SETTING_SHOW, false)
+                    )
+                }
+            }
+        )
+
+        secureSettings.registerContentObserver(
+            SETTING_ACTION,
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(
+                        secureSettings.getBool(SETTING_ACTION, false)
+                    )
+                }
+            }
+        )
+    }
+
+    private fun SharedPreferences.putAttempts(value: Int) {
+        edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, value).commit()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
index 4b88b44..b904ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ControlsSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsRepositoryImplTest.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
 
 import android.content.pm.UserInfo
 import android.provider.Settings
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
rename to packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
index 8a1bed2..b6628db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/FakeControlsSettingsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/FakeControlsSettingsRepository.kt
@@ -15,7 +15,7 @@
  *
  */
 
-package com.android.systemui.controls
+package com.android.systemui.controls.settings
 
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index e679b13..779788a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -16,18 +16,29 @@
 
 package com.android.systemui.controls.ui
 
+import android.app.PendingIntent
 import android.content.ComponentName
 import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.ServiceInfo
+import android.os.UserHandle
+import android.service.controls.ControlsProviderService
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.CustomIconCache
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserFileManager
@@ -38,19 +49,26 @@
 import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wm.shell.TaskView
 import com.android.wm.shell.TaskViewFactory
 import com.google.common.truth.Truth.assertThat
 import dagger.Lazy
 import java.util.Optional
+import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.anyString
-import org.mockito.Mockito.mock
+import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
@@ -70,9 +88,9 @@
     @Mock lateinit var userFileManager: UserFileManager
     @Mock lateinit var userTracker: UserTracker
     @Mock lateinit var taskViewFactory: TaskViewFactory
-    @Mock lateinit var activityContext: Context
     @Mock lateinit var dumpManager: DumpManager
     val sharedPreferences = FakeSharedPreferences()
+    lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
 
     var uiExecutor = FakeExecutor(FakeSystemClock())
     var bgExecutor = FakeExecutor(FakeSystemClock())
@@ -83,6 +101,17 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
+        controlsSettingsRepository = FakeControlsSettingsRepository()
+
+        // This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
+        // need to clone it once so we don't modify the original one.
+        mContext.addMockSystemService(
+            Context.LAYOUT_INFLATER_SERVICE,
+            mContext.baseContext
+                .getSystemService(LayoutInflater::class.java)!!
+                .cloneInContext(mContext)
+        )
+
         parent = FrameLayout(mContext)
 
         underTest =
@@ -100,6 +129,7 @@
                 userFileManager,
                 userTracker,
                 Optional.of(taskViewFactory),
+                controlsSettingsRepository,
                 dumpManager
             )
         `when`(
@@ -113,11 +143,12 @@
         `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
             .thenReturn(sharedPreferences)
         `when`(userTracker.userId).thenReturn(0)
+        `when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
     }
 
     @Test
     fun testGetPreferredStructure() {
-        val structureInfo = mock(StructureInfo::class.java)
+        val structureInfo = mock<StructureInfo>()
         underTest.getPreferredSelectedItem(listOf(structureInfo))
         verify(userFileManager)
             .getSharedPreferences(
@@ -189,14 +220,195 @@
     @Test
     fun testPanelDoesNotRefreshControls() {
         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+        verify(controlsController, never()).refreshStatus(any(), any())
+    }
+
+    @Test
+    fun testPanelCallsTaskViewFactoryCreate() {
+        mockLayoutInflater()
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        verify(taskViewFactory).create(eq(context), eq(uiExecutor), any())
+    }
+
+    @Test
+    fun testPanelControllerStartActivityWithCorrectArguments() {
+        mockLayoutInflater()
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntent = verifyPanelCreatedAndStartTaskView()
+
+        with(pendingIntent) {
+            assertThat(isActivity).isTrue()
+            assertThat(intent.component).isEqualTo(serviceInfo.panelActivity)
+            assertThat(
+                    intent.getBooleanExtra(
+                        ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                        false
+                    )
+                )
+                .isTrue()
+        }
+    }
+
+    @Test
+    fun testPendingIntentExtrasAreModified() {
+        mockLayoutInflater()
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(true)
+
+        val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
+        val serviceInfo = setUpPanel(panel)
+
+        underTest.show(parent, {}, context)
+
+        val captor = argumentCaptor<ControlsListingController.ControlsListingCallback>()
+
+        verify(controlsListingController).addCallback(capture(captor))
+
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntent = verifyPanelCreatedAndStartTaskView()
+        assertThat(
+                pendingIntent.intent.getBooleanExtra(
+                    ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                    false
+                )
+            )
+            .isTrue()
+
+        underTest.hide()
+
+        clearInvocations(controlsListingController, taskViewFactory)
+        controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(false)
+        underTest.show(parent, {}, context)
+
+        verify(controlsListingController).addCallback(capture(captor))
+        captor.value.onServicesUpdated(listOf(serviceInfo))
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val newPendingIntent = verifyPanelCreatedAndStartTaskView()
+        assertThat(
+                newPendingIntent.intent.getBooleanExtra(
+                    ControlsProviderService.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
+                    false
+                )
+            )
+            .isFalse()
+    }
+
+    private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
+        val activity = ComponentName("pkg", "activity")
         sharedPreferences
             .edit()
             .putString("controls_component", panel.componentName.flattenToString())
             .putString("controls_structure", panel.appName.toString())
             .putBoolean("controls_is_panel", true)
             .commit()
+        return ControlsServiceInfo(panel.componentName, panel.appName, activity)
+    }
 
-        underTest.show(parent, {}, activityContext)
-        verify(controlsController, never()).refreshStatus(any(), any())
+    private fun verifyPanelCreatedAndStartTaskView(): PendingIntent {
+        val taskViewConsumerCaptor = argumentCaptor<Consumer<TaskView>>()
+        verify(taskViewFactory).create(eq(context), eq(uiExecutor), capture(taskViewConsumerCaptor))
+
+        val taskView: TaskView = mock {
+            `when`(this.post(any())).thenAnswer {
+                uiExecutor.execute(it.arguments[0] as Runnable)
+                true
+            }
+        }
+        // calls PanelTaskViewController#launchTaskView
+        taskViewConsumerCaptor.value.accept(taskView)
+        val listenerCaptor = argumentCaptor<TaskView.Listener>()
+        verify(taskView).setListener(any(), capture(listenerCaptor))
+        listenerCaptor.value.onInitialized()
+        FakeExecutor.exhaustExecutors(uiExecutor, bgExecutor)
+
+        val pendingIntentCaptor = argumentCaptor<PendingIntent>()
+        verify(taskView).startActivity(capture(pendingIntentCaptor), any(), any(), any())
+        return pendingIntentCaptor.value
+    }
+
+    private fun ControlsServiceInfo(
+        componentName: ComponentName,
+        label: CharSequence,
+        panelComponentName: ComponentName? = null
+    ): ControlsServiceInfo {
+        val serviceInfo =
+            ServiceInfo().apply {
+                applicationInfo = ApplicationInfo()
+                packageName = componentName.packageName
+                name = componentName.className
+            }
+        return spy(ControlsServiceInfo(mContext, serviceInfo)).apply {
+            `when`(loadLabel()).thenReturn(label)
+            `when`(loadIcon()).thenReturn(mock())
+            `when`(panelActivity).thenReturn(panelComponentName)
+        }
+    }
+
+    private fun mockLayoutInflater() {
+        LayoutInflater.from(context)
+            .setPrivateFactory(
+                object : LayoutInflater.Factory2 {
+                    override fun onCreateView(
+                        view: View?,
+                        name: String,
+                        context: Context,
+                        attrs: AttributeSet
+                    ): View? {
+                        return onCreateView(name, context, attrs)
+                    }
+
+                    override fun onCreateView(
+                        name: String,
+                        context: Context,
+                        attrs: AttributeSet
+                    ): View? {
+                        if (FrameLayout::class.java.simpleName.equals(name)) {
+                            val mock: FrameLayout = mock {
+                                `when`(this.context).thenReturn(context)
+                                `when`(this.id).thenReturn(R.id.controls_panel)
+                                `when`(this.requireViewById<View>(any())).thenCallRealMethod()
+                                `when`(this.findViewById<View>(R.id.controls_panel))
+                                    .thenReturn(this)
+                                `when`(this.post(any())).thenAnswer {
+                                    uiExecutor.execute(it.arguments[0] as Runnable)
+                                    true
+                                }
+                            }
+                            return mock
+                        } else {
+                            return null
+                        }
+                    }
+                }
+            )
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
index 1e7b1f2..ed16721 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
@@ -48,22 +48,22 @@
     @Test
     fun testRestart_ImmediateWhenAsleep() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        restarter.restart()
-        verify(systemExitRestarter).restart()
+        restarter.restartSystemUI()
+        verify(systemExitRestarter).restartSystemUI()
     }
 
     @Test
     fun testRestart_WaitsForSceenOff() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
 
-        restarter.restart()
-        verify(systemExitRestarter, never()).restart()
+        restarter.restartSystemUI()
+        verify(systemExitRestarter, never()).restartSystemUI()
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
 
         captor.value.onFinishedGoingToSleep()
 
-        verify(systemExitRestarter).restart()
+        verify(systemExitRestarter).restartSystemUI()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
index 68ca48d..7d807e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
@@ -63,7 +63,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
+        restarter.restartSystemUI()
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -72,11 +72,11 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
-        restarter.restart()
-        verify(systemExitRestarter, never()).restart()
+        restarter.restartSystemUI()
+        verify(systemExitRestarter, never()).restartSystemUI()
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(systemExitRestarter).restart()
+        verify(systemExitRestarter).restartSystemUI()
     }
 
     @Test
@@ -85,7 +85,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
+        restarter.restartSystemUI()
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -95,7 +95,7 @@
         whenever(batteryController.isPluggedIn).thenReturn(false)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
+        restarter.restartSystemUI()
         assertThat(executor.numPending()).isEqualTo(0)
     }
 
@@ -105,8 +105,8 @@
         whenever(batteryController.isPluggedIn).thenReturn(true)
 
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
-        restarter.restart()
+        restarter.restartSystemUI()
+        restarter.restartSystemUI()
         assertThat(executor.numPending()).isEqualTo(1)
     }
 
@@ -115,7 +115,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
         whenever(batteryController.isPluggedIn).thenReturn(true)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
+        restarter.restartSystemUI()
 
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
@@ -131,7 +131,7 @@
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
         whenever(batteryController.isPluggedIn).thenReturn(false)
         assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restart()
+        restarter.restartSystemUI()
 
         val captor =
             ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index dc5522e..aa54a1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -23,8 +23,10 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 
+import static org.junit.Assert.assertNotEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -171,6 +173,34 @@
     }
 
     @Test
+    public void testKeyguardSessionOnDeviceStartsSleepingTwiceInARow_startsNewKeyguardSession()
+            throws RemoteException {
+        // GIVEN session tracker started w/o any sessions
+        mSessionTracker.start();
+        captureKeyguardUpdateMonitorCallback();
+
+        // WHEN device starts going to sleep
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN the keyguard session has a session id
+        final InstanceId firstSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+        assertNotNull(firstSessionId);
+
+        // WHEN device starts going to sleep a second time
+        mKeyguardUpdateMonitorCallback.onStartedGoingToSleep(0);
+
+        // THEN there's a new keyguard session with a unique session id
+        final InstanceId secondSessionId = mSessionTracker.getSessionId(SESSION_KEYGUARD);
+        assertNotNull(secondSessionId);
+        assertNotEquals(firstSessionId, secondSessionId);
+
+        // THEN session start event gets sent to status bar service twice (once per going to
+        // sleep signal)
+        verify(mStatusBarService, times(2)).onSessionStarted(
+                eq(SESSION_KEYGUARD), any(InstanceId.class));
+    }
+
+    @Test
     public void testKeyguardSessionOnKeyguardShowingChange() throws RemoteException {
         // GIVEN session tracker started w/o any sessions
         mSessionTracker.start();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 761773b..fdef344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -702,7 +702,7 @@
     }
 
     @Test
-    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToGone() {
+    fun bind_seekBarDisabled_noActions_seekBarVisibilityIsSetToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -711,7 +711,7 @@
 
         player.bindPlayer(state, PACKAGE)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
@@ -741,7 +741,7 @@
     }
 
     @Test
-    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToGone() {
+    fun seekBarChangesToDisabledAfterBind_noActions_seekBarChangesToInvisible() {
         useRealConstraintSets()
 
         val state = mediaData.copy(semanticActions = MediaButton())
@@ -752,7 +752,7 @@
 
         getEnabledChangeListener().onEnabledChanged(enabled = false)
 
-        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.GONE)
+        assertThat(expandedSet.getVisibility(seekBar.id)).isEqualTo(ConstraintSet.INVISIBLE)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 5f64336..5c0f0fe 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -38,6 +38,8 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -73,11 +75,6 @@
 
     @Before
     public void setUp() {
-        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
-        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
-                .onCreateViewHolder(new LinearLayout(mContext), 0);
-        mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
-
         when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
         when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
         when(mMediaOutputController.isAnyDeviceTransferring()).thenReturn(false);
@@ -85,6 +82,7 @@
         when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
         when(mMediaOutputController.getCurrentConnectedMediaDevice()).thenReturn(mMediaDevice1);
         when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(true);
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(false);
         when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
         when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
         when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
@@ -96,6 +94,11 @@
                 LocalMediaManager.MediaDeviceState.STATE_DISCONNECTED);
         mMediaDevices.add(mMediaDevice1);
         mMediaDevices.add(mMediaDevice2);
+
+        mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
     }
 
     @Test
@@ -169,6 +172,63 @@
     }
 
     @Test
+    public void advanced_onBindViewHolder_bindNonRemoteConnectedDevice_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.isActiveRemoteDevice(mMediaDevice1)).thenReturn(false);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void advanced_onBindViewHolder_bindConnectedRemoteDevice_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+                ImmutableList.of(mMediaDevice2));
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void advanced_onBindViewHolder_bindSingleConnectedRemoteDevice_verifyView() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+                ImmutableList.of());
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mTitleText.getText().toString()).isEqualTo(TEST_DEVICE_NAME_1);
+        assertThat(mViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mTwoLineLayout.getVisibility()).isEqualTo(View.GONE);
+        assertThat(mViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
+        assertThat(mViewHolder.mEndTouchArea.getVisibility()).isEqualTo(View.GONE);
+    }
+
+    @Test
     public void onBindViewHolder_bindConnectedDeviceWithMutingExpectedDeviceExist_verifyView() {
         when(mMediaOutputController.hasMutingExpectedDevice()).thenReturn(true);
         when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(false);
@@ -352,6 +412,38 @@
     }
 
     @Test
+    public void advanced_onGroupActionTriggered_clicksEndAreaOfSelectableDevice_triggerGrouping() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        List<MediaDevice> selectableDevices = new ArrayList<>();
+        selectableDevices.add(mMediaDevice2);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(selectableDevices);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 1);
+
+        mViewHolder.mEndTouchArea.performClick();
+
+        verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
+    }
+
+    @Test
+    public void advanced_onGroupActionTriggered_clickSelectedRemoteDevice_triggerUngrouping() {
+        when(mMediaOutputController.isAdvancedLayoutSupported()).thenReturn(true);
+        when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(
+                ImmutableList.of(mMediaDevice2));
+        when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
+                ImmutableList.of(mMediaDevice1));
+        when(mMediaOutputController.isCurrentConnectedDeviceRemote()).thenReturn(true);
+        mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
+                .onCreateViewHolder(new LinearLayout(mContext), 0);
+        mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
+
+        mViewHolder.mEndTouchArea.performClick();
+
+        verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
+    }
+
+    @Test
     public void onItemClick_onGroupActionTriggered_verifySeekbarDisabled() {
         when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
         List<MediaDevice> selectableDevices = new ArrayList<>();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9be201e..094d69a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -51,6 +51,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -89,6 +90,7 @@
     private final AudioManager mAudioManager = mock(AudioManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+    private FeatureFlags mFlags = mock(FeatureFlags.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputBaseDialogImpl mMediaOutputBaseDialogImpl;
@@ -121,7 +123,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
         mMediaOutputBaseDialogImpl = new MediaOutputBaseDialogImpl(mContext, mBroadcastSender,
                 mMediaOutputController);
         mMediaOutputBaseDialogImpl.onCreate(new Bundle());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index cb31fde..c544c0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -62,6 +62,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -110,6 +111,7 @@
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private CommonNotifCollection mNotifCollection = mock(CommonNotifCollection.class);
     private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
+    private FeatureFlags mFlags = mock(FeatureFlags.class);
     private final ActivityLaunchAnimator.Controller mActivityLaunchAnimatorController = mock(
             ActivityLaunchAnimator.Controller.class);
     private final NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
@@ -141,7 +143,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
         mLocalMediaManager = spy(mMediaOutputController.mLocalMediaManager);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         MediaDescription.Builder builder = new MediaDescription.Builder();
@@ -194,7 +196,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
 
         mMediaOutputController.start(mCb);
 
@@ -224,7 +226,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
 
         mMediaOutputController.start(mCb);
 
@@ -318,7 +320,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
         testMediaOutputController.start(mCb);
         reset(mCb);
 
@@ -341,7 +343,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
         testMediaOutputController.start(mCb);
         reset(mCb);
 
@@ -377,7 +379,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
         testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -394,7 +396,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
 
         LocalMediaManager testLocalMediaManager = spy(testMediaOutputController.mLocalMediaManager);
         testMediaOutputController.mLocalMediaManager = testLocalMediaManager;
@@ -671,7 +673,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
 
         assertThat(mMediaOutputController.getNotificationIcon()).isNull();
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index bae3569..31866a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -50,6 +50,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -93,6 +94,7 @@
     private final AudioManager mAudioManager = mock(AudioManager.class);
     private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
     private KeyguardManager mKeyguardManager = mock(KeyguardManager.class);
+    private FeatureFlags mFlags = mock(FeatureFlags.class);
 
     private List<MediaController> mMediaControllers = new ArrayList<>();
     private MediaOutputDialog mMediaOutputDialog;
@@ -115,7 +117,7 @@
                 mMediaSessionManager, mLocalBluetoothManager, mStarter,
                 mNotifCollection, mDialogLaunchAnimator,
                 Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager,
-                mKeyguardManager);
+                mKeyguardManager, mFlags);
         mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
         mMediaOutputDialog = new MediaOutputDialog(mContext, false, mBroadcastSender,
                 mMediaOutputController, mUiEventLogger);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
index 6a4c0f6..cce3e36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttUtilsTest.kt
@@ -65,41 +65,14 @@
     }
 
     @Test
-    fun getIconFromPackageName_nullPackageName_returnsDefault() {
-        val icon = MediaTttUtils.getIconFromPackageName(context, appPackageName = null, logger)
-
-        val expectedDesc =
-            ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
-                .loadContentDescription(context)
-        assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
-    }
-
-    @Test
-    fun getIconFromPackageName_invalidPackageName_returnsDefault() {
-        val icon = MediaTttUtils.getIconFromPackageName(context, "fakePackageName", logger)
-
-        val expectedDesc =
-            ContentDescription.Resource(R.string.media_output_dialog_unknown_launch_app_name)
-                .loadContentDescription(context)
-        assertThat(icon.contentDescription.loadContentDescription(context)).isEqualTo(expectedDesc)
-    }
-
-    @Test
-    fun getIconFromPackageName_validPackageName_returnsAppInfo() {
-        val icon = MediaTttUtils.getIconFromPackageName(context, PACKAGE_NAME, logger)
-
-        assertThat(icon)
-            .isEqualTo(Icon.Loaded(appIconFromPackageName, ContentDescription.Loaded(APP_NAME)))
-    }
-
-    @Test
     fun getIconInfoFromPackageName_nullPackageName_returnsDefault() {
         val iconInfo =
             MediaTttUtils.getIconInfoFromPackageName(context, appPackageName = null, logger)
 
         assertThat(iconInfo.isAppIcon).isFalse()
-        assertThat(iconInfo.contentDescription)
+        assertThat(iconInfo.contentDescription.loadContentDescription(context))
             .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+        assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
     }
 
     @Test
@@ -107,8 +80,9 @@
         val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, "fakePackageName", logger)
 
         assertThat(iconInfo.isAppIcon).isFalse()
-        assertThat(iconInfo.contentDescription)
+        assertThat(iconInfo.contentDescription.loadContentDescription(context))
             .isEqualTo(context.getString(R.string.media_output_dialog_unknown_launch_app_name))
+        assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Resource(R.drawable.ic_cast))
     }
 
     @Test
@@ -116,8 +90,48 @@
         val iconInfo = MediaTttUtils.getIconInfoFromPackageName(context, PACKAGE_NAME, logger)
 
         assertThat(iconInfo.isAppIcon).isTrue()
-        assertThat(iconInfo.drawable).isEqualTo(appIconFromPackageName)
-        assertThat(iconInfo.contentDescription).isEqualTo(APP_NAME)
+        assertThat(iconInfo.icon).isEqualTo(MediaTttIcon.Loaded(appIconFromPackageName))
+        assertThat(iconInfo.contentDescription.loadContentDescription(context)).isEqualTo(APP_NAME)
+    }
+
+    @Test
+    fun iconInfo_toTintedIcon_loaded() {
+        val contentDescription = ContentDescription.Loaded("test")
+        val drawable = context.getDrawable(R.drawable.ic_cake)!!
+        val tintAttr = android.R.attr.textColorTertiary
+
+        val iconInfo =
+            IconInfo(
+                contentDescription,
+                MediaTttIcon.Loaded(drawable),
+                tintAttr,
+                isAppIcon = false,
+            )
+
+        val tinted = iconInfo.toTintedIcon()
+
+        assertThat(tinted.icon).isEqualTo(Icon.Loaded(drawable, contentDescription))
+        assertThat(tinted.tintAttr).isEqualTo(tintAttr)
+    }
+
+    @Test
+    fun iconInfo_toTintedIcon_resource() {
+        val contentDescription = ContentDescription.Loaded("test")
+        val drawableRes = R.drawable.ic_cake
+        val tintAttr = android.R.attr.textColorTertiary
+
+        val iconInfo =
+            IconInfo(
+                contentDescription,
+                MediaTttIcon.Resource(drawableRes),
+                tintAttr,
+                isAppIcon = false
+            )
+
+        val tinted = iconInfo.toTintedIcon()
+
+        assertThat(tinted.icon).isEqualTo(Icon.Resource(drawableRes, contentDescription))
+        assertThat(tinted.tintAttr).isEqualTo(tintAttr)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 4437394..311740e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -265,6 +265,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverSucceeded_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -278,13 +280,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_RECEIVER_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToReceiverSucceeded_nullUndoCallback_noUndo() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -297,6 +301,7 @@
 
     @Test
     fun transferToReceiverSucceeded_withUndoRunnable_undoVisible() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -313,6 +318,7 @@
     @Test
     fun transferToReceiverSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -325,8 +331,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_RECEIVER_CLICKED.id)
         assertThat(undoCallbackCalled).isTrue()
         assertThat(getChipbarView().getChipText())
@@ -335,6 +342,8 @@
 
     @Test
     fun commandQueueCallback_transferToThisDeviceSucceeded_triggersCorrectChip() {
+        displayThisDeviceTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -348,13 +357,15 @@
             .isEqualTo(ChipStateSender.TRANSFER_TO_THIS_DEVICE_SUCCEEDED.getExpectedStateText())
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_SUCCEEDED.id)
         verify(vibratorHelper, never()).vibrate(any<VibrationEffect>())
     }
 
     @Test
     fun transferToThisDeviceSucceeded_nullUndoCallback_noUndo() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -367,6 +378,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_withUndoRunnable_undoVisible() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -383,6 +395,7 @@
     @Test
     fun transferToThisDeviceSucceeded_undoButtonClick_switchesToTransferToThisDeviceTriggered() {
         var undoCallbackCalled = false
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -395,8 +408,9 @@
 
         getChipbarView().getUndoButton().performClick()
 
-        // Event index 1 since initially displaying the succeeded chip would also log an event
-        assertThat(uiEventLoggerFake.eventId(1))
+        // Event index 2 since initially displaying the triggered and succeeded chip would also log
+        // events.
+        assertThat(uiEventLoggerFake.eventId(2))
             .isEqualTo(
                 MediaTttSenderUiEvents.MEDIA_TTT_SENDER_UNDO_TRANSFER_TO_THIS_DEVICE_CLICKED.id
             )
@@ -407,6 +421,8 @@
 
     @Test
     fun commandQueueCallback_transferToReceiverFailed_triggersCorrectChip() {
+        displayReceiverTriggered()
+        reset(vibratorHelper)
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
             routeInfo,
@@ -421,7 +437,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_RECEIVER_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -429,6 +446,12 @@
     @Test
     fun commandQueueCallback_transferToThisDeviceFailed_triggersCorrectChip() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        reset(vibratorHelper)
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
             routeInfo,
             null
@@ -442,7 +465,8 @@
         assertThat(chipbarView.getLoadingIcon().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getUndoButton().visibility).isEqualTo(View.GONE)
         assertThat(chipbarView.getErrorIcon().visibility).isEqualTo(View.VISIBLE)
-        assertThat(uiEventLoggerFake.eventId(0))
+        // Event index 1 since initially displaying the triggered chip would also log an event.
+        assertThat(uiEventLoggerFake.eventId(1))
             .isEqualTo(MediaTttSenderUiEvents.MEDIA_TTT_SENDER_TRANSFER_TO_THIS_DEVICE_FAILED.id)
         verify(vibratorHelper).vibrate(any<VibrationEffect>())
     }
@@ -517,6 +541,166 @@
     }
 
     @Test
+    fun commandQueueCallback_receiverTriggeredThenAlmostStart_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceTriggeredThenAlmostEnd_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_receiverSucceededThenReceiverTriggered_invalidTransitionLogged() {
+        displayReceiverTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_thisDeviceSucceededThenThisDeviceTriggered_invalidTransitionLogged() {
+        displayThisDeviceTriggered()
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostStartThenReceiverSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceSucceeded_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_AlmostStartThenReceiverFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
+    fun commandQueueCallback_almostEndThenThisDeviceFailed_invalidTransitionLogged() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_END_CAST,
+            routeInfo,
+            null
+        )
+        verify(windowManager).addView(any(), any())
+        reset(windowManager)
+
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_FAILED,
+            routeInfo,
+            null
+        )
+
+        verify(logger).logInvalidStateTransitionError(any(), any())
+        verify(windowManager, never()).addView(any(), any())
+    }
+
+    @Test
     fun receivesNewStateFromCommandQueue_isLogged() {
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_ALMOST_CLOSE_TO_START_CAST,
@@ -575,6 +759,7 @@
 
     @Test
     fun transferToReceiverSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -598,6 +783,7 @@
 
     @Test
     fun transferToThisDeviceSucceededThenFarFromReceiver_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -621,6 +807,7 @@
 
     @Test
     fun transferToReceiverSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayReceiverTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_SUCCEEDED,
             routeInfo,
@@ -660,6 +847,7 @@
 
     @Test
     fun transferToThisDeviceSucceeded_thenUndo_thenFar_viewStillDisplayedButDoesTimeOut() {
+        displayThisDeviceTriggered()
         commandQueueCallback.updateMediaTapToTransferSenderDisplay(
             StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_SUCCEEDED,
             routeInfo,
@@ -717,6 +905,26 @@
     private fun ChipStateSender.getExpectedStateText(): String? {
         return this.getChipTextString(context, OTHER_DEVICE_NAME).loadText(context)
     }
+
+    // display receiver triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_RECEIVER_TRIGGERED).
+    private fun displayReceiverTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_RECEIVER_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
+
+    // display this device triggered state helper method to make sure we start from a valid state
+    // transition (FAR_FROM_RECEIVER -> TRANSFER_TO_THIS_DEVICE_TRIGGERED).
+    private fun displayThisDeviceTriggered() {
+        commandQueueCallback.updateMediaTapToTransferSenderDisplay(
+            StatusBarManager.MEDIA_TRANSFER_SENDER_STATE_TRANSFER_TO_THIS_DEVICE_TRIGGERED,
+            routeInfo,
+            null
+        )
+    }
 }
 
 private const val APP_NAME = "Fake app name"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index 9758842..4a9c750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -16,16 +16,21 @@
 package com.android.systemui.notetask
 
 import android.app.KeyguardManager
+import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.UserManager
 import android.test.suitebuilder.annotation.SmallTest
-import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION
+import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
+import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import org.junit.Before
 import org.junit.Test
@@ -48,6 +53,7 @@
     private val notesIntent = Intent(NOTES_ACTION)
 
     @Mock lateinit var context: Context
+    @Mock lateinit var packageManager: PackageManager
     @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver
     @Mock lateinit var bubbles: Bubbles
     @Mock lateinit var optionalBubbles: Optional<Bubbles>
@@ -60,6 +66,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        whenever(context.packageManager).thenReturn(packageManager)
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent)
         whenever(optionalBubbles.orElse(null)).thenReturn(bubbles)
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager)
@@ -78,89 +85,125 @@
         )
     }
 
+    // region showNoteTask
     @Test
-    fun handleSystemKey_keyguardIsLocked_shouldStartActivity() {
+    fun showNoteTask_keyguardIsLocked_shouldStartActivity() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() {
+    fun showNoteTask_keyguardIsUnlocked_shouldStartBubbles() {
         whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(bubbles).showAppBubble(notesIntent)
         verify(context, never()).startActivity(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+    fun showNoteTask_isInMultiWindowMode_shouldStartActivity() {
+        whenever(keyguardManager.isKeyguardLocked).thenReturn(false)
 
-        verify(context, never()).startActivity(notesIntent)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = true)
+
+        verify(context).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_bubblesIsNull_shouldDoNothing() {
+    fun showNoteTask_bubblesIsNull_shouldDoNothing() {
         whenever(optionalBubbles.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_keyguardManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_keyguardManagerIsNull_shouldDoNothing() {
         whenever(optionalKeyguardManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userManagerIsNull_shouldDoNothing() {
+    fun showNoteTask_userManagerIsNull_shouldDoNothing() {
         whenever(optionalUserManager.orElse(null)).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_intentResolverReturnsNull_shouldDoNothing() {
+    fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() {
         whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(null)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_flagDisabled_shouldDoNothing() {
-        createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+    fun showNoteTask_flagDisabled_shouldDoNothing() {
+        createNoteTaskController(isEnabled = false).showNoteTask()
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
 
     @Test
-    fun handleSystemKey_userIsLocked_shouldDoNothing() {
+    fun showNoteTask_userIsLocked_shouldDoNothing() {
         whenever(userManager.isUserUnlocked).thenReturn(false)
 
-        createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+        createNoteTaskController().showNoteTask(isInMultiWindowMode = false)
 
         verify(context, never()).startActivity(notesIntent)
         verify(bubbles, never()).showAppBubble(notesIntent)
     }
+    // endregion
+
+    // region setNoteTaskShortcutEnabled
+    @Test
+    fun setNoteTaskShortcutEnabled_setTrue() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = true)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+
+    @Test
+    fun setNoteTaskShortcutEnabled_setFalse() {
+        createNoteTaskController().setNoteTaskShortcutEnabled(value = false)
+
+        val argument = argumentCaptor<ComponentName>()
+        verify(context.packageManager)
+            .setComponentEnabledSetting(
+                argument.capture(),
+                eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
+                eq(PackageManager.DONT_KILL_APP),
+            )
+        val expected = ComponentName(context, CreateNoteTaskShortcutActivity::class.java)
+        assertThat(argument.value.flattenToString()).isEqualTo(expected.flattenToString())
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
index 334089c..538131a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt
@@ -16,10 +16,10 @@
 package com.android.systemui.notetask
 
 import android.test.suitebuilder.annotation.SmallTest
+import android.view.KeyEvent
 import androidx.test.runner.AndroidJUnit4
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.CommandQueue
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -45,6 +45,7 @@
     @Mock lateinit var commandQueue: CommandQueue
     @Mock lateinit var bubbles: Bubbles
     @Mock lateinit var optionalBubbles: Optional<Bubbles>
+    @Mock lateinit var noteTaskController: NoteTaskController
 
     @Before
     fun setUp() {
@@ -57,12 +58,13 @@
     private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer {
         return NoteTaskInitializer(
             optionalBubbles = optionalBubbles,
-            lazyNoteTaskController = mock(),
+            noteTaskController = noteTaskController,
             commandQueue = commandQueue,
             isEnabled = isEnabled,
         )
     }
 
+    // region initializer
     @Test
     fun initialize_shouldAddCallbacks() {
         createNoteTaskInitializer().initialize()
@@ -85,4 +87,35 @@
 
         verify(commandQueue, never()).addCallback(any())
     }
+
+    @Test
+    fun initialize_flagEnabled_shouldEnableShortcut() {
+        createNoteTaskInitializer().initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(true)
+    }
+
+    @Test
+    fun initialize_flagDisabled_shouldDisableShortcut() {
+        createNoteTaskInitializer(isEnabled = false).initialize()
+
+        verify(noteTaskController).setNoteTaskShortcutEnabled(false)
+    }
+    // endregion
+
+    // region handleSystemKey
+    @Test
+    fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1)
+
+        verify(noteTaskController).showNoteTask()
+    }
+
+    @Test
+    fun handleSystemKey_receiveInvalidSystemKey_shouldDoNothing() {
+        createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent.KEYCODE_UNKNOWN)
+
+        verify(noteTaskController, never()).showNoteTask()
+    }
+    // endregion
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
deleted file mode 100644
index 2ba8782..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FooterActionsControllerTest.kt
+++ /dev/null
@@ -1,440 +0,0 @@
-package com.android.systemui.qs
-
-import android.content.Intent
-import android.os.Handler
-import android.os.UserManager
-import android.provider.Settings
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper
-import android.testing.ViewUtils
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.test.filters.SmallTest
-import com.android.internal.logging.MetricsLogger
-import com.android.internal.logging.UiEventLogger
-import com.android.internal.logging.testing.FakeMetricsLogger
-import com.android.systemui.R
-import com.android.systemui.animation.ActivityLaunchAnimator
-import com.android.systemui.classifier.FalsingManagerFake
-import com.android.systemui.globalactions.GlobalActionsDialogLite
-import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.MultiUserSwitchController
-import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.FakeConfigurationController
-import com.android.systemui.statusbar.policy.UserInfoController
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.settings.FakeSettings
-import com.android.systemui.utils.leaks.LeakCheckedTest
-import com.google.common.truth.Expect
-import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyBoolean
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.atLeastOnce
-import org.mockito.Mockito.clearInvocations
-import org.mockito.Mockito.never
-import org.mockito.Mockito.reset
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
-class FooterActionsControllerTest : LeakCheckedTest() {
-
-    @get:Rule var expect: Expect = Expect.create()
-
-    @Mock private lateinit var userManager: UserManager
-    @Mock private lateinit var userTracker: UserTracker
-    @Mock private lateinit var activityStarter: ActivityStarter
-    @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
-    @Mock private lateinit var userInfoController: UserInfoController
-    @Mock private lateinit var multiUserSwitchControllerFactory: MultiUserSwitchController.Factory
-    @Mock private lateinit var multiUserSwitchController: MultiUserSwitchController
-    @Mock private lateinit var globalActionsDialogProvider: Provider<GlobalActionsDialogLite>
-    @Mock private lateinit var globalActionsDialog: GlobalActionsDialogLite
-    @Mock private lateinit var uiEventLogger: UiEventLogger
-    @Mock private lateinit var securityFooterController: QSSecurityFooter
-    @Mock private lateinit var fgsManagerController: QSFgsManagerFooter
-    @Captor
-    private lateinit var visibilityChangedCaptor:
-        ArgumentCaptor<VisibilityChangedDispatcher.OnVisibilityChangedListener>
-
-    private lateinit var controller: FooterActionsController
-
-    private val configurationController = FakeConfigurationController()
-    private val metricsLogger: MetricsLogger = FakeMetricsLogger()
-    private val falsingManager: FalsingManagerFake = FalsingManagerFake()
-    private lateinit var view: FooterActionsView
-    private lateinit var testableLooper: TestableLooper
-    private lateinit var fakeSettings: FakeSettings
-    private lateinit var securityFooter: View
-    private lateinit var fgsFooter: View
-
-    @Before
-    fun setUp() {
-        // We want to make sure testable resources are always used
-        context.ensureTestableResources()
-
-        MockitoAnnotations.initMocks(this)
-        testableLooper = TestableLooper.get(this)
-        fakeSettings = FakeSettings()
-
-        whenever(multiUserSwitchControllerFactory.create(any()))
-            .thenReturn(multiUserSwitchController)
-        whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
-
-        securityFooter = View(mContext)
-        fgsFooter = View(mContext)
-
-        whenever(securityFooterController.view).thenReturn(securityFooter)
-        whenever(fgsManagerController.view).thenReturn(fgsFooter)
-
-        view = inflateView()
-
-        controller = constructFooterActionsController(view)
-        controller.init()
-        ViewUtils.attachView(view)
-        // View looper is the testable looper associated with the test
-        testableLooper.processAllMessages()
-    }
-
-    @After
-    fun tearDown() {
-        if (view.isAttachedToWindow) {
-            ViewUtils.detachView(view)
-        }
-    }
-
-    @Test
-    fun testInitializesControllers() {
-        verify(multiUserSwitchController).init()
-        verify(fgsManagerController).init()
-        verify(securityFooterController).init()
-    }
-
-    @Test
-    fun testLogPowerMenuClick() {
-        controller.visible = true
-        falsingManager.setFalseTap(false)
-
-        view.findViewById<View>(R.id.pm_lite).performClick()
-        // Verify clicks are logged
-        verify(uiEventLogger, Mockito.times(1))
-            .log(GlobalActionsDialogLite.GlobalActionsEvent.GA_OPEN_QS)
-    }
-
-    @Test
-    fun testSettings() {
-        val captor = ArgumentCaptor.forClass(Intent::class.java)
-        whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
-        view.findViewById<View>(R.id.settings_button_container).performClick()
-
-        verify(activityStarter)
-            .startActivity(capture(captor), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
-
-        assertThat(captor.value.action).isEqualTo(Settings.ACTION_SETTINGS)
-    }
-
-    @Test
-    fun testSettings_UserNotSetup() {
-        whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
-        view.findViewById<View>(R.id.settings_button_container).performClick()
-        // Verify Settings wasn't launched.
-        verify(activityStarter, never())
-            .startActivity(any(), anyBoolean(), any<ActivityLaunchAnimator.Controller>())
-    }
-
-    @Test
-    fun testMultiUserSwitchUpdatedWhenExpansionStarts() {
-        // When expansion starts, listening is set to true
-        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
-
-        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
-        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
-        controller.setListening(true)
-        testableLooper.processAllMessages()
-
-        assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun testMultiUserSwitchUpdatedWhenSettingChanged() {
-        // Always listening to setting while View is attached
-        testableLooper.processAllMessages()
-
-        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
-        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
-        // The setting is only used as an indicator for whether the view should refresh. The actual
-        // value of the setting is ignored; isMultiUserEnabled is the source of truth
-        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
-        // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
-        fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
-        testableLooper.processAllMessages()
-
-        assertThat(multiUserSwitch.visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun testMultiUserSettingNotListenedAfterDetach() {
-        testableLooper.processAllMessages()
-
-        val multiUserSwitch = view.requireViewById<View>(R.id.multi_user_switch)
-        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-
-        ViewUtils.detachView(view)
-
-        // The setting is only used as an indicator for whether the view should refresh. The actual
-        // value of the setting is ignored; isMultiUserEnabled is the source of truth
-        whenever(multiUserSwitchController.isMultiUserEnabled).thenReturn(true)
-
-        // Changing the value of USER_SWITCHER_ENABLED should cause the view to update
-        fakeSettings.putIntForUser(Settings.Global.USER_SWITCHER_ENABLED, 1, userTracker.userId)
-        testableLooper.processAllMessages()
-
-        assertThat(multiUserSwitch.visibility).isNotEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun testCleanUpGAD() {
-        reset(globalActionsDialogProvider)
-        // We are creating a new controller, so detach the views from it
-        (securityFooter.parent as ViewGroup).removeView(securityFooter)
-        (fgsFooter.parent as ViewGroup).removeView(fgsFooter)
-
-        whenever(globalActionsDialogProvider.get()).thenReturn(globalActionsDialog)
-        val view = inflateView()
-        controller = constructFooterActionsController(view)
-        controller.init()
-        verify(globalActionsDialogProvider, never()).get()
-
-        // GAD is constructed during attachment
-        ViewUtils.attachView(view)
-        testableLooper.processAllMessages()
-        verify(globalActionsDialogProvider).get()
-
-        ViewUtils.detachView(view)
-        testableLooper.processAllMessages()
-        verify(globalActionsDialog).destroy()
-    }
-
-    @Test
-    fun testSeparatorVisibility_noneVisible_gone() {
-        verify(securityFooterController)
-            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
-        val listener = visibilityChangedCaptor.value
-        val separator = controller.securityFootersSeparator
-
-        setVisibilities(securityFooterVisible = false, fgsFooterVisible = false, listener)
-        assertThat(separator.visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun testSeparatorVisibility_onlySecurityFooterVisible_gone() {
-        verify(securityFooterController)
-            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
-        val listener = visibilityChangedCaptor.value
-        val separator = controller.securityFootersSeparator
-
-        setVisibilities(securityFooterVisible = true, fgsFooterVisible = false, listener)
-        assertThat(separator.visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun testSeparatorVisibility_onlyFgsFooterVisible_gone() {
-        verify(securityFooterController)
-            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
-        val listener = visibilityChangedCaptor.value
-        val separator = controller.securityFootersSeparator
-
-        setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
-        assertThat(separator.visibility).isEqualTo(View.GONE)
-    }
-
-    @Test
-    fun testSeparatorVisibility_bothVisible_visible() {
-        verify(securityFooterController)
-            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
-        val listener = visibilityChangedCaptor.value
-        val separator = controller.securityFootersSeparator
-
-        setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
-        assertThat(separator.visibility).isEqualTo(View.VISIBLE)
-    }
-
-    @Test
-    fun testFgsFooterCollapsed() {
-        verify(securityFooterController)
-            .setOnVisibilityChangedListener(capture(visibilityChangedCaptor))
-        val listener = visibilityChangedCaptor.value
-
-        val booleanCaptor = ArgumentCaptor.forClass(Boolean::class.java)
-
-        clearInvocations(fgsManagerController)
-        setVisibilities(securityFooterVisible = false, fgsFooterVisible = true, listener)
-        verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
-        assertThat(booleanCaptor.allValues.last()).isFalse()
-
-        clearInvocations(fgsManagerController)
-        setVisibilities(securityFooterVisible = true, fgsFooterVisible = true, listener)
-        verify(fgsManagerController, atLeastOnce()).setCollapsed(capture(booleanCaptor))
-        assertThat(booleanCaptor.allValues.last()).isTrue()
-    }
-
-    @Test
-    fun setExpansion_inSplitShade_alphaFollowsExpansion() {
-        enableSplitShade()
-
-        controller.setExpansion(0f)
-        expect.that(view.alpha).isEqualTo(0f)
-
-        controller.setExpansion(0.25f)
-        expect.that(view.alpha).isEqualTo(0.25f)
-
-        controller.setExpansion(0.5f)
-        expect.that(view.alpha).isEqualTo(0.5f)
-
-        controller.setExpansion(0.75f)
-        expect.that(view.alpha).isEqualTo(0.75f)
-
-        controller.setExpansion(1f)
-        expect.that(view.alpha).isEqualTo(1f)
-    }
-
-    @Test
-    fun setExpansion_inSplitShade_backgroundAlphaFollowsExpansion_with_0_9_delay() {
-        enableSplitShade()
-
-        controller.setExpansion(0f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
-        controller.setExpansion(0.5f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
-        controller.setExpansion(0.9f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(0f)
-
-        controller.setExpansion(0.91f)
-        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.1f)
-
-        controller.setExpansion(0.95f)
-        expect.that(view.backgroundAlphaFraction).isWithin(FLOAT_TOLERANCE).of(0.5f)
-
-        controller.setExpansion(1f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-    }
-
-    @Test
-    fun setExpansion_inSingleShade_alphaFollowsExpansion_with_0_9_delay() {
-        disableSplitShade()
-
-        controller.setExpansion(0f)
-        expect.that(view.alpha).isEqualTo(0f)
-
-        controller.setExpansion(0.5f)
-        expect.that(view.alpha).isEqualTo(0f)
-
-        controller.setExpansion(0.9f)
-        expect.that(view.alpha).isEqualTo(0f)
-
-        controller.setExpansion(0.91f)
-        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.1f)
-
-        controller.setExpansion(0.95f)
-        expect.that(view.alpha).isWithin(FLOAT_TOLERANCE).of(0.5f)
-
-        controller.setExpansion(1f)
-        expect.that(view.alpha).isEqualTo(1f)
-    }
-
-    @Test
-    fun setExpansion_inSingleShade_backgroundAlphaAlways1() {
-        disableSplitShade()
-
-        controller.setExpansion(0f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-
-        controller.setExpansion(0.5f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-
-        controller.setExpansion(1f)
-        expect.that(view.backgroundAlphaFraction).isEqualTo(1f)
-    }
-
-    private fun setVisibilities(
-        securityFooterVisible: Boolean,
-        fgsFooterVisible: Boolean,
-        listener: VisibilityChangedDispatcher.OnVisibilityChangedListener
-    ) {
-        securityFooter.visibility = if (securityFooterVisible) View.VISIBLE else View.GONE
-        listener.onVisibilityChanged(securityFooter.visibility)
-        fgsFooter.visibility = if (fgsFooterVisible) View.VISIBLE else View.GONE
-        listener.onVisibilityChanged(fgsFooter.visibility)
-    }
-
-    private fun inflateView(): FooterActionsView {
-        return LayoutInflater.from(context).inflate(R.layout.footer_actions, null)
-            as FooterActionsView
-    }
-
-    private fun constructFooterActionsController(view: FooterActionsView): FooterActionsController {
-        return FooterActionsController(
-            view,
-            multiUserSwitchControllerFactory,
-            activityStarter,
-            userManager,
-            userTracker,
-            userInfoController,
-            deviceProvisionedController,
-            securityFooterController,
-            fgsManagerController,
-            falsingManager,
-            metricsLogger,
-            globalActionsDialogProvider,
-            uiEventLogger,
-            showPMLiteButton = true,
-            fakeSettings,
-            Handler(testableLooper.looper),
-            configurationController)
-    }
-
-    private fun enableSplitShade() {
-        setSplitShadeEnabled(true)
-    }
-
-    private fun disableSplitShade() {
-        setSplitShadeEnabled(false)
-    }
-
-    private fun setSplitShadeEnabled(enabled: Boolean) {
-        overrideResource(R.bool.config_use_split_notification_shade, enabled)
-        configurationController.notifyConfigurationChanged()
-    }
-}
-
-private const val FLOAT_TOLERANCE = 0.01f
-
-private val View.backgroundAlphaFraction: Float?
-    get() {
-        return if (background != null) {
-            background.alpha / 255f
-        } else {
-            null
-        }
-    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index aedb935..ffe918d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.annotation.Nullable;
 import android.app.Fragment;
 import android.content.Context;
 import android.graphics.Rect;
@@ -42,6 +44,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.lifecycle.Lifecycle;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.BouncerPanelExpansionCalculator;
@@ -50,12 +53,12 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
 import com.android.systemui.qs.external.TileServiceRequestController;
+import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
@@ -99,6 +102,8 @@
     @Mock private QSAnimator mQSAnimator;
     @Mock private SysuiStatusBarStateController mStatusBarStateController;
     @Mock private QSSquishinessController mSquishinessController;
+    @Mock private FooterActionsViewModel mFooterActionsViewModel;
+    @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
     private View mQsFragmentView;
 
     public QSFragmentTest() {
@@ -245,7 +250,8 @@
         fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                 squishinessFraction);
 
-        verify(mQSFooterActionController).setExpansion(panelExpansionFraction);
+        verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
+                panelExpansionFraction, /* isInSplitShade= */ true);
     }
 
     @Test
@@ -262,7 +268,8 @@
         fragment.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
                 squishinessFraction);
 
-        verify(mQSFooterActionController).setExpansion(expansion);
+        verify(mFooterActionsViewModel).onQuickSettingsExpansionChanged(
+                expansion, /* isInSplitShade= */ false);
     }
 
     @Test
@@ -379,6 +386,13 @@
         assertThat(mQsFragmentView.getTranslationY()).isEqualTo(0);
     }
 
+    private Lifecycle.State getListeningAndVisibilityLifecycleState() {
+        return getFragment()
+                .getListeningAndVisibilityLifecycleOwner()
+                .getLifecycle()
+                .getCurrentState();
+    }
+
     @Test
     public void setListeningFalse_notVisible() {
         QSFragment fragment = resumeAndGetFragment();
@@ -387,7 +401,7 @@
 
         fragment.setListening(false);
         verify(mQSContainerImplController).setListening(false);
-        verify(mQSFooterActionController).setListening(false);
+        assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
     }
 
@@ -399,7 +413,7 @@
 
         fragment.setListening(true);
         verify(mQSContainerImplController).setListening(false);
-        verify(mQSFooterActionController).setListening(false);
+        assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.STARTED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
     }
 
@@ -411,7 +425,7 @@
 
         fragment.setListening(false);
         verify(mQSContainerImplController).setListening(false);
-        verify(mQSFooterActionController).setListening(false);
+        assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.CREATED);
         verify(mQSPanelController).setListening(eq(false), anyBoolean());
     }
 
@@ -423,7 +437,7 @@
 
         fragment.setListening(true);
         verify(mQSContainerImplController).setListening(true);
-        verify(mQSFooterActionController).setListening(true);
+        assertThat(getListeningAndVisibilityLifecycleState()).isEqualTo(Lifecycle.State.RESUMED);
         verify(mQSPanelController).setListening(eq(true), anyBoolean());
     }
 
@@ -480,7 +494,6 @@
         setUpOther();
 
         FakeFeatureFlags featureFlags = new FakeFeatureFlags();
-        featureFlags.set(Flags.NEW_FOOTER_ACTIONS, false);
         return new QSFragment(
                 new RemoteInputQuickSettingsDisabler(
                         context, commandQueue, mock(ConfigurationController.class)),
@@ -495,8 +508,8 @@
                 mFalsingManager,
                 mock(DumpManager.class),
                 featureFlags,
-                mock(NewFooterActionsController.class),
-                mock(FooterActionsViewModel.Factory.class));
+                mock(FooterActionsController.class),
+                mFooterActionsViewModelFactory);
     }
 
     private void setUpOther() {
@@ -505,6 +518,7 @@
         when(mQSContainerImplController.getView()).thenReturn(mContainer);
         when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
         when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
+        when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
     }
 
     private void setUpMedia() {
@@ -519,15 +533,40 @@
                 .thenReturn(mQSPanelScrollView);
         when(mQsFragmentView.findViewById(R.id.header)).thenReturn(mHeader);
         when(mQsFragmentView.findViewById(android.R.id.edit)).thenReturn(new View(mContext));
+        when(mQsFragmentView.findViewById(R.id.qs_footer_actions)).thenAnswer(
+                invocation -> FooterActionsViewBinder.create(mContext));
     }
 
     private void setUpInflater() {
+        LayoutInflater realInflater = LayoutInflater.from(mContext);
+
         when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
-        when(mLayoutInflater.inflate(anyInt(), any(ViewGroup.class), anyBoolean()))
-                .thenReturn(mQsFragmentView);
+        when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
+                .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
+                        (ViewGroup) invocation.getArgument(1),
+                        (boolean) invocation.getArgument(2)));
+        when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class)))
+                .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
+                        (ViewGroup) invocation.getArgument(1)));
         mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater);
     }
 
+    private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) {
+        return inflate(realInflater, layoutRes, root, root != null);
+    }
+
+    private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root,
+            boolean attachToRoot) {
+        if (layoutRes == R.layout.footer_actions
+                || layoutRes == R.layout.footer_actions_text_button
+                || layoutRes == R.layout.footer_actions_number_button
+                || layoutRes == R.layout.footer_actions_icon_button) {
+            return realInflater.inflate(layoutRes, root, attachToRoot);
+        }
+
+        return mQsFragmentView;
+    }
+
     private void setupQsComponent() {
         when(mQsComponentFactory.create(any(QSFragment.class))).thenReturn(mQsFragmentComponent);
         when(mQsFragmentComponent.getQSPanelController()).thenReturn(mQSPanelController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index 5e9c1aa..c656d6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -17,23 +17,24 @@
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNull;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 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.annotation.IdRes;
 import android.app.AlertDialog;
 import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.VectorDrawable;
@@ -42,27 +43,27 @@
 import android.provider.Settings;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
-import android.testing.LayoutInflaterBuilder;
-import android.testing.TestableImageView;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
-import android.testing.ViewUtils;
 import android.text.SpannableStringBuilder;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.animation.Expandable;
 import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.common.shared.model.Icon;
 import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig;
+import com.android.systemui.security.data.model.SecurityModel;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.SecurityController;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -71,8 +72,6 @@
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.concurrent.atomic.AtomicInteger;
-
 /*
  * Compile and run the whole SystemUI test suite:
    runtest --path frameworks/base/packages/SystemUI/tests
@@ -96,11 +95,6 @@
             new ComponentName("TestDPC", "Test");
     private static final int DEFAULT_ICON_ID = R.drawable.ic_info_outline;
 
-    private ViewGroup mRootView;
-    private ViewGroup mSecurityFooterView;
-    private TextView mFooterText;
-    private TestableImageView mPrimaryFooterIcon;
-    private QSSecurityFooter mFooter;
     private QSSecurityFooterUtils mFooterUtils;
     @Mock
     private SecurityController mSecurityController;
@@ -122,58 +116,53 @@
         Looper looper = mTestableLooper.getLooper();
         Handler mainHandler = new Handler(looper);
         when(mUserTracker.getUserInfo()).thenReturn(mock(UserInfo.class));
-        mSecurityFooterView = (ViewGroup) new LayoutInflaterBuilder(mContext)
-                .replace("ImageView", TestableImageView.class)
-                .build().inflate(R.layout.quick_settings_security_footer, null, false);
         mFooterUtils = new QSSecurityFooterUtils(getContext(),
                 getContext().getSystemService(DevicePolicyManager.class), mUserTracker,
                 mainHandler, mActivityStarter, mSecurityController, looper, mDialogLaunchAnimator);
-        mFooter = new QSSecurityFooter(mSecurityFooterView, mainHandler, mSecurityController,
-                looper, mBroadcastDispatcher, mFooterUtils);
-        mFooterText = mSecurityFooterView.findViewById(R.id.footer_text);
-        mPrimaryFooterIcon = mSecurityFooterView.findViewById(R.id.primary_footer_icon);
 
         when(mSecurityController.getDeviceOwnerComponentOnAnyUser())
                 .thenReturn(DEVICE_OWNER_COMPONENT);
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
-
-        // mSecurityFooterView must have a ViewGroup parent so that
-        // DialogLaunchAnimator.Controller.fromView() does not return null.
-        mRootView = new FrameLayout(mContext);
-        mRootView.addView(mSecurityFooterView);
-        ViewUtils.attachView(mRootView);
-
-        mFooter.init();
     }
 
-    @After
-    public void tearDown() {
-        ViewUtils.detachView(mRootView);
+    @Nullable
+    private SecurityButtonConfig getButtonConfig() {
+        SecurityModel securityModel = SecurityModel.create(mSecurityController);
+        return mFooterUtils.getButtonConfig(securityModel);
+    }
+
+    private void assertIsDefaultIcon(Icon icon) {
+        assertIsIconResource(icon, DEFAULT_ICON_ID);
+    }
+
+    private void assertIsIconResource(Icon icon, @IdRes int res) {
+        assertThat(icon).isInstanceOf(Icon.Resource.class);
+        assertEquals(res, ((Icon.Resource) icon).getRes());
+    }
+
+    private void assertIsIconDrawable(Icon icon, Drawable drawable) {
+        assertThat(icon).isInstanceOf(Icon.Loaded.class);
+        assertEquals(drawable, ((Icon.Loaded) icon).getDrawable());
     }
 
     @Test
     public void testUnmanaged() {
         when(mSecurityController.isDeviceManaged()).thenReturn(false);
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(false);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(View.GONE, mSecurityFooterView.getVisibility());
+        assertNull(getButtonConfig());
     }
 
     @Test
     public void testManagedNoOwnerName() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName()).thenReturn(null);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management),
-                     mFooterText.getText());
-        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+                     buttonConfig.getText());
+        assertIsDefaultIcon(buttonConfig.getIcon());
     }
 
     @Test
@@ -181,15 +170,13 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management,
-                                        MANAGING_ORGANIZATION),
-                mFooterText.getText());
-        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+                        MANAGING_ORGANIZATION),
+                buttonConfig.getText());
+        assertIsDefaultIcon(buttonConfig.getIcon());
     }
 
     @Test
@@ -200,15 +187,13 @@
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
-                R.string.quick_settings_financed_disclosure_named_management,
-                MANAGING_ORGANIZATION), mFooterText.getText());
-        assertEquals(View.VISIBLE, mSecurityFooterView.getVisibility());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+                        R.string.quick_settings_financed_disclosure_named_management,
+                        MANAGING_ORGANIZATION),
+                buttonConfig.getText());
+        assertIsDefaultIcon(buttonConfig.getIcon());
     }
 
     @Test
@@ -220,21 +205,16 @@
         when(mUserTracker.getUserInfo()).thenReturn(mockUserInfo);
         Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.DEVICE_DEMO_MODE, 1);
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(View.GONE, mSecurityFooterView.getVisibility());
+        assertNull(getButtonConfig());
     }
 
     @Test
     public void testUntappableView_profileOwnerOfOrgOwnedDevice() {
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mSecurityFooterView.isClickable());
-        assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertFalse(buttonConfig.isClickable());
     }
 
     @Test
@@ -244,12 +224,9 @@
         when(mSecurityController.isWorkProfileOn()).thenReturn(true);
         when(mSecurityController.hasWorkProfile()).thenReturn(true);
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-        assertTrue(mSecurityFooterView.isClickable());
-        assertEquals(View.VISIBLE,
-                mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertTrue(buttonConfig.isClickable());
     }
 
     @Test
@@ -258,35 +235,31 @@
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
         when(mSecurityController.isWorkProfileOn()).thenReturn(false);
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-        assertFalse(mSecurityFooterView.isClickable());
-        assertEquals(View.GONE, mSecurityFooterView.findViewById(R.id.footer_icon).getVisibility());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertFalse(buttonConfig.isClickable());
     }
 
     @Test
     public void testNetworkLoggingEnabled_deviceOwner() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
-                mFooterText.getText());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+                buttonConfig.getText());
+        assertIsDefaultIcon(buttonConfig.getIcon());
 
         // Same situation, but with organization name set
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
-                             R.string.quick_settings_disclosure_named_management_monitoring,
-                             MANAGING_ORGANIZATION),
-                     mFooterText.getText());
+                        R.string.quick_settings_disclosure_named_management_monitoring,
+                        MANAGING_ORGANIZATION),
+                buttonConfig.getText());
     }
 
     @Test
@@ -294,12 +267,12 @@
         when(mSecurityController.hasWorkProfile()).thenReturn(true);
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
         when(mSecurityController.isWorkProfileOn()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
-                R.string.quick_settings_disclosure_managed_profile_network_activity),
-                mFooterText.getText());
+                        R.string.quick_settings_disclosure_managed_profile_network_activity),
+                buttonConfig.getText());
     }
 
     @Test
@@ -307,21 +280,19 @@
         when(mSecurityController.hasWorkProfile()).thenReturn(true);
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
         when(mSecurityController.isWorkProfileOn()).thenReturn(false);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals("", mFooterText.getText());
+        assertNull(getButtonConfig());
     }
 
     @Test
     public void testManagedCACertsInstalled() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
-                mFooterText.getText());
+                buttonConfig.getText());
     }
 
     @Test
@@ -329,25 +300,23 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_named_vpn,
-                                        VPN_PACKAGE),
-                     mFooterText.getText());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+                        VPN_PACKAGE),
+                buttonConfig.getText());
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
 
         // Same situation, but with organization name set
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
-                              R.string.quick_settings_disclosure_named_management_named_vpn,
-                              MANAGING_ORGANIZATION, VPN_PACKAGE),
-                     mFooterText.getText());
+                        R.string.quick_settings_disclosure_named_management_named_vpn,
+                        MANAGING_ORGANIZATION, VPN_PACKAGE),
+                buttonConfig.getText());
     }
 
     @Test
@@ -356,23 +325,21 @@
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
         when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_vpns),
-                     mFooterText.getText());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+                     buttonConfig.getText());
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
 
         // Same situation, but with organization name set
         when(mSecurityController.getDeviceOwnerOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_management_vpns,
                                         MANAGING_ORGANIZATION),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -381,13 +348,12 @@
         when(mSecurityController.isNetworkLoggingEnabled()).thenReturn(true);
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn("VPN Test App");
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_management_monitoring),
-                mFooterText.getText());
+                buttonConfig.getText());
     }
 
     @Test
@@ -395,24 +361,23 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(false);
         when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
         when(mSecurityController.isWorkProfileOn()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsDefaultIcon(buttonConfig.getIcon());
         assertEquals(mContext.getString(
                              R.string.quick_settings_disclosure_managed_profile_monitoring),
-                     mFooterText.getText());
+                     buttonConfig.getText());
 
         // Same situation, but with organization name set
         when(mSecurityController.getWorkProfileOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
                              R.string.quick_settings_disclosure_named_managed_profile_monitoring,
                              MANAGING_ORGANIZATION),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -420,22 +385,20 @@
         when(mSecurityController.isDeviceManaged()).thenReturn(false);
         when(mSecurityController.hasCACertInWorkProfile()).thenReturn(true);
         when(mSecurityController.isWorkProfileOn()).thenReturn(false);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals("", mFooterText.getText());
+        assertNull(getButtonConfig());
     }
 
     @Test
     public void testCACertsInstalled() {
         when(mSecurityController.isDeviceManaged()).thenReturn(false);
         when(mSecurityController.hasCACertInCurrentUser()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsDefaultIcon(buttonConfig.getIcon());
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_monitoring),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -443,12 +406,12 @@
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
         when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_vpns),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -456,14 +419,14 @@
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
         when(mSecurityController.isWorkProfileOn()).thenReturn(true);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
         assertEquals(mContext.getString(
                              R.string.quick_settings_disclosure_managed_profile_named_vpn,
                              VPN_PACKAGE_2),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -471,22 +434,19 @@
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getWorkProfileVpnName()).thenReturn(VPN_PACKAGE_2);
         when(mSecurityController.isWorkProfileOn()).thenReturn(false);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals("", mFooterText.getText());
+        assertNull(getButtonConfig());
     }
 
     @Test
     public void testProfileOwnerOfOrganizationOwnedDeviceNoName() {
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
 
-        mFooter.refreshState();
-        TestableLooper.get(this).processAllMessages();
-
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
                 R.string.quick_settings_disclosure_management),
-                mFooterText.getText());
+                buttonConfig.getText());
     }
 
     @Test
@@ -495,35 +455,33 @@
         when(mSecurityController.getWorkProfileOrganizationName())
                 .thenReturn(MANAGING_ORGANIZATION);
 
-        mFooter.refreshState();
-        TestableLooper.get(this).processAllMessages();
-
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
                 R.string.quick_settings_disclosure_named_management,
                 MANAGING_ORGANIZATION),
-                mFooterText.getText());
+                buttonConfig.getText());
     }
 
     @Test
     public void testVpnEnabled() {
         when(mSecurityController.isVpnEnabled()).thenReturn(true);
         when(mSecurityController.getPrimaryVpnName()).thenReturn(VPN_PACKAGE);
-        mFooter.refreshState();
 
-        TestableLooper.get(this).processAllMessages();
-        assertEquals(R.drawable.stat_sys_vpn_ic, mPrimaryFooterIcon.getLastImageResource());
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsIconResource(buttonConfig.getIcon(), R.drawable.stat_sys_vpn_ic);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_named_vpn,
                                         VPN_PACKAGE),
-                     mFooterText.getText());
+                     buttonConfig.getText());
 
         when(mSecurityController.hasWorkProfile()).thenReturn(true);
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(
                              R.string.quick_settings_disclosure_personal_profile_named_vpn,
                              VPN_PACKAGE),
-                     mFooterText.getText());
+                     buttonConfig.getText());
     }
 
     @Test
@@ -687,45 +645,33 @@
     }
 
     @Test
-    public void testNoClickWhenGone() {
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-
-        assertFalse(mFooter.hasFooter());
-        mFooter.onClick(mFooter.getView());
-
-        // Proxy for dialog being created
-        verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
-    }
-
-    @Test
     public void testParentalControls() {
         // Make sure the security footer is visible, so that the images are updated.
         when(mSecurityController.isProfileOwnerOfOrganizationOwnedDevice()).thenReturn(true);
-
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(true);
 
+        // We use the default icon when there is no admin icon.
+        when(mSecurityController.getIcon(any())).thenReturn(null);
+        SecurityButtonConfig buttonConfig = getButtonConfig();
+        assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
+                buttonConfig.getText());
+        assertIsDefaultIcon(buttonConfig.getIcon());
+
         Drawable testDrawable = new VectorDrawable();
         when(mSecurityController.getIcon(any())).thenReturn(testDrawable);
         assertNotNull(mSecurityController.getIcon(null));
 
-        mFooter.refreshState();
-
-        TestableLooper.get(this).processAllMessages();
-
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
         assertEquals(mContext.getString(R.string.quick_settings_disclosure_parental_controls),
-                mFooterText.getText());
-        assertEquals(View.VISIBLE, mPrimaryFooterIcon.getVisibility());
-
-        assertEquals(testDrawable, mPrimaryFooterIcon.getDrawable());
+                buttonConfig.getText());
+        assertIsIconDrawable(buttonConfig.getIcon(), testDrawable);
 
         // Ensure the primary icon is back to default after parental controls are gone
         when(mSecurityController.isParentalControlsEnabled()).thenReturn(false);
-        mFooter.refreshState();
-        TestableLooper.get(this).processAllMessages();
-
-        assertEquals(DEFAULT_ICON_ID, mPrimaryFooterIcon.getLastImageResource());
+        buttonConfig = getButtonConfig();
+        assertNotNull(buttonConfig);
+        assertIsDefaultIcon(buttonConfig.getIcon());
     }
 
     @Test
@@ -739,16 +685,6 @@
     }
 
     @Test
-    public void testDialogUsesDialogLauncher() {
-        when(mSecurityController.isDeviceManaged()).thenReturn(true);
-        mFooter.onClick(mSecurityFooterView);
-
-        mTestableLooper.processAllMessages();
-
-        verify(mDialogLaunchAnimator).show(any(), any());
-    }
-
-    @Test
     public void testCreateDialogViewForFinancedDevice() {
         when(mSecurityController.isDeviceManaged()).thenReturn(true);
         when(mSecurityController.getDeviceOwnerOrganizationName())
@@ -778,7 +714,10 @@
         when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
                 .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
 
-        mFooter.showDeviceMonitoringDialog();
+        Expandable expandable = mock(Expandable.class);
+        when(expandable.dialogLaunchController(any())).thenReturn(
+                mock(DialogLaunchAnimator.Controller.class));
+        mFooterUtils.showDeviceMonitoringDialog(getContext(), expandable);
         ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
 
         mTestableLooper.processAllMessages();
@@ -793,47 +732,6 @@
         dialog.dismiss();
     }
 
-    @Test
-    public void testVisibilityListener() {
-        final AtomicInteger lastVisibility = new AtomicInteger(-1);
-        VisibilityChangedDispatcher.OnVisibilityChangedListener listener = lastVisibility::set;
-
-        mFooter.setOnVisibilityChangedListener(listener);
-
-        when(mSecurityController.isDeviceManaged()).thenReturn(true);
-        mFooter.refreshState();
-        mTestableLooper.processAllMessages();
-        assertEquals(View.VISIBLE, lastVisibility.get());
-
-        when(mSecurityController.isDeviceManaged()).thenReturn(false);
-        mFooter.refreshState();
-        mTestableLooper.processAllMessages();
-        assertEquals(View.GONE, lastVisibility.get());
-    }
-
-    @Test
-    public void testBroadcastShowsDialog() {
-        // Setup dialog content
-        when(mSecurityController.isDeviceManaged()).thenReturn(true);
-        when(mSecurityController.getDeviceOwnerOrganizationName())
-                .thenReturn(MANAGING_ORGANIZATION);
-        when(mSecurityController.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
-                .thenReturn(DEVICE_OWNER_TYPE_FINANCED);
-
-        ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
-        verify(mBroadcastDispatcher).registerReceiverWithHandler(captor.capture(), any(), any(),
-                any());
-
-        // Pretend view is not attached anymore.
-        mRootView.removeView(mSecurityFooterView);
-        captor.getValue().onReceive(mContext,
-                new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG));
-        mTestableLooper.processAllMessages();
-
-        assertTrue(mFooterUtils.getDialog().isShowing());
-        mFooterUtils.getDialog().dismiss();
-    }
-
     private CharSequence addLink(CharSequence description) {
         final SpannableStringBuilder message = new SpannableStringBuilder();
         message.append(description);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 47afa70..01411c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -385,4 +385,86 @@
         underTest.onVisibilityChangeRequested(visible = true)
         assertThat(underTest.isVisible.value).isTrue()
     }
+
+    @Test
+    fun alpha_inSplitShade_followsExpansion() {
+        val underTest = utils.footerActionsViewModel()
+
+        underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
+        assertThat(underTest.alpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.25f, isInSplitShade = true)
+        assertThat(underTest.alpha.value).isEqualTo(0.25f)
+
+        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+        assertThat(underTest.alpha.value).isEqualTo(0.5f)
+
+        underTest.onQuickSettingsExpansionChanged(0.75f, isInSplitShade = true)
+        assertThat(underTest.alpha.value).isEqualTo(0.75f)
+
+        underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
+        assertThat(underTest.alpha.value).isEqualTo(1f)
+    }
+
+    @Test
+    fun backgroundAlpha_inSplitShade_followsExpansion_with_0_99_delay() {
+        val underTest = utils.footerActionsViewModel()
+        val floatTolerance = 0.01f
+
+        underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.991f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.1f)
+
+        underTest.onQuickSettingsExpansionChanged(0.995f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isWithin(floatTolerance).of(0.5f)
+
+        underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = true)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+    }
+
+    @Test
+    fun alpha_inSingleShade_followsExpansion_with_0_9_delay() {
+        val underTest = utils.footerActionsViewModel()
+        val floatTolerance = 0.01f
+
+        underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.9f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isEqualTo(0f)
+
+        underTest.onQuickSettingsExpansionChanged(0.91f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.1f)
+
+        underTest.onQuickSettingsExpansionChanged(0.95f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isWithin(floatTolerance).of(0.5f)
+
+        underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
+        assertThat(underTest.alpha.value).isEqualTo(1f)
+    }
+
+    @Test
+    fun backgroundAlpha_inSingleShade_always1() {
+        val underTest = utils.footerActionsViewModel()
+
+        underTest.onQuickSettingsExpansionChanged(0f, isInSplitShade = false)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+
+        underTest.onQuickSettingsExpansionChanged(0.5f, isInSplitShade = false)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+
+        underTest.onQuickSettingsExpansionChanged(1f, isInSplitShade = false)
+        assertThat(underTest.backgroundAlpha.value).isEqualTo(1f)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
index d91baa5..80c39cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java
@@ -23,6 +23,7 @@
 
 
 import android.os.Handler;
+import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -38,6 +39,7 @@
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
 import com.android.systemui.statusbar.connectivity.AccessPointController;
+import com.android.systemui.statusbar.connectivity.IconState;
 import com.android.systemui.statusbar.connectivity.NetworkController;
 
 import org.junit.Before;
@@ -113,4 +115,24 @@
             .isNotEqualTo(mContext.getString(R.string.quick_settings_networks_available));
         assertThat(mTile.getLastTileState()).isEqualTo(-1);
     }
+
+    @Test
+    public void setIsAirplaneMode_APM_enabled_wifi_disabled() {
+        IconState state = new IconState(true, 0, "");
+        mTile.mSignalCallback.setIsAirplaneMode(state);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.getState().state).isEqualTo(Tile.STATE_INACTIVE);
+        assertThat(mTile.getState().secondaryLabel)
+            .isEqualTo(mContext.getString(R.string.status_bar_airplane));
+    }
+
+    @Test
+    public void setIsAirplaneMode_APM_enabled_wifi_enabled() {
+        IconState state = new IconState(false, 0, "");
+        mTile.mSignalCallback.setIsAirplaneMode(state);
+        mTestableLooper.processAllMessages();
+        assertThat(mTile.getState().state).isEqualTo(Tile.STATE_ACTIVE);
+        assertThat(mTile.getState().secondaryLabel)
+            .isNotEqualTo(mContext.getString(R.string.status_bar_airplane));
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 56a840c..0302dad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -173,6 +173,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -611,6 +612,7 @@
     }
 
     @Test
+    @Ignore("b/261472011 - Test appears inconsistent across environments")
     public void getVerticalSpaceForLockscreenNotifications_useLockIconBottomPadding_returnsSpaceAvailable() {
         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 20,
@@ -622,6 +624,7 @@
     }
 
     @Test
+    @Ignore("b/261472011 - Test appears inconsistent across environments")
     public void getVerticalSpaceForLockscreenNotifications_useIndicationBottomPadding_returnsSpaceAvailable() {
         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 0,
@@ -633,6 +636,7 @@
     }
 
     @Test
+    @Ignore("b/261472011 - Test appears inconsistent across environments")
     public void getVerticalSpaceForLockscreenNotifications_useAmbientBottomPadding_returnsSpaceAvailable() {
         setBottomPadding(/* stackScrollLayoutBottom= */ 180,
                 /* lockIconPadding= */ 0,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
similarity index 92%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
index 17d81c8..7693fee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionMonitorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
@@ -68,13 +68,15 @@
         mConditionMonitor = new Monitor(mExecutor);
     }
 
-    public Monitor.Subscription.Builder getDefaultBuilder(Monitor.Callback callback) {
+    public Monitor.Subscription.Builder getDefaultBuilder(
+            Monitor.Callback callback) {
         return new Monitor.Subscription.Builder(callback)
                 .addConditions(mConditions);
     }
 
     private Condition createMockCondition() {
-        final Condition condition = Mockito.mock(Condition.class);
+        final Condition condition = Mockito.mock(
+                Condition.class);
         when(condition.isConditionSet()).thenReturn(true);
         return condition;
     }
@@ -83,11 +85,14 @@
     public void testOverridingCondition() {
         final Condition overridingCondition = createMockCondition();
         final Condition regularCondition = createMockCondition();
-        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+        final Monitor.Callback callback = Mockito.mock(
+                Monitor.Callback.class);
 
-        final Monitor.Callback referenceCallback = Mockito.mock(Monitor.Callback.class);
+        final Monitor.Callback referenceCallback = Mockito.mock(
+                Monitor.Callback.class);
 
-        final Monitor monitor = new Monitor(mExecutor);
+        final Monitor
+                monitor = new Monitor(mExecutor);
 
         monitor.addSubscription(getDefaultBuilder(callback)
                 .addCondition(overridingCondition)
@@ -136,9 +141,11 @@
         final Condition overridingCondition = createMockCondition();
         final Condition overridingCondition2 = createMockCondition();
         final Condition regularCondition = createMockCondition();
-        final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
+        final Monitor.Callback callback = Mockito.mock(
+                Monitor.Callback.class);
 
-        final Monitor monitor = new Monitor(mExecutor);
+        final Monitor
+                monitor = new Monitor(mExecutor);
 
         monitor.addSubscription(getDefaultBuilder(callback)
                 .addCondition(overridingCondition)
@@ -211,9 +218,11 @@
     public void addCallback_addSecondCallback_reportWithExistingValue() {
         final Monitor.Callback callback1 =
                 mock(Monitor.Callback.class);
-        final Condition condition = mock(Condition.class);
+        final Condition condition = mock(
+                Condition.class);
         when(condition.isConditionMet()).thenReturn(true);
-        final Monitor monitor = new Monitor(mExecutor);
+        final Monitor
+                monitor = new Monitor(mExecutor);
         monitor.addSubscription(new Monitor.Subscription.Builder(callback1)
                 .addCondition(condition)
                 .build());
@@ -229,8 +238,10 @@
 
     @Test
     public void addCallback_noConditions_reportAllConditionsMet() {
-        final Monitor monitor = new Monitor(mExecutor);
-        final Monitor.Callback callback = mock(Monitor.Callback.class);
+        final Monitor
+                monitor = new Monitor(mExecutor);
+        final Monitor.Callback callback = mock(
+                Monitor.Callback.class);
 
         monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
         mExecutor.runAllReady();
@@ -239,8 +250,10 @@
 
     @Test
     public void removeCallback_noFailureOnDoubleRemove() {
-        final Condition condition = mock(Condition.class);
-        final Monitor monitor = new Monitor(mExecutor);
+        final Condition condition = mock(
+                Condition.class);
+        final Monitor
+                monitor = new Monitor(mExecutor);
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
         final Monitor.Subscription.Token token = monitor.addSubscription(
@@ -255,8 +268,10 @@
 
     @Test
     public void removeCallback_shouldNoLongerReceiveUpdate() {
-        final Condition condition = mock(Condition.class);
-        final Monitor monitor = new Monitor(mExecutor);
+        final Condition condition = mock(
+                Condition.class);
+        final Monitor
+                monitor = new Monitor(mExecutor);
         final Monitor.Callback callback =
                 mock(Monitor.Callback.class);
         final Monitor.Subscription.Token token = monitor.addSubscription(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
index 2878864..8443221 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/ConditionTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -47,16 +47,20 @@
 
     @Test
     public void addCallback_addFirstCallback_triggerStart() {
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(mCondition).start();
     }
 
     @Test
     public void addCallback_addMultipleCallbacks_triggerStartOnlyOnce() {
-        final Condition.Callback callback1 = mock(Condition.Callback.class);
-        final Condition.Callback callback2 = mock(Condition.Callback.class);
-        final Condition.Callback callback3 = mock(Condition.Callback.class);
+        final Condition.Callback callback1 = mock(
+                Condition.Callback.class);
+        final Condition.Callback callback2 = mock(
+                Condition.Callback.class);
+        final Condition.Callback callback3 = mock(
+                Condition.Callback.class);
 
         mCondition.addCallback(callback1);
         mCondition.addCallback(callback2);
@@ -67,12 +71,14 @@
 
     @Test
     public void addCallback_alreadyStarted_triggerUpdate() {
-        final Condition.Callback callback1 = mock(Condition.Callback.class);
+        final Condition.Callback callback1 = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback1);
 
         mCondition.fakeUpdateCondition(true);
 
-        final Condition.Callback callback2 = mock(Condition.Callback.class);
+        final Condition.Callback callback2 = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback2);
         verify(callback2).onConditionChanged(mCondition);
         assertThat(mCondition.isConditionMet()).isTrue();
@@ -80,7 +86,8 @@
 
     @Test
     public void removeCallback_removeLastCallback_triggerStop() {
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
         verify(mCondition, never()).stop();
 
@@ -92,7 +99,8 @@
     public void updateCondition_falseToTrue_reportTrue() {
         mCondition.fakeUpdateCondition(false);
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
@@ -104,7 +112,8 @@
     public void updateCondition_trueToFalse_reportFalse() {
         mCondition.fakeUpdateCondition(true);
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
@@ -116,7 +125,8 @@
     public void updateCondition_trueToTrue_reportNothing() {
         mCondition.fakeUpdateCondition(true);
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(true);
@@ -127,7 +137,8 @@
     public void updateCondition_falseToFalse_reportNothing() {
         mCondition.fakeUpdateCondition(false);
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         mCondition.addCallback(callback);
 
         mCondition.fakeUpdateCondition(false);
@@ -149,7 +160,8 @@
         final Condition combinedCondition = mCondition.or(
                 new FakeCondition(/* initialValue= */ false));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -164,7 +176,8 @@
         final Condition combinedCondition = mCondition.or(
                 new FakeCondition(/* initialValue= */ true));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -179,7 +192,8 @@
         final Condition combinedCondition = mCondition.or(
                 new FakeCondition(/* initialValue= */ true));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -195,7 +209,8 @@
         final Condition combinedCondition = mCondition.or(
                 new FakeCondition(/* initialValue= */ null));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -211,7 +226,8 @@
         final Condition combinedCondition = mCondition.or(
                 new FakeCondition(/* initialValue= */ null));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isFalse();
@@ -226,7 +242,8 @@
         final Condition combinedCondition = mCondition.and(
                 new FakeCondition(/* initialValue= */ false));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -241,7 +258,8 @@
         final Condition combinedCondition = mCondition.and(
                 new FakeCondition(/* initialValue= */ true));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -256,7 +274,8 @@
         final Condition combinedCondition = mCondition.and(
                 new FakeCondition(/* initialValue= */ false));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
@@ -272,7 +291,8 @@
         final Condition combinedCondition = mCondition.and(
                 new FakeCondition(/* initialValue= */ null));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isFalse();
@@ -288,7 +308,8 @@
         final Condition combinedCondition = mCondition.and(
                 new FakeCondition(/* initialValue= */ null));
 
-        final Condition.Callback callback = mock(Condition.Callback.class);
+        final Condition.Callback callback = mock(
+                Condition.Callback.class);
         combinedCondition.addCallback(callback);
 
         assertThat(combinedCondition.isConditionSet()).isTrue();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
similarity index 91%
rename from packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
rename to packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
index 07ed110..55a6d39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/condition/FakeCondition.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/condition/FakeCondition.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.util.condition;
+package com.android.systemui.shared.condition;
 
 /**
  * Fake implementation of {@link Condition}, and provides a way for tests to update
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index faf4592..5431eba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -72,6 +72,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
@@ -245,6 +246,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index ca75a40..9441d49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -49,6 +49,7 @@
 import com.android.settingslib.net.DataUsageController;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -150,6 +151,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index 84c242c..4c1f0a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.plugins.log.LogBuffer;
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.CarrierConfigTracker;
 
@@ -78,6 +79,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
@@ -115,6 +117,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mMockProvisionController,
@@ -150,6 +153,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
@@ -188,6 +192,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
@@ -274,6 +279,7 @@
                 mFakeExecutor,
                 mCallbackHandler,
                 mock(AccessPointControllerImpl.class),
+                mock(StatusBarPipelineFlags.class),
                 mock(DataUsageController.class),
                 mMockSubDefaults,
                 mock(DeviceProvisionedController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
new file mode 100644
index 0000000..89faa239
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/RoundableTest.kt
@@ -0,0 +1,164 @@
+package com.android.systemui.statusbar.notification
+
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(JUnit4::class)
+class RoundableTest : SysuiTestCase() {
+    val targetView: View = mock()
+    val roundable = FakeRoundable(targetView)
+
+    @Test
+    fun defaultConfig_shouldNotHaveRoundedCorner() {
+        // the expected default value for the roundness is top = 0f, bottom = 0f
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun applyRoundnessAndInvalidate_should_invalidate_targetView() {
+        roundable.applyRoundnessAndInvalidate()
+
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestTopRoundness_update_and_invalidate_targetView() {
+        roundable.requestTopRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestBottomRoundness_update_and_invalidate_targetView() {
+        roundable.requestBottomRoundness(value = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, times(1)).invalidate()
+    }
+
+    @Test
+    fun requestRoundness_update_and_invalidate_targetView() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun requestRoundnessReset_update_and_invalidate_targetView() {
+        roundable.requestRoundness(1f, 1f, SOURCE1)
+        assertEquals(1f, roundable.roundableState.topRoundness)
+        assertEquals(1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundnessReset(sourceType = SOURCE1)
+
+        assertEquals(0f, roundable.roundableState.topRoundness)
+        assertEquals(0f, roundable.roundableState.bottomRoundness)
+        verify(targetView, atLeastOnce()).invalidate()
+    }
+
+    @Test
+    fun hasRoundedCorner_return_true_ifRoundnessIsGreaterThenZero() {
+        roundable.requestRoundness(top = 1f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 1f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 1f, sourceType = SOURCE1)
+        assertEquals(true, roundable.hasRoundedCorner())
+
+        roundable.requestRoundness(top = 0f, bottom = 0f, sourceType = SOURCE1)
+        assertEquals(false, roundable.hasRoundedCorner())
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.1f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_higher_second_step() {
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        assertEquals(0.1f, roundable.roundableState.topRoundness)
+        assertEquals(0.1f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.3f, 0.3f, SOURCE1)
+        // SOURCE1 has 0.3f - SOURCE2 has 0.2f
+        assertEquals(0.3f, roundable.roundableState.topRoundness)
+        assertEquals(0.3f, roundable.roundableState.bottomRoundness)
+    }
+
+    @Test
+    fun roundness_take_maxValue_onMultipleSources_first_lower_second_step() {
+        roundable.requestRoundness(0.5f, 0.5f, SOURCE1)
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.2f, 0.2f, SOURCE2)
+        // SOURCE1 has 0.5f - SOURCE2 has 0.2f
+        assertEquals(0.5f, roundable.roundableState.topRoundness)
+        assertEquals(0.5f, roundable.roundableState.bottomRoundness)
+
+        roundable.requestRoundness(0.1f, 0.1f, SOURCE1)
+        // SOURCE1 has 0.1f - SOURCE2 has 0.2f
+        assertEquals(0.2f, roundable.roundableState.topRoundness)
+        assertEquals(0.2f, roundable.roundableState.bottomRoundness)
+    }
+
+    class FakeRoundable(
+        targetView: View,
+        radius: Float = MAX_RADIUS,
+    ) : Roundable {
+        override val roundableState =
+            RoundableState(
+                targetView = targetView,
+                roundable = this,
+                maxRadius = radius,
+            )
+    }
+
+    companion object {
+        private const val MAX_RADIUS = 10f
+        private val SOURCE1 = SourceType.from("Source1")
+        private val SOURCE2 = SourceType.from("Source2")
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 088d165..59d4720 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -213,8 +213,7 @@
             SourceType sourceType
     ) throws Exception {
         ExpandableNotificationRow row = createRow();
-        row.requestTopRoundness(topRoundness, false, sourceType);
-        row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+        row.requestRoundness(topRoundness, bottomRoundness, sourceType, /*animate = */ false);
         assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
         assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
         return row;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 438b528..fd1944e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,7 +25,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.SourceType;
+import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
@@ -158,7 +158,7 @@
         ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnScroll);
+                /* sourceType = */ LegacySourceType.OnScroll);
 
         mChildrenContainer.addNotification(row, 0);
 
@@ -171,11 +171,11 @@
         ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.DefaultValue);
+                /* sourceType = */ LegacySourceType.DefaultValue);
         ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnDismissAnimation);
+                /* sourceType = */ LegacySourceType.OnDismissAnimation);
 
         mChildrenContainer.addNotification(row1, 0);
         mChildrenContainer.addNotification(row2, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 8c8b644..bd0a556 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -35,6 +35,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationRoundnessLogger;
@@ -73,7 +74,8 @@
         mRoundnessManager = new NotificationRoundnessManager(
                 new NotificationSectionsFeatureManager(new DeviceConfigProxy(), mContext),
                 mLogger,
-                mock(DumpManager.class));
+                mock(DumpManager.class),
+                mock(FeatureFlags.class));
         allowTestableLooperAsMainThread();
         NotificationTestHelper testHelper = new NotificationTestHelper(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index ecc0224..30da08e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -30,6 +30,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -59,10 +60,12 @@
     @Mock private KeyguardMediaController mKeyguardMediaController;
     @Mock private NotificationSectionsFeatureManager mSectionsFeatureManager;
     @Mock private MediaContainerController mMediaContainerController;
+    @Mock private NotificationRoundnessManager mNotificationRoundnessManager;
     @Mock private SectionHeaderController mIncomingHeaderController;
     @Mock private SectionHeaderController mPeopleHeaderController;
     @Mock private SectionHeaderController mAlertingHeaderController;
     @Mock private SectionHeaderController mSilentHeaderController;
+    @Mock private FeatureFlags mFeatureFlag;
 
     private NotificationSectionsManager mSectionsManager;
 
@@ -89,10 +92,12 @@
                         mKeyguardMediaController,
                         mSectionsFeatureManager,
                         mMediaContainerController,
+                        mNotificationRoundnessManager,
                         mIncomingHeaderController,
                         mPeopleHeaderController,
                         mAlertingHeaderController,
-                        mSilentHeaderController
+                        mSilentHeaderController,
+                        mFeatureFlag
                 );
         // Required in order for the header inflation to work properly
         when(mNssl.generateLayoutParams(any(AttributeSet.class)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index bda2336..9d759c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -9,7 +9,7 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.notification.SourceType
+import com.android.systemui.statusbar.notification.LegacySourceType
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper
@@ -314,9 +314,9 @@
         val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnScroll)
+                /* sourceType = */ LegacySourceType.OnScroll)
 
-        NotificationShelf.resetOnScrollRoundness(row)
+        NotificationShelf.resetLegacyOnScrollRoundness(row)
 
         assertEquals(0f, row.topRoundness)
         assertEquals(0f, row.bottomRoundness)
@@ -327,14 +327,14 @@
         val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.DefaultValue)
+                /* sourceType = */ LegacySourceType.DefaultValue)
         val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
                 /* topRoundness = */ 1f,
                 /* bottomRoundness = */ 1f,
-                /* sourceType = */ SourceType.OnDismissAnimation)
+                /* sourceType = */ LegacySourceType.OnDismissAnimation)
 
-        NotificationShelf.resetOnScrollRoundness(row1)
-        NotificationShelf.resetOnScrollRoundness(row2)
+        NotificationShelf.resetLegacyOnScrollRoundness(row1)
+        NotificationShelf.resetLegacyOnScrollRoundness(row2)
 
         assertEquals(1f, row1.topRoundness)
         assertEquals(1f, row1.bottomRoundness)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 4ea1c71..680a323 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -74,6 +74,7 @@
     private NotificationSwipeHelper mSwipeHelper;
     private NotificationSwipeHelper.NotificationCallback mCallback;
     private NotificationMenuRowPlugin.OnMenuEventListener mListener;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
     private View mView;
     private MotionEvent mEvent;
     private NotificationMenuRowPlugin mMenuRow;
@@ -92,10 +93,17 @@
     public void setUp() throws Exception {
         mCallback = mock(NotificationSwipeHelper.NotificationCallback.class);
         mListener = mock(NotificationMenuRowPlugin.OnMenuEventListener.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
         mFeatureFlags = mock(FeatureFlags.class);
         mSwipeHelper = spy(new NotificationSwipeHelper(
-                mContext.getResources(), ViewConfiguration.get(mContext),
-                new FalsingManagerFake(), mFeatureFlags, SwipeHelper.X, mCallback, mListener));
+                mContext.getResources(),
+                ViewConfiguration.get(mContext),
+                new FalsingManagerFake(),
+                mFeatureFlags,
+                SwipeHelper.X,
+                mCallback,
+                mListener,
+                mNotificationRoundnessManager));
         mView = mock(View.class);
         mEvent = mock(MotionEvent.class);
         mMenuRow = mock(NotificationMenuRowPlugin.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
index a2e9230..81a3f12 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationTargetsHelperTest.kt
@@ -35,7 +35,7 @@
     ) =
         NotificationTargetsHelper(
             FakeFeatureFlags().apply {
-                set(Flags.NOTIFICATION_GROUP_CORNER, notificationGroupCorner)
+                set(Flags.USE_ROUNDNESS_SOURCETYPES, notificationGroupCorner)
             }
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 038af8f..6155e3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mockito.doAnswer
@@ -295,6 +296,7 @@
     }
 
     @Test
+    @Ignore("b/261408895")
     fun equivalentConfigObject_listenerNotNotified() {
         val config = mContext.resources.configuration
         val listener = createAndAddListener()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 103b7b42..9727b6c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -32,6 +32,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.NotificationPanelViewController;
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -71,6 +73,8 @@
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
     private CommandQueue mCommandQueue;
+    private NotificationRoundnessManager mNotificationRoundnessManager;
+    private FeatureFlags mFeatureFlag;
 
     @Before
     public void setUp() throws Exception {
@@ -89,6 +93,8 @@
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
         mCommandQueue = mock(CommandQueue.class);
+        mNotificationRoundnessManager = mock(NotificationRoundnessManager.class);
+        mFeatureFlag = mock(FeatureFlags.class);
         mHeadsUpAppearanceController = new HeadsUpAppearanceController(
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
@@ -100,6 +106,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.of(mOperatorNameView));
@@ -182,6 +190,8 @@
                 mCommandQueue,
                 mStackScrollerController,
                 mPanelView,
+                mNotificationRoundnessManager,
+                mFeatureFlag,
                 mHeadsUpStatusBarView,
                 new Clock(mContext, null),
                 Optional.empty());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
new file mode 100644
index 0000000..7eba3b46
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImplTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.statusbar.phone
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class ManagedProfileControllerImplTest : SysuiTestCase() {
+
+    private val mainExecutor: FakeExecutor = FakeExecutor(FakeSystemClock())
+
+    private lateinit var controller: ManagedProfileControllerImpl
+
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userManager: UserManager
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        controller = ManagedProfileControllerImpl(context, mainExecutor, userTracker, userManager)
+    }
+
+    @Test
+    fun hasWorkingProfile_isWorkModeEnabled_returnsTrue() {
+        `when`(userTracker.userId).thenReturn(1)
+        setupWorkingProfile(1)
+
+        Assert.assertEquals(true, controller.hasActiveProfile())
+    }
+
+    @Test
+    fun noWorkingProfile_isWorkModeEnabled_returnsFalse() {
+        `when`(userTracker.userId).thenReturn(1)
+
+        Assert.assertEquals(false, controller.hasActiveProfile())
+    }
+
+    @Test
+    fun listeningUserChanges_isWorkModeEnabled_returnsTrue() {
+        `when`(userTracker.userId).thenReturn(1)
+        controller.addCallback(TestCallback)
+        `when`(userTracker.userId).thenReturn(2)
+        setupWorkingProfile(2)
+
+        Assert.assertEquals(true, controller.hasActiveProfile())
+    }
+
+    private fun setupWorkingProfile(userId: Int) {
+        `when`(userManager.getEnabledProfiles(userId))
+            .thenReturn(
+                listOf(UserInfo(userId, "test_user", "", 0, UserManager.USER_TYPE_PROFILE_MANAGED))
+            )
+    }
+
+    private object TestCallback : ManagedProfileController.Callback {
+
+        override fun onManagedProfileChanged() = Unit
+
+        override fun onManagedProfileRemoved() = Unit
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 471f8d3..14a319b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.systemui.statusbar.phone;
@@ -105,7 +105,6 @@
     @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
     @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
     @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
-    @Mock private KeyguardBouncer mPrimaryBouncer;
     @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
     @Mock private KeyguardMessageArea mKeyguardMessageArea;
     @Mock private ShadeController mShadeController;
@@ -133,16 +132,14 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        when(mKeyguardBouncerFactory.create(
-                any(ViewGroup.class),
-                any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
-                .thenReturn(mPrimaryBouncer);
         when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
         when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
         when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
                 .thenReturn(mKeyguardMessageAreaController);
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
 
+        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(true);
+
         mStatusBarKeyguardViewManager =
                 new StatusBarKeyguardViewManager(
                         getContext(),
@@ -184,7 +181,7 @@
         mStatusBarKeyguardViewManager.show(null);
         ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
                 ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
-        verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+        verify(mPrimaryBouncerCallbackInteractor).addBouncerExpansionCallback(
                 callbackArgumentCaptor.capture());
         mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
     }
@@ -195,87 +192,87 @@
         Runnable cancelAction = () -> {};
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, false /* afterKeyguardGone */);
-        verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+        verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction));
+        verify(mPrimaryBouncerInteractor).show(eq(true));
     }
 
     @Test
     public void showBouncer_onlyWhenShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mPrimaryBouncer, never()).show(anyBoolean());
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_notWhenBouncerAlreadyShowing() {
         mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
-        when(mPrimaryBouncer.isSecure()).thenReturn(true);
+        when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(
+                KeyguardSecurityModel.SecurityMode.Password);
         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
-        verify(mPrimaryBouncer, never()).show(anyBoolean());
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void showBouncer_showsTheBouncer() {
         mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
-        verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+        verify(mPrimaryBouncerInteractor).show(eq(true));
     }
 
     @Test
     public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
         when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
     public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
 
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
-        verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.6f));
     }
 
     @Test
     public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(0.5f));
 
-        reset(mPrimaryBouncer);
+        reset(mPrimaryBouncerInteractor);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(eq(0.5f));
     }
 
     @Test
     public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
         mStatusBarKeyguardViewManager.hide(0, 0);
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
 
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+        verify(mPrimaryBouncerInteractor).setPanelExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
     }
 
     @Test
     public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
         mKeyguardStateController.setCanDismissLockScreen(false);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer).show(eq(false), eq(false));
+        verify(mPrimaryBouncerInteractor).show(eq(false));
 
         // But not when it's already visible
-        reset(mPrimaryBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+        verify(mPrimaryBouncerInteractor, never()).show(eq(false));
 
         // Or animating away
-        reset(mPrimaryBouncer);
-        when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+        reset(mPrimaryBouncerInteractor);
+        when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
-        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+        verify(mPrimaryBouncerInteractor, never()).show(eq(false));
     }
 
     @Test
@@ -287,7 +284,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -304,7 +301,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -315,7 +312,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -332,7 +329,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -343,7 +340,7 @@
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
                         /* expanded= */ true,
                         /* tracking= */ false));
-        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+        verify(mPrimaryBouncerInteractor, never()).setPanelExpansion(anyFloat());
     }
 
     @Test
@@ -351,7 +348,7 @@
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces).animateKeyguardUnoccluding();
 
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         clearInvocations(mCentralSurfaces);
         mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
         verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
@@ -402,7 +399,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
         mStatusBarKeyguardViewManager.hide(0, 30);
         verify(action, never()).onDismiss();
@@ -416,7 +413,7 @@
         mStatusBarKeyguardViewManager.dismissWithAction(
                 action, cancelAction, true /* afterKeyguardGone */);
 
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         mStatusBarKeyguardViewManager.hideBouncer(true);
 
         verify(action, never()).onDismiss();
@@ -438,7 +435,7 @@
     @Test
     public void testShowing_whenAlternateAuthShowing() {
         mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is showing not accurate when alternative auth showing",
@@ -448,7 +445,7 @@
     @Test
     public void testWillBeShowing_whenAlternateAuthShowing() {
         mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         assertTrue(
                 "Is or will be showing not accurate when alternative auth showing",
@@ -459,7 +456,7 @@
     public void testHideAlternateBouncer_onShowBouncer() {
         // GIVEN alt auth is showing
         mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
         reset(mAlternateBouncer);
 
@@ -472,8 +469,8 @@
 
     @Test
     public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
 
         assertTrue(
                 "Is or will be showing should be true when bouncer is in transit",
@@ -484,7 +481,7 @@
     public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
         // GIVEN alt auth exists, unlocking with biometric isn't allowed
         mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
                 .thenReturn(false);
 
@@ -493,7 +490,7 @@
         mStatusBarKeyguardViewManager.showBouncer(scrimmed);
 
         // THEN regular bouncer is shown
-        verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+        verify(mPrimaryBouncerInteractor).show(eq(scrimmed));
         verify(mAlternateBouncer, never()).showAlternateBouncer();
     }
 
@@ -501,7 +498,7 @@
     public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
         // GIVEN alt auth exists, unlocking with biometric is allowed
         mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
-        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
 
         // WHEN showGenericBouncer is called
@@ -509,30 +506,28 @@
 
         // THEN alt auth bouncer is shown
         verify(mAlternateBouncer).showAlternateBouncer();
-        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncerInteractor, never()).show(anyBoolean());
     }
 
     @Test
     public void testUpdateResources_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateResources();
 
-        verify(mPrimaryBouncer).updateResources();
+        verify(mPrimaryBouncerInteractor).updateResources();
     }
 
     @Test
     public void updateKeyguardPosition_delegatesToBouncer() {
         mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
 
-        verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+        verify(mPrimaryBouncerInteractor).setKeyguardPosition(1.0f);
     }
 
     @Test
     public void testIsBouncerInTransit() {
-        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true);
         Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
-        when(mPrimaryBouncer.inTransit()).thenReturn(false);
-        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
-        mPrimaryBouncer = null;
+        when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false);
         Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
     }
 
@@ -564,7 +559,7 @@
                 eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
                 mOnBackInvokedCallback.capture());
 
-        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(true);
         when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
         /* invoke the back callback directly */
         mOnBackInvokedCallback.getValue().onBackInvoked();
@@ -594,13 +589,6 @@
     }
 
     @Test
-    public void flag_off_DoesNotCallBouncerInteractor() {
-        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
-        mStatusBarKeyguardViewManager.hideBouncer(false);
-        verify(mPrimaryBouncerInteractor, never()).hide();
-    }
-
-    @Test
     public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
         mStatusBarKeyguardViewManager =
                 new StatusBarKeyguardViewManager(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
new file mode 100644
index 0000000..96fba39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest_Old.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.phone;
+
+import static com.android.systemui.flags.Flags.MODERN_BOUNCER;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyFloat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewRootImpl;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
+import android.window.WindowOnBackInvokedDispatcher;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.LatencyTracker;
+import com.android.internal.widget.LockPatternUtils;
+import com.android.keyguard.KeyguardMessageArea;
+import com.android.keyguard.KeyguardMessageAreaController;
+import com.android.keyguard.KeyguardSecurityModel;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.ViewMediatorCallback;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dock.DockManager;
+import com.android.systemui.dreams.DreamOverlayStateController;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.keyguard.data.BouncerView;
+import com.android.systemui.keyguard.data.BouncerViewDelegate;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor;
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
+import com.android.systemui.navigationbar.NavigationModeController;
+import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationShadeWindowController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.unfold.SysUIUnfoldComponent;
+
+import com.google.common.truth.Truth;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * StatusBarKeyguardViewManager Test with deprecated KeyguardBouncer.java.
+ * TODO: Delete when deleting {@link KeyguardBouncer}
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class StatusBarKeyguardViewManagerTest_Old extends SysuiTestCase {
+    private static final ShadeExpansionChangeEvent EXPANSION_EVENT =
+            expansionEvent(/* fraction= */ 0.5f, /* expanded= */ false, /* tracking= */ true);
+
+    @Mock private ViewMediatorCallback mViewMediatorCallback;
+    @Mock private LockPatternUtils mLockPatternUtils;
+    @Mock private CentralSurfaces mCentralSurfaces;
+    @Mock private ViewGroup mContainer;
+    @Mock private NotificationPanelViewController mNotificationPanelView;
+    @Mock private BiometricUnlockController mBiometricUnlockController;
+    @Mock private SysuiStatusBarStateController mStatusBarStateController;
+    @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    @Mock private View mNotificationContainer;
+    @Mock private KeyguardBypassController mBypassController;
+    @Mock private KeyguardBouncer.Factory mKeyguardBouncerFactory;
+    @Mock private KeyguardMessageAreaController.Factory mKeyguardMessageAreaFactory;
+    @Mock private KeyguardMessageAreaController mKeyguardMessageAreaController;
+    @Mock private KeyguardBouncer mPrimaryBouncer;
+    @Mock private StatusBarKeyguardViewManager.AlternateBouncer mAlternateBouncer;
+    @Mock private KeyguardMessageArea mKeyguardMessageArea;
+    @Mock private ShadeController mShadeController;
+    @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
+    @Mock private DreamOverlayStateController mDreamOverlayStateController;
+    @Mock private LatencyTracker mLatencyTracker;
+    @Mock private FeatureFlags mFeatureFlags;
+    @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
+    @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
+    @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
+    @Mock private BouncerView mBouncerView;
+    @Mock private BouncerViewDelegate mBouncerViewDelegate;
+
+    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private KeyguardBouncer.PrimaryBouncerExpansionCallback mBouncerExpansionCallback;
+    private FakeKeyguardStateController mKeyguardStateController =
+            spy(new FakeKeyguardStateController());
+
+    @Mock private ViewRootImpl mViewRootImpl;
+    @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
+    @Captor
+    private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mKeyguardBouncerFactory.create(
+                any(ViewGroup.class),
+                any(KeyguardBouncer.PrimaryBouncerExpansionCallback.class)))
+                .thenReturn(mPrimaryBouncer);
+        when(mCentralSurfaces.getBouncerContainer()).thenReturn(mContainer);
+        when(mContainer.findViewById(anyInt())).thenReturn(mKeyguardMessageArea);
+        when(mKeyguardMessageAreaFactory.create(any(KeyguardMessageArea.class)))
+                .thenReturn(mKeyguardMessageAreaController);
+        when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
+
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+        when(mViewRootImpl.getOnBackInvokedDispatcher())
+                .thenReturn(mOnBackInvokedDispatcher);
+        mStatusBarKeyguardViewManager.registerCentralSurfaces(
+                mCentralSurfaces,
+                mNotificationPanelView,
+                new ShadeExpansionStateManager(),
+                mBiometricUnlockController,
+                mNotificationContainer,
+                mBypassController);
+        mStatusBarKeyguardViewManager.show(null);
+        ArgumentCaptor<KeyguardBouncer.PrimaryBouncerExpansionCallback> callbackArgumentCaptor =
+                ArgumentCaptor.forClass(KeyguardBouncer.PrimaryBouncerExpansionCallback.class);
+        verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+                callbackArgumentCaptor.capture());
+        mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
+    }
+
+    @Test
+    public void dismissWithAction_AfterKeyguardGoneSetToFalse() {
+        OnDismissAction action = () -> false;
+        Runnable cancelAction = () -> {};
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, false /* afterKeyguardGone */);
+        verify(mPrimaryBouncer).showWithDismissAction(eq(action), eq(cancelAction));
+    }
+
+    @Test
+    public void showBouncer_onlyWhenShowing() {
+        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
+    }
+
+    @Test
+    public void showBouncer_notWhenBouncerAlreadyShowing() {
+        mStatusBarKeyguardViewManager.hide(0 /* startTime */, 0 /* fadeoutDuration */);
+        when(mPrimaryBouncer.isSecure()).thenReturn(true);
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+        verify(mPrimaryBouncer, never()).show(anyBoolean());
+    }
+
+    @Test
+    public void showBouncer_showsTheBouncer() {
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true /* scrimmed */);
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(true));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverShowsDuringHintAnimation() {
+        when(mNotificationPanelView.isUnlockHintRunning()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_propagatesToBouncerOnlyIfShowing() {
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(/* fraction= */ 0.6f, /* expanded= */ false, /* tracking= */ true));
+        verify(mPrimaryBouncer).setExpansion(eq(0.6f));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_duplicateEventsAreIgnored() {
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).setExpansion(eq(0.5f));
+
+        reset(mPrimaryBouncer);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).setExpansion(eq(0.5f));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_hideBouncer_afterKeyguardHidden() {
+        mStatusBarKeyguardViewManager.hide(0, 0);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).setExpansion(eq(KeyguardBouncer.EXPANSION_HIDDEN));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_showsBouncerWhenSwiping() {
+        mKeyguardStateController.setCanDismissLockScreen(false);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer).show(eq(false), eq(false));
+
+        // But not when it's already visible
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+
+        // Or animating away
+        reset(mPrimaryBouncer);
+        when(mPrimaryBouncer.isAnimatingAway()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(EXPANSION_EVENT);
+        verify(mPrimaryBouncer, never()).show(eq(false), eq(false));
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenWakeAndUnlock() {
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenDismissBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_DISMISS_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenOccluded() {
+        when(mKeyguardStateController.isOccluded()).thenReturn(true);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShowBouncer() {
+        // Since KeyguardBouncer.EXPANSION_VISIBLE = 0 panel expansion, if the unlock is dismissing
+        // the bouncer, there may be an onPanelExpansionChanged(0) call to collapse the panel
+        // which would mistakenly cause the bouncer to show briefly before its visibility
+        // is set to hide. Therefore, we don't want to propagate panelExpansionChanged to the
+        // bouncer if the bouncer is dismissing as a result of a biometric unlock.
+        when(mBiometricUnlockController.getMode())
+                .thenReturn(BiometricUnlockController.MODE_SHOW_BOUNCER);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void onPanelExpansionChanged_neverTranslatesBouncerWhenShadeLocked() {
+        when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED);
+        mStatusBarKeyguardViewManager.onPanelExpansionChanged(
+                expansionEvent(
+                        /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
+                        /* expanded= */ true,
+                        /* tracking= */ false));
+        verify(mPrimaryBouncer, never()).setExpansion(anyFloat());
+    }
+
+    @Test
+    public void setOccluded_animatesPanelExpansion_onlyIfBouncerHidden() {
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+        verify(mCentralSurfaces).animateKeyguardUnoccluding();
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        clearInvocations(mCentralSurfaces);
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, true /* animated */);
+        verify(mCentralSurfaces, never()).animateKeyguardUnoccluding();
+    }
+
+    @Test
+    public void setOccluded_onKeyguardOccludedChangedCalled() {
+        clearInvocations(mKeyguardStateController);
+        clearInvocations(mKeyguardUpdateMonitor);
+
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
+
+        clearInvocations(mKeyguardUpdateMonitor);
+        clearInvocations(mKeyguardStateController);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+
+        clearInvocations(mKeyguardUpdateMonitor);
+        clearInvocations(mKeyguardStateController);
+
+        mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, false);
+    }
+
+    @Test
+    public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
+        mStatusBarKeyguardViewManager.show(null);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+    }
+
+    @Test
+    public void setOccluded_isLaunchingActivityOverLockscreen_onKeyguardOccludedChangedCalled() {
+        when(mCentralSurfaces.isLaunchingActivityOverLockscreen()).thenReturn(true);
+        mStatusBarKeyguardViewManager.show(null);
+
+        mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
+        verify(mKeyguardStateController).notifyKeyguardState(true, true);
+    }
+
+    @Test
+    public void testHiding_cancelsGoneRunnable() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(true);
+        mStatusBarKeyguardViewManager.hide(0, 30);
+        verify(action, never()).onDismiss();
+        verify(cancelAction).run();
+    }
+
+    @Test
+    public void testHidingBouncer_cancelsGoneRunnable() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(true);
+
+        verify(action, never()).onDismiss();
+        verify(cancelAction).run();
+    }
+
+    @Test
+    public void testHiding_doesntCancelWhenShowing() {
+        OnDismissAction action = mock(OnDismissAction.class);
+        Runnable cancelAction = mock(Runnable.class);
+        mStatusBarKeyguardViewManager.dismissWithAction(
+                action, cancelAction, true /* afterKeyguardGone */);
+
+        mStatusBarKeyguardViewManager.hide(0, 30);
+        verify(action).onDismiss();
+        verify(cancelAction, never()).run();
+    }
+
+    @Test
+    public void testShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        assertTrue(
+                "Is showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.isBouncerShowing());
+    }
+
+    @Test
+    public void testWillBeShowing_whenAlternateAuthShowing() {
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        assertTrue(
+                "Is or will be showing not accurate when alternative auth showing",
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+    }
+
+    @Test
+    public void testHideAlternateBouncer_onShowBouncer() {
+        // GIVEN alt auth is showing
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mAlternateBouncer.isShowingAlternateBouncer()).thenReturn(true);
+        reset(mAlternateBouncer);
+
+        // WHEN showBouncer is called
+        mStatusBarKeyguardViewManager.showPrimaryBouncer(true);
+
+        // THEN alt bouncer should be hidden
+        verify(mAlternateBouncer).hideAlternateBouncer();
+    }
+
+    @Test
+    public void testBouncerIsOrWillBeShowing_whenBouncerIsInTransit() {
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+
+        assertTrue(
+                "Is or will be showing should be true when bouncer is in transit",
+                mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing());
+    }
+
+    @Test
+    public void testShowAltAuth_unlockingWithBiometricNotAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric isn't allowed
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
+                .thenReturn(false);
+
+        // WHEN showGenericBouncer is called
+        final boolean scrimmed = true;
+        mStatusBarKeyguardViewManager.showBouncer(scrimmed);
+
+        // THEN regular bouncer is shown
+        verify(mPrimaryBouncer).show(anyBoolean(), eq(scrimmed));
+        verify(mAlternateBouncer, never()).showAlternateBouncer();
+    }
+
+    @Test
+    public void testShowAlternateBouncer_unlockingWithBiometricAllowed() {
+        // GIVEN alt auth exists, unlocking with biometric is allowed
+        mStatusBarKeyguardViewManager.setAlternateBouncer(mAlternateBouncer);
+        when(mPrimaryBouncer.isShowing()).thenReturn(false);
+        when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
+
+        // WHEN showGenericBouncer is called
+        mStatusBarKeyguardViewManager.showBouncer(true);
+
+        // THEN alt auth bouncer is shown
+        verify(mAlternateBouncer).showAlternateBouncer();
+        verify(mPrimaryBouncer, never()).show(anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void testUpdateResources_delegatesToBouncer() {
+        mStatusBarKeyguardViewManager.updateResources();
+
+        verify(mPrimaryBouncer).updateResources();
+    }
+
+    @Test
+    public void updateKeyguardPosition_delegatesToBouncer() {
+        mStatusBarKeyguardViewManager.updateKeyguardPosition(1.0f);
+
+        verify(mPrimaryBouncer).updateKeyguardPosition(1.0f);
+    }
+
+    @Test
+    public void testIsBouncerInTransit() {
+        when(mPrimaryBouncer.inTransit()).thenReturn(true);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isTrue();
+        when(mPrimaryBouncer.inTransit()).thenReturn(false);
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+        mPrimaryBouncer = null;
+        Truth.assertThat(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).isFalse();
+    }
+
+    private static ShadeExpansionChangeEvent expansionEvent(
+            float fraction, boolean expanded, boolean tracking) {
+        return new ShadeExpansionChangeEvent(
+                fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
+    }
+
+    @Test
+    public void testPredictiveBackCallback_registration() {
+        /* verify that a predictive back callback is registered when the bouncer becomes visible */
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mOnBackInvokedCallback.capture());
+
+        /* verify that the same callback is unregistered when the bouncer becomes invisible */
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(
+                eq(mOnBackInvokedCallback.getValue()));
+    }
+
+    @Test
+    public void testPredictiveBackCallback_invocationHidesBouncer() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        /* capture the predictive back callback during registration */
+        verify(mOnBackInvokedDispatcher).registerOnBackInvokedCallback(
+                eq(OnBackInvokedDispatcher.PRIORITY_OVERLAY),
+                mOnBackInvokedCallback.capture());
+
+        when(mPrimaryBouncer.isShowing()).thenReturn(true);
+        when(mCentralSurfaces.shouldKeyguardHideImmediately()).thenReturn(true);
+        /* invoke the back callback directly */
+        mOnBackInvokedCallback.getValue().onBackInvoked();
+
+        /* verify that the bouncer will be hidden as a result of the invocation */
+        verify(mCentralSurfaces).setBouncerShowing(eq(false));
+    }
+
+    @Test
+    public void testReportBouncerOnDreamWhenVisible() {
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        Mockito.clearInvocations(mCentralSurfaces);
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+    }
+
+    @Test
+    public void testReportBouncerOnDreamWhenNotVisible() {
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+        Mockito.clearInvocations(mCentralSurfaces);
+        when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(false);
+        verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+    }
+
+    @Test
+    public void flag_off_DoesNotCallBouncerInteractor() {
+        when(mFeatureFlags.isEnabled(MODERN_BOUNCER)).thenReturn(false);
+        mStatusBarKeyguardViewManager.hideBouncer(false);
+        verify(mPrimaryBouncerInteractor, never()).hide();
+    }
+
+    @Test
+    public void hideAlternateBouncer_beforeCentralSurfacesRegistered() {
+        mStatusBarKeyguardViewManager =
+                new StatusBarKeyguardViewManager(
+                        getContext(),
+                        mViewMediatorCallback,
+                        mLockPatternUtils,
+                        mStatusBarStateController,
+                        mock(ConfigurationController.class),
+                        mKeyguardUpdateMonitor,
+                        mDreamOverlayStateController,
+                        mock(NavigationModeController.class),
+                        mock(DockManager.class),
+                        mock(NotificationShadeWindowController.class),
+                        mKeyguardStateController,
+                        mock(NotificationMediaManager.class),
+                        mKeyguardBouncerFactory,
+                        mKeyguardMessageAreaFactory,
+                        Optional.of(mSysUiUnfoldComponent),
+                        () -> mShadeController,
+                        mLatencyTracker,
+                        mKeyguardSecurityModel,
+                        mFeatureFlags,
+                        mPrimaryBouncerCallbackInteractor,
+                        mPrimaryBouncerInteractor,
+                        mBouncerView) {
+                    @Override
+                    public ViewRootImpl getViewRootImpl() {
+                        return mViewRootImpl;
+                    }
+                };
+
+        // the following call before registering centralSurfaces should NOT throw a NPE:
+        mStatusBarKeyguardViewManager.hideAlternateBouncer(true);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 288f54c..5265ec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -16,12 +16,13 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileConnectionRepository : MobileConnectionRepository {
-    private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
-    override val subscriptionModelFlow = _subscriptionsModelFlow
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
+class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+    private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
+    override val connectionInfo = _connectionInfo
 
     private val _dataEnabled = MutableStateFlow(true)
     override val dataEnabled = _dataEnabled
@@ -29,8 +30,8 @@
     private val _isDefaultDataSubscription = MutableStateFlow(true)
     override val isDefaultDataSubscription = _isDefaultDataSubscription
 
-    fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
-        _subscriptionsModelFlow.value = model
+    fun setConnectionInfo(model: MobileConnectionModel) {
+        _connectionInfo.value = model
     }
 
     fun setDataEnabled(enabled: Boolean) {
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 533d5d9..d6af0e6 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
@@ -16,23 +16,43 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import com.android.settingslib.mobile.MobileMappings.Config
+import android.telephony.TelephonyDisplayInfo
+import android.telephony.TelephonyManager
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileConnectionsRepository : MobileConnectionsRepository {
-    private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
-    override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
+// TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
+class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
+    MobileConnectionsRepository {
+    val GSM_KEY = mobileMappings.toIconKey(GSM)
+    val LTE_KEY = mobileMappings.toIconKey(LTE)
+    val UMTS_KEY = mobileMappings.toIconKey(UMTS)
+    val LTE_ADVANCED_KEY = mobileMappings.toIconKeyOverride(LTE_ADVANCED_PRO)
+
+    /**
+     * To avoid a reliance on [MobileMappings], we'll build a simpler map from network type to
+     * mobile icon. See TelephonyManager.NETWORK_TYPES for a list of types and [TelephonyIcons] for
+     * the exhaustive set of icons
+     */
+    val TEST_MAPPING: Map<String, SignalIcon.MobileIconGroup> =
+        mapOf(
+            GSM_KEY to TelephonyIcons.THREE_G,
+            LTE_KEY to TelephonyIcons.LTE,
+            UMTS_KEY to TelephonyIcons.FOUR_G,
+            LTE_ADVANCED_KEY to TelephonyIcons.NR_5G,
+        )
+
+    private val _subscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+    override val subscriptions = _subscriptions
 
     private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
     override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
 
-    private val _defaultDataSubRatConfig = MutableStateFlow(Config())
-    override val defaultDataSubRatConfig = _defaultDataSubRatConfig
-
     private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
     override val defaultDataSubId = _defaultDataSubId
 
@@ -41,18 +61,21 @@
 
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
-        return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
+        return subIdRepos[subId]
+            ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
     }
 
     private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
     override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
 
-    fun setSubscriptions(subs: List<SubscriptionInfo>) {
-        _subscriptionsFlow.value = subs
-    }
+    private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
+    override val defaultMobileIconMapping = _defaultMobileIconMapping
 
-    fun setDefaultDataSubRatConfig(config: Config) {
-        _defaultDataSubRatConfig.value = config
+    private val _defaultMobileIconGroup = MutableStateFlow(DEFAULT_ICON)
+    override val defaultMobileIconGroup = _defaultMobileIconGroup
+
+    fun setSubscriptions(subs: List<SubscriptionModel>) {
+        _subscriptions.value = subs
     }
 
     fun setDefaultDataSubId(id: Int) {
@@ -74,4 +97,14 @@
     fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
         connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
     }
+
+    companion object {
+        val DEFAULT_ICON = TelephonyIcons.G
+
+        // Use [MobileMappings] to define some simple definitions
+        const val GSM = TelephonyManager.NETWORK_TYPE_GSM
+        const val LTE = TelephonyManager.NETWORK_TYPE_LTE
+        const val UMTS = TelephonyManager.NETWORK_TYPE_UMTS
+        const val LTE_ADVANCED_PRO = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
+    }
 }
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
new file mode 100644
index 0000000..18ae90d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository
+
+import android.net.ConnectivityManager
+import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.validMobileEvent
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileConnectionsRepositoryImpl
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * The switcher acts as a dispatcher to either the `prod` or `demo` versions of the repository
+ * interface it's switching on. These tests just need to verify that the entire interface properly
+ * switches over when the value of `demoMode` changes
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class MobileRepositorySwitcherTest : SysuiTestCase() {
+    private lateinit var underTest: MobileRepositorySwitcher
+    private lateinit var realRepo: MobileConnectionsRepositoryImpl
+    private lateinit var demoRepo: DemoMobileConnectionsRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @Mock private lateinit var connectivityManager: ConnectivityManager
+    @Mock private lateinit var subscriptionManager: SubscriptionManager
+    @Mock private lateinit var telephonyManager: TelephonyManager
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var demoModeController: DemoModeController
+
+    private val globalSettings = FakeSettings()
+    private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+    private val mobileMappings = FakeMobileMappingsProxy()
+
+    private val scope = CoroutineScope(IMMEDIATE)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        // Never start in demo mode
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventsFlow)
+            }
+
+        realRepo =
+            MobileConnectionsRepositoryImpl(
+                connectivityManager,
+                subscriptionManager,
+                telephonyManager,
+                logger,
+                mobileMappings,
+                fakeBroadcastDispatcher,
+                globalSettings,
+                context,
+                IMMEDIATE,
+                scope,
+                mock(),
+            )
+
+        demoRepo =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = scope,
+                context = context,
+            )
+
+        underTest =
+            MobileRepositorySwitcher(
+                scope = scope,
+                realRepository = realRepo,
+                demoMobileConnectionsRepository = demoRepo,
+                demoModeController = demoModeController,
+            )
+    }
+
+    @After
+    fun tearDown() {
+        scope.cancel()
+    }
+
+    @Test
+    fun `active repo matches demo mode setting`() =
+        runBlocking(IMMEDIATE) {
+            whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+            var latest: MobileConnectionsRepository? = null
+            val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(realRepo)
+
+            startDemoMode()
+
+            assertThat(latest).isEqualTo(demoRepo)
+
+            finishDemoMode()
+
+            assertThat(latest).isEqualTo(realRepo)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `subscription list updates when demo mode changes`() =
+        runBlocking(IMMEDIATE) {
+            whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            // The real subscriptions has 2 subs
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+            // Demo mode turns on, and we should see only the demo subscriptions
+            startDemoMode()
+            fakeNetworkEventsFlow.value = validMobileEvent(subId = 3)
+
+            // Demo mobile connections repository makes arbitrarily-formed subscription info
+            // objects, so just validate the data we care about
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(3)
+
+            finishDemoMode()
+
+            assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
+
+            job.cancel()
+        }
+
+    private fun startDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(true)
+        getDemoModeCallback().onDemoModeStarted()
+    }
+
+    private fun finishDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+        getDemoModeCallback().onDemoModeFinished()
+    }
+
+    private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
+        val callbackCaptor =
+            kotlinArgumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
+        verify(subscriptionManager)
+            .addOnSubscriptionsChangedListener(any(), callbackCaptor.capture())
+        return callbackCaptor.value
+    }
+
+    private fun getDemoModeCallback(): DemoMode {
+        val captor = kotlinArgumentCaptor<DemoMode>()
+        verify(demoModeController).addCallback(captor.capture())
+        return captor.value
+    }
+
+    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 MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+
+        private const val SUB_2_ID = 2
+        private val SUB_2 =
+            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+        private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+    }
+}
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
new file mode 100644
index 0000000..e943de2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -0,0 +1,249 @@
+/*
+ * 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.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Parameterized.Parameters
+
+/**
+ * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
+ * verifies that passing the given model to [DemoMobileConnectionsRepository] results in the correct
+ * flows emitting from the given connection.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(Parameterized::class)
+internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
+    SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+    private lateinit var connectionsRepo: DemoMobileConnectionsRepository
+    private lateinit var underTest: DemoMobileConnectionRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @Before
+    fun setUp() {
+        // The data source only provides one API, so we can mock it with a flow here for convenience
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+            }
+
+        connectionsRepo =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = testScope.backgroundScope,
+                context = context,
+            )
+
+        connectionsRepo.startProcessingCommands()
+    }
+
+    @After
+    fun tearDown() {
+        testScope.cancel()
+    }
+
+    @Test
+    fun demoNetworkData() =
+        testScope.runTest {
+            val networkModel =
+                FakeNetworkEventModel.Mobile(
+                    level = testCase.level,
+                    dataType = testCase.dataType,
+                    subId = testCase.subId,
+                    carrierId = testCase.carrierId,
+                    inflateStrength = testCase.inflateStrength,
+                    activity = testCase.activity,
+                    carrierNetworkChange = testCase.carrierNetworkChange,
+                )
+
+            fakeNetworkEventFlow.value = networkModel
+            underTest = connectionsRepo.getRepoForSubId(subId)
+
+            assertConnection(underTest, networkModel)
+        }
+
+    private fun assertConnection(
+        conn: DemoMobileConnectionRepository,
+        model: FakeNetworkEventModel
+    ) {
+        when (model) {
+            is FakeNetworkEventModel.Mobile -> {
+                val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+                assertThat(conn.subId).isEqualTo(model.subId)
+                assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
+                assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
+                assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+                assertThat(connectionInfo.carrierNetworkChangeActive)
+                    .isEqualTo(model.carrierNetworkChange)
+
+                // TODO(b/261029387): check these once we start handling them
+                assertThat(connectionInfo.isEmergencyOnly).isFalse()
+                assertThat(connectionInfo.isGsm).isFalse()
+                assertThat(connectionInfo.dataConnectionState)
+                    .isEqualTo(DataConnectionState.Connected)
+            }
+            // MobileDisabled isn't combinatorial in nature, and is tested in
+            // DemoMobileConnectionsRepositoryTest.kt
+            else -> {}
+        }
+    }
+
+    /** Matches [FakeNetworkEventModel] */
+    internal data class TestCase(
+        val level: Int,
+        val dataType: SignalIcon.MobileIconGroup,
+        val subId: Int,
+        val carrierId: Int,
+        val inflateStrength: Boolean,
+        @Annotation.DataActivityType val activity: Int,
+        val carrierNetworkChange: Boolean,
+    ) {
+        override fun toString(): String {
+            return "INPUT(level=$level, " +
+                "dataType=${dataType.name}, " +
+                "subId=$subId, " +
+                "carrierId=$carrierId, " +
+                "inflateStrength=$inflateStrength, " +
+                "activity=$activity, " +
+                "carrierNetworkChange=$carrierNetworkChange)"
+        }
+
+        // Convenience for iterating test data and creating new cases
+        fun modifiedBy(
+            level: Int? = null,
+            dataType: SignalIcon.MobileIconGroup? = null,
+            subId: Int? = null,
+            carrierId: Int? = null,
+            inflateStrength: Boolean? = null,
+            @Annotation.DataActivityType activity: Int? = null,
+            carrierNetworkChange: Boolean? = null,
+        ): TestCase =
+            TestCase(
+                level = level ?: this.level,
+                dataType = dataType ?: this.dataType,
+                subId = subId ?: this.subId,
+                carrierId = carrierId ?: this.carrierId,
+                inflateStrength = inflateStrength ?: this.inflateStrength,
+                activity = activity ?: this.activity,
+                carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+            )
+    }
+
+    companion object {
+        private val subId = 1
+
+        private val booleanList = listOf(true, false)
+        private val levels = listOf(0, 1, 2, 3)
+        private val dataTypes =
+            listOf(
+                TelephonyIcons.THREE_G,
+                TelephonyIcons.LTE,
+                TelephonyIcons.FOUR_G,
+                TelephonyIcons.NR_5G,
+                TelephonyIcons.NR_5G_PLUS,
+            )
+        private val carrierIds = listOf(1, 10, 100)
+        private val inflateStrength = booleanList
+        private val activity =
+            listOf(
+                TelephonyManager.DATA_ACTIVITY_NONE,
+                TelephonyManager.DATA_ACTIVITY_IN,
+                TelephonyManager.DATA_ACTIVITY_OUT,
+                TelephonyManager.DATA_ACTIVITY_INOUT
+            )
+        private val carrierNetworkChange = booleanList
+
+        @Parameters(name = "{0}") @JvmStatic fun data() = testData()
+
+        /**
+         * Generate some test data. For the sake of convenience, we'll parameterize only non-null
+         * network event data. So given the lists of test data:
+         * ```
+         *    list1 = [1, 2, 3]
+         *    list2 = [false, true]
+         *    list3 = [a, b, c]
+         * ```
+         * We'll generate test cases for:
+         *
+         * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
+         * false, b) Test (1, false, c)
+         *
+         * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
+         * Since this test is built to exercise demo mode, the general approach is to define a
+         * fully-formed "base case", and from there to make sure to use every valid parameter once,
+         * by defining the rest of the test cases against the base case. Specific use-cases can be
+         * added to the non-parameterized test, or manually below the generated test cases.
+         */
+        private fun testData(): List<TestCase> {
+            val testSet = mutableSetOf<TestCase>()
+
+            val baseCase =
+                TestCase(
+                    levels.first(),
+                    dataTypes.first(),
+                    subId,
+                    carrierIds.first(),
+                    inflateStrength.first(),
+                    activity.first(),
+                    carrierNetworkChange.first()
+                )
+
+            val tail =
+                sequenceOf(
+                        levels.map { baseCase.modifiedBy(level = it) },
+                        dataTypes.map { baseCase.modifiedBy(dataType = it) },
+                        carrierIds.map { baseCase.modifiedBy(carrierId = it) },
+                        inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
+                        activity.map { baseCase.modifiedBy(activity = it) },
+                        carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+                    )
+                    .flatten()
+
+            testSet.add(baseCase)
+            tail.toCollection(testSet)
+
+            return testSet.toList()
+        }
+    }
+}
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
new file mode 100644
index 0000000..32d0410
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
+
+import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.UNKNOWN_CARRIER_ID
+import androidx.test.filters.SmallTest
+import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
+
+    private lateinit var underTest: DemoMobileConnectionsRepository
+    private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+
+    @Before
+    fun setUp() {
+        // The data source only provides one API, so we can mock it with a flow here for convenience
+        mockDataSource =
+            mock<DemoModeMobileConnectionDataSource>().also {
+                whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
+            }
+
+        underTest =
+            DemoMobileConnectionsRepository(
+                dataSource = mockDataSource,
+                scope = testScope.backgroundScope,
+                context = context,
+            )
+
+        underTest.startProcessingCommands()
+    }
+
+    @Test
+    fun `network event - create new subscription`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network event - reuses subscription when same Id`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEmpty()
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            // Second network event comes in with the same subId, does not create a new subscription
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 2)
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0].subscriptionId).isEqualTo(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `multiple subscriptions`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2)
+
+            assertThat(latest).hasSize(2)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId specified - single conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = 1)
+
+            assertThat(latest).hasSize(0)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId not specified - single conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+            assertThat(latest).hasSize(0)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - disables connection - subId specified - multiple conn`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = 2)
+
+            assertThat(latest).hasSize(1)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `mobile disabled event - subId not specified - multiple conn - ignores command`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+            fakeNetworkEventFlow.value = MobileDisabled(subId = null)
+
+            assertThat(latest).hasSize(2)
+
+            job.cancel()
+        }
+
+    /** Regression test for b/261706421 */
+    @Test
+    fun `multiple connections - remove all - does not throw`() =
+        testScope.runTest {
+            var latest: List<SubscriptionModel>? = null
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
+
+            // Two subscriptions are added
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 1, level = 1)
+            fakeNetworkEventFlow.value = validMobileEvent(subId = 2, level = 1)
+
+            // Then both are removed by turning off demo mode
+            underTest.stopProcessingCommands()
+
+            assertThat(latest).isEmpty()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `demo connection - single subscription`() =
+        testScope.runTest {
+            var currentEvent: FakeNetworkEventModel = validMobileEvent(subId = 1)
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptions
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = currentEvent
+
+            assertThat(connections).hasSize(1)
+            val connection1 = connections!![0]
+
+            assertConnection(connection1, currentEvent)
+
+            // Exercise the whole api
+
+            currentEvent = validMobileEvent(subId = 1, level = 2)
+            fakeNetworkEventFlow.value = currentEvent
+            assertConnection(connection1, currentEvent)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `demo connection - two connections - update second - no affect on first`() =
+        testScope.runTest {
+            var currentEvent1 = validMobileEvent(subId = 1)
+            var connection1: DemoMobileConnectionRepository? = null
+            var currentEvent2 = validMobileEvent(subId = 2)
+            var connection2: DemoMobileConnectionRepository? = null
+            var connections: List<DemoMobileConnectionRepository>? = null
+            val job =
+                underTest.subscriptions
+                    .onEach { infos ->
+                        connections =
+                            infos.map { info -> underTest.getRepoForSubId(info.subscriptionId) }
+                    }
+                    .launchIn(this)
+
+            fakeNetworkEventFlow.value = currentEvent1
+            fakeNetworkEventFlow.value = currentEvent2
+            assertThat(connections).hasSize(2)
+            connections!!.forEach {
+                if (it.subId == 1) {
+                    connection1 = it
+                } else if (it.subId == 2) {
+                    connection2 = it
+                } else {
+                    Assert.fail("Unexpected subscription")
+                }
+            }
+
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            // WHEN the event changes for connection 2, it updates, and connection 1 stays the same
+            currentEvent2 = validMobileEvent(subId = 2, activity = DATA_ACTIVITY_INOUT)
+            fakeNetworkEventFlow.value = currentEvent2
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            // and vice versa
+            currentEvent1 = validMobileEvent(subId = 1, inflateStrength = true)
+            fakeNetworkEventFlow.value = currentEvent1
+            assertConnection(connection1!!, currentEvent1)
+            assertConnection(connection2!!, currentEvent2)
+
+            job.cancel()
+        }
+
+    private fun assertConnection(
+        conn: DemoMobileConnectionRepository,
+        model: FakeNetworkEventModel
+    ) {
+        when (model) {
+            is FakeNetworkEventModel.Mobile -> {
+                val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+                assertThat(conn.subId).isEqualTo(model.subId)
+                assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
+                assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
+                assertThat(connectionInfo.dataActivityDirection).isEqualTo(model.activity)
+                assertThat(connectionInfo.carrierNetworkChangeActive)
+                    .isEqualTo(model.carrierNetworkChange)
+
+                // TODO(b/261029387) check these once we start handling them
+                assertThat(connectionInfo.isEmergencyOnly).isFalse()
+                assertThat(connectionInfo.isGsm).isFalse()
+                assertThat(connectionInfo.dataConnectionState)
+                    .isEqualTo(DataConnectionState.Connected)
+            }
+            else -> {}
+        }
+    }
+}
+
+/** Convenience to create a valid fake network event with minimal params */
+fun validMobileEvent(
+    level: Int? = 1,
+    dataType: SignalIcon.MobileIconGroup? = THREE_G,
+    subId: Int? = 1,
+    carrierId: Int? = UNKNOWN_CARRIER_ID,
+    inflateStrength: Boolean? = false,
+    activity: Int? = null,
+    carrierNetworkChange: Boolean = false,
+): FakeNetworkEventModel =
+    FakeNetworkEventModel.Mobile(
+        level = level,
+        dataType = dataType,
+        subId = subId,
+        carrierId = carrierId,
+        inflateStrength = inflateStrength,
+        activity = activity,
+        carrierNetworkChange = carrierNetworkChange,
+    )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
similarity index 79%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 5ce51bb..1fc9c60 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
 import android.os.UserHandle
 import android.provider.Settings
@@ -31,14 +31,18 @@
 import android.telephony.TelephonyManager.DATA_CONNECTING
 import android.telephony.TelephonyManager.DATA_DISCONNECTED
 import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -70,8 +74,9 @@
     @Mock private lateinit var logger: ConnectivityPipelineLogger
 
     private val scope = CoroutineScope(IMMEDIATE)
+    private val mobileMappings = FakeMobileMappingsProxy()
     private val globalSettings = FakeSettings()
-    private val connectionsRepo = FakeMobileConnectionsRepository()
+    private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
 
     @Before
     fun setUp() {
@@ -87,6 +92,7 @@
                 globalSettings,
                 connectionsRepo.defaultDataSubId,
                 connectionsRepo.globalMobileDataSettingChangedEvent,
+                mobileMappings,
                 IMMEDIATE,
                 logger,
                 scope,
@@ -101,10 +107,10 @@
     @Test
     fun testFlowForSubId_default() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
-            assertThat(latest).isEqualTo(MobileSubscriptionModel())
+            assertThat(latest).isEqualTo(MobileConnectionModel())
 
             job.cancel()
         }
@@ -112,8 +118,8 @@
     @Test
     fun testFlowForSubId_emergencyOnly() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.isEmergencyOnly = true
@@ -128,8 +134,8 @@
     @Test
     fun testFlowForSubId_emergencyOnly_toggles() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<ServiceStateListener>()
             val serviceState = ServiceState()
@@ -146,8 +152,8 @@
     @Test
     fun testFlowForSubId_signalStrengths_levelsUpdate() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
             val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
@@ -163,8 +169,8 @@
     @Test
     fun testFlowForSubId_dataConnectionState_connected() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -178,8 +184,8 @@
     @Test
     fun testFlowForSubId_dataConnectionState_connecting() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -193,8 +199,8 @@
     @Test
     fun testFlowForSubId_dataConnectionState_disconnected() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -208,8 +214,8 @@
     @Test
     fun testFlowForSubId_dataConnectionState_disconnecting() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
@@ -221,10 +227,25 @@
         }
 
     @Test
+    fun testFlowForSubId_dataConnectionState_unknown() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+
+            job.cancel()
+        }
+
+    @Test
     fun testFlowForSubId_dataActivity() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
             callback.onDataActivity(3)
@@ -237,8 +258,8 @@
     @Test
     fun testFlowForSubId_carrierNetworkChange() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
             callback.onCarrierNetworkChange(true)
@@ -251,11 +272,11 @@
     @Test
     fun subscriptionFlow_networkType_default() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val type = NETWORK_TYPE_UNKNOWN
-            val expected = DefaultNetworkType(type)
+            val expected = UnknownNetworkType
 
             assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
 
@@ -265,12 +286,12 @@
     @Test
     fun subscriptionFlow_networkType_updatesUsingDefault() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = NETWORK_TYPE_LTE
-            val expected = DefaultNetworkType(type)
+            val expected = DefaultNetworkType(type, mobileMappings.toIconKey(type))
             val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
             callback.onDisplayInfoChanged(ti)
 
@@ -282,14 +303,15 @@
     @Test
     fun subscriptionFlow_networkType_updatesUsingOverride() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileSubscriptionModel? = null
-            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+            var latest: MobileConnectionModel? = null
+            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
-            val expected = OverrideNetworkType(type)
+            val expected = OverrideNetworkType(type, mobileMappings.toIconKeyOverride(type))
             val ti =
                 mock<TelephonyDisplayInfo>().also {
+                    whenever(it.networkType).thenReturn(type)
                     whenever(it.overrideNetworkType).thenReturn(type)
                 }
             callback.onDisplayInfoChanged(ti)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
similarity index 81%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index a953a3d..4b82b39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.statusbar.pipeline.mobile.data.repository
+package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
 import android.content.Intent
 import android.net.ConnectivityManager
@@ -32,6 +32,8 @@
 import com.android.internal.telephony.PhoneConstants
 import com.android.systemui.SysuiTestCase
 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.util.FakeMobileMappingsProxy
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -50,6 +52,7 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -65,6 +68,8 @@
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
 
+    private val mobileMappings = FakeMobileMappingsProxy()
+
     private val scope = CoroutineScope(IMMEDIATE)
     private val globalSettings = FakeSettings()
 
@@ -72,18 +77,37 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        // Set up so the individual connection repositories
+        whenever(telephonyManager.createForSubscriptionId(anyInt())).thenAnswer { invocation ->
+            telephonyManager.also {
+                whenever(telephonyManager.subscriptionId).thenReturn(invocation.getArgument(0))
+            }
+        }
+
+        val connectionFactory: MobileConnectionRepositoryImpl.Factory =
+            MobileConnectionRepositoryImpl.Factory(
+                context = context,
+                telephonyManager = telephonyManager,
+                bgDispatcher = IMMEDIATE,
+                globalSettings = globalSettings,
+                logger = logger,
+                mobileMappingsProxy = mobileMappings,
+                scope = scope,
+            )
+
         underTest =
             MobileConnectionsRepositoryImpl(
                 connectivityManager,
                 subscriptionManager,
                 telephonyManager,
                 logger,
+                mobileMappings,
                 fakeBroadcastDispatcher,
                 globalSettings,
                 context,
                 IMMEDIATE,
                 scope,
-                mock(),
+                connectionFactory,
             )
     }
 
@@ -95,21 +119,21 @@
     @Test
     fun testSubscriptions_initiallyEmpty() =
         runBlocking(IMMEDIATE) {
-            assertThat(underTest.subscriptionsFlow.value).isEqualTo(listOf<SubscriptionInfo>())
+            assertThat(underTest.subscriptions.value).isEqualTo(listOf<SubscriptionModel>())
         }
 
     @Test
     fun testSubscriptions_listUpdates() =
         runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
 
-            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
             getSubscriptionCallback().onSubscriptionsChanged()
 
-            assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
+            assertThat(latest).isEqualTo(listOf(MODEL_1, MODEL_2))
 
             job.cancel()
         }
@@ -117,9 +141,9 @@
     @Test
     fun testSubscriptions_removingSub_updatesList() =
         runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
 
-            val job = underTest.subscriptionsFlow.onEach { latest = it }.launchIn(this)
+            val job = underTest.subscriptions.onEach { latest = it }.launchIn(this)
 
             // WHEN 2 networks show up
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
@@ -132,7 +156,7 @@
             getSubscriptionCallback().onSubscriptionsChanged()
 
             // THEN the subscriptions list represents the newest change
-            assertThat(latest).isEqualTo(listOf(SUB_2))
+            assertThat(latest).isEqualTo(listOf(MODEL_2))
 
             job.cancel()
         }
@@ -162,7 +186,7 @@
     @Test
     fun testConnectionRepository_validSubId_isCached() =
         runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
+            val job = underTest.subscriptions.launchIn(this)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1))
@@ -179,7 +203,7 @@
     @Test
     fun testConnectionCache_clearsInvalidSubscriptions() =
         runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
+            val job = underTest.subscriptions.launchIn(this)
 
             whenever(subscriptionManager.completeActiveSubscriptionInfoList)
                 .thenReturn(listOf(SUB_1, SUB_2))
@@ -202,10 +226,36 @@
             job.cancel()
         }
 
+    /** Regression test for b/261706421 */
+    @Test
+    fun testConnectionsCache_clearMultipleSubscriptionsAtOnce_doesNotThrow() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger caching
+            val repo1 = underTest.getRepoForSubId(SUB_1_ID)
+            val repo2 = underTest.getRepoForSubId(SUB_2_ID)
+
+            assertThat(underTest.getSubIdRepoCache())
+                .containsExactly(SUB_1_ID, repo1, SUB_2_ID, repo2)
+
+            // All subscriptions disappear
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList).thenReturn(listOf())
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            assertThat(underTest.getSubIdRepoCache()).isEmpty()
+
+            job.cancel()
+        }
+
     @Test
     fun testConnectionRepository_invalidSubId_throws() =
         runBlocking(IMMEDIATE) {
-            val job = underTest.subscriptionsFlow.launchIn(this)
+            val job = underTest.subscriptions.launchIn(this)
 
             assertThrows(IllegalArgumentException::class.java) {
                 underTest.getRepoForSubId(SUB_1_ID)
@@ -371,10 +421,12 @@
         private const val SUB_1_ID = 1
         private val SUB_1 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+        private val MODEL_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
 
         private const val SUB_2_ID = 2
         private val SUB_2 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+        private val MODEL_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
 
         private const val NET_ID = 123
         private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
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 061c3b54..0d4044d 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
@@ -16,14 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
-import android.telephony.SubscriptionInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO
 import android.telephony.TelephonyManager.NETWORK_TYPE_GSM
 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
 import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
@@ -47,8 +48,8 @@
 
     override val isDefaultConnectionFailed = MutableStateFlow(false)
 
-    private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
-    override val filteredSubscriptions = _filteredSubscriptions
+    private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
+    override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
 
     private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
     override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
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 7fc1c0f..fd41b5b 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
@@ -24,9 +24,9 @@
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FIVE_G_OVERRIDE
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor.Companion.FOUR_G
@@ -49,7 +49,7 @@
     private lateinit var underTest: MobileIconInteractor
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
     private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
-    private val connectionRepository = FakeMobileConnectionRepository()
+    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
 
     private val scope = CoroutineScope(IMMEDIATE)
 
@@ -62,7 +62,6 @@
                 mobileIconsInteractor.defaultMobileIconMapping,
                 mobileIconsInteractor.defaultMobileIconGroup,
                 mobileIconsInteractor.isDefaultConnectionFailed,
-                mobileMappingsProxy,
                 connectionRepository,
             )
     }
@@ -70,8 +69,8 @@
     @Test
     fun gsm_level_default_unknown() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(isGsm = true),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(isGsm = true),
             )
 
             var latest: Int? = null
@@ -85,8 +84,8 @@
     @Test
     fun gsm_usesGsmLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
                     isGsm = true,
                     primaryLevel = GSM_LEVEL,
                     cdmaLevel = CDMA_LEVEL
@@ -104,8 +103,8 @@
     @Test
     fun cdma_level_default_unknown() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(isGsm = false),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(isGsm = false),
             )
 
             var latest: Int? = null
@@ -118,8 +117,8 @@
     @Test
     fun cdma_usesCdmaLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
                     isGsm = false,
                     primaryLevel = GSM_LEVEL,
                     cdmaLevel = CDMA_LEVEL
@@ -137,8 +136,11 @@
     @Test
     fun iconGroup_three_g() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType =
+                        DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+                ),
             )
 
             var latest: MobileIconGroup? = null
@@ -152,16 +154,23 @@
     @Test
     fun iconGroup_updates_on_change() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(resolvedNetworkType = DefaultNetworkType(THREE_G)),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType =
+                        DefaultNetworkType(THREE_G, mobileMappingsProxy.toIconKey(THREE_G))
+                ),
             )
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(
-                    resolvedNetworkType = DefaultNetworkType(FOUR_G),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType =
+                        DefaultNetworkType(
+                            FOUR_G,
+                            mobileMappingsProxy.toIconKey(FOUR_G),
+                        ),
                 ),
             )
             yield()
@@ -174,8 +183,14 @@
     @Test
     fun iconGroup_5g_override_type() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(resolvedNetworkType = OverrideNetworkType(FIVE_G_OVERRIDE)),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType =
+                        OverrideNetworkType(
+                            FIVE_G_OVERRIDE,
+                            mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE)
+                        )
+                ),
             )
 
             var latest: MobileIconGroup? = null
@@ -189,9 +204,13 @@
     @Test
     fun iconGroup_default_if_no_lookup() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(
-                    resolvedNetworkType = DefaultNetworkType(NETWORK_TYPE_UNKNOWN),
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    resolvedNetworkType =
+                        DefaultNetworkType(
+                            NETWORK_TYPE_UNKNOWN,
+                            mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)
+                        ),
                 ),
             )
 
@@ -238,8 +257,8 @@
             var latest: Boolean? = null
             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
             )
             yield()
 
@@ -254,8 +273,8 @@
             var latest: Boolean? = null
             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setMobileSubscriptionModel(
-                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
             )
 
             assertThat(latest).isFalse()
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 b56dcd7..58e57e2 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
@@ -16,17 +16,16 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
-import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 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.FakeMobileConnectionRepository
 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.util.CarrierConfigTracker
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -45,8 +44,8 @@
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
     private val userSetupRepository = FakeUserSetupRepository()
-    private val connectionsRepository = FakeMobileConnectionsRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
+    private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
     private val scope = CoroutineScope(IMMEDIATE)
 
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -69,7 +68,6 @@
             MobileIconsInteractorImpl(
                 connectionsRepository,
                 carrierConfigTracker,
-                mobileMappingsProxy,
                 userSetupRepository,
                 scope
             )
@@ -80,10 +78,10 @@
     @Test
     fun filteredSubscriptions_default() =
         runBlocking(IMMEDIATE) {
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
-            assertThat(latest).isEqualTo(listOf<SubscriptionInfo>())
+            assertThat(latest).isEqualTo(listOf<SubscriptionModel>())
 
             job.cancel()
         }
@@ -93,7 +91,7 @@
         runBlocking(IMMEDIATE) {
             connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
             assertThat(latest).isEqualTo(listOf(SUB_1, SUB_2))
@@ -109,7 +107,7 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
             // Filtered subscriptions should show the active one when the config is false
@@ -126,7 +124,7 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
             // Filtered subscriptions should show the active one when the config is false
@@ -143,7 +141,7 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -161,7 +159,7 @@
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
-            var latest: List<SubscriptionInfo>? = null
+            var latest: List<SubscriptionModel>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
 
             // Filtered subscriptions should show the primary (non-opportunistic) if the config is
@@ -261,29 +259,19 @@
         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 CONNECTION_1 = FakeMobileConnectionRepository()
+        private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
+        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
 
         private const val SUB_2_ID = 2
-        private val SUB_2 =
-            mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
-        private val CONNECTION_2 = FakeMobileConnectionRepository()
+        private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
+        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
 
         private const val SUB_3_ID = 3
-        private val SUB_3_OPP =
-            mock<SubscriptionInfo>().also {
-                whenever(it.subscriptionId).thenReturn(SUB_3_ID)
-                whenever(it.isOpportunistic).thenReturn(true)
-            }
-        private val CONNECTION_3 = FakeMobileConnectionRepository()
+        private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
+        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
 
         private const val SUB_4_ID = 4
-        private val SUB_4_OPP =
-            mock<SubscriptionInfo>().also {
-                whenever(it.subscriptionId).thenReturn(SUB_4_ID)
-                whenever(it.isOpportunistic).thenReturn(true)
-            }
-        private val CONNECTION_4 = FakeMobileConnectionRepository()
+        private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
+        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 5c16e129..3d9fd96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.lifecycle.InstantTaskExecutorRule
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
@@ -64,6 +65,7 @@
     private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock
     private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock
     private lateinit var connectivityConstants: ConnectivityConstants
     @Mock
@@ -103,6 +105,7 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             scope,
             statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
index 3001b81..12b93819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -67,6 +69,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -123,6 +126,7 @@
                     connectivityConstants,
                     context,
                     logger,
+                    tableLogBuffer,
                     interactor,
                     scope,
                     statusBarPipelineFlags,
@@ -137,15 +141,21 @@
             yield()
 
             // THEN we get the expected icon
-            assertThat(iconFlow.value?.res).isEqualTo(testCase.expected?.iconResource)
-            val expectedContentDescription =
-                if (testCase.expected == null) {
-                    null
-                } else {
-                    testCase.expected.contentDescription.invoke(context)
+            val actualIcon = iconFlow.value
+            when (testCase.expected) {
+                null -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java)
                 }
-            assertThat(iconFlow.value?.contentDescription?.loadContentDescription(context))
-                .isEqualTo(expectedContentDescription)
+                else -> {
+                    assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java)
+                    val actualIconVisible = actualIcon as WifiIcon.Visible
+                    assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource)
+                    val expectedContentDescription =
+                        testCase.expected.contentDescription.invoke(context)
+                    assertThat(actualIconVisible.contentDescription.loadContentDescription(context))
+                        .isEqualTo(expectedContentDescription)
+                }
+            }
 
             job.cancel()
         }
@@ -174,7 +184,7 @@
         val isDefault: Boolean = false,
         val network: WifiNetworkModel,
 
-        /** The expected output. Null if we expect the output to be null. */
+        /** The expected output. Null if we expect the output to be hidden. */
         val expected: Expected?
     ) {
         override fun toString(): String {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
index 6a6b2a8..7502020 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
+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
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -59,6 +60,7 @@
 
     @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
     @Mock private lateinit var connectivityConstants: ConnectivityConstants
     @Mock private lateinit var wifiConstants: WifiConstants
     private lateinit var airplaneModeRepository: FakeAirplaneModeRepository
@@ -103,21 +105,21 @@
 
     @Test
     fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
-        var latestHome: Icon? = null
+        var latestHome: WifiIcon? = null
         val jobHome = underTest
             .home
             .wifiIcon
             .onEach { latestHome = it }
             .launchIn(this)
 
-        var latestKeyguard: Icon? = null
+        var latestKeyguard: WifiIcon? = null
         val jobKeyguard = underTest
             .keyguard
             .wifiIcon
             .onEach { latestKeyguard = it }
             .launchIn(this)
 
-        var latestQs: Icon? = null
+        var latestQs: WifiIcon? = null
         val jobQs = underTest
             .qs
             .wifiIcon
@@ -133,7 +135,7 @@
         )
         yield()
 
-        assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+        assertThat(latestHome).isInstanceOf(WifiIcon.Visible::class.java)
         assertThat(latestHome).isEqualTo(latestKeyguard)
         assertThat(latestKeyguard).isEqualTo(latestQs)
 
@@ -541,6 +543,7 @@
             connectivityConstants,
             context,
             logger,
+            tableLogBuffer,
             interactor,
             scope,
             statusBarPipelineFlags,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 47c84ab..7014f93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -35,6 +35,7 @@
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
+import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -109,6 +110,35 @@
     }
 
     @Test
+    fun displayView_contentDescription_iconHasDescription() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, ContentDescription.Loaded("loadedCD")),
+                Text.Loaded("text"),
+                endItem = null,
+            )
+        )
+
+        val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+        assertThat(contentDescView.contentDescription.toString()).contains("loadedCD")
+        assertThat(contentDescView.contentDescription.toString()).contains("text")
+    }
+
+    @Test
+    fun displayView_contentDescription_iconHasNoDescription() {
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Resource(R.drawable.ic_cake, contentDescription = null),
+                Text.Loaded("text"),
+                endItem = null,
+            )
+        )
+
+        val contentDescView = getChipbarView().requireViewById<ViewGroup>(R.id.chipbar_inner)
+        assertThat(contentDescView.contentDescription.toString()).isEqualTo("text")
+    }
+
+    @Test
     fun displayView_loadedIcon_correctlyRendered() {
         val drawable = context.getDrawable(R.drawable.ic_celebration)!!
 
@@ -370,7 +400,7 @@
         vibrationEffect: VibrationEffect? = null,
     ): ChipbarInfo {
         return ChipbarInfo(
-            startIcon,
+            TintedIcon(startIcon, tintAttr = null),
             text,
             endItem,
             vibrationEffect,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 5beb2b3..ffa4e2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.user.domain.model.ShowDialogRequestModel
 import com.android.systemui.user.shared.model.UserActionModel
 import com.android.systemui.user.shared.model.UserModel
+import com.android.systemui.user.utils.MultiUserActionsEvent
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.eq
@@ -74,6 +75,7 @@
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.never
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -158,6 +160,7 @@
                         resumeSessionReceiver = resumeSessionReceiver,
                         resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     ),
+                uiEventLogger = uiEventLogger,
                 featureFlags = featureFlags,
             )
     }
@@ -457,6 +460,9 @@
             val dialogShower: UserSwitchDialogController.DialogShower = mock()
 
             underTest.executeAction(UserActionModel.ADD_USER, dialogShower)
+
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.CREATE_USER_FROM_USER_SWITCHER)
             assertThat(dialogRequest)
                 .isEqualTo(
                     ShowDialogRequestModel.ShowAddUserDialog(
@@ -478,6 +484,8 @@
         runBlocking(IMMEDIATE) {
             underTest.executeAction(UserActionModel.ADD_SUPERVISED_USER)
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER)
             val intentCaptor = kotlinArgumentCaptor<Intent>()
             verify(activityStarter).startActivity(intentCaptor.capture(), eq(true))
             assertThat(intentCaptor.value.action)
@@ -525,6 +533,8 @@
 
             underTest.executeAction(UserActionModel.ENTER_GUEST_MODE)
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
             assertThat(dialogRequests)
                 .contains(
                     ShowDialogRequestModel.ShowUserCreationDialog(isGuest = true),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index 108fa62..5d4c1cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -255,6 +255,7 @@
                     activityManager = activityManager,
                     refreshUsersScheduler = refreshUsersScheduler,
                     guestUserInteractor = guestUserInteractor,
+                    uiEventLogger = uiEventLogger,
                 )
         )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index 784a26b..0efede1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -158,6 +158,7 @@
                             activityManager = activityManager,
                             refreshUsersScheduler = refreshUsersScheduler,
                             guestUserInteractor = guestUserInteractor,
+                            uiEventLogger = uiEventLogger,
                         ),
                     powerInteractor =
                         PowerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
new file mode 100644
index 0000000..01dd60a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/TestableAlertDialogTest.kt
@@ -0,0 +1,333 @@
+/*
+ * 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.util
+
+import android.content.DialogInterface
+import android.content.DialogInterface.BUTTON_NEGATIVE
+import android.content.DialogInterface.BUTTON_NEUTRAL
+import android.content.DialogInterface.BUTTON_POSITIVE
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class TestableAlertDialogTest : SysuiTestCase() {
+
+    @Test
+    fun dialogNotShowingWhenCreated() {
+        val dialog = TestableAlertDialog(context)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dialogShownDoesntCrash() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+    }
+
+    @Test
+    fun dialogShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+
+        assertThat(dialog.isShowing).isTrue()
+    }
+
+    @Test
+    fun showListenerCalled() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnShowListener = mock()
+        dialog.setOnShowListener(listener)
+
+        dialog.show()
+
+        verify(listener).onShow(dialog)
+    }
+
+    @Test
+    fun showListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnShowListener = mock()
+        dialog.setOnShowListener(listener)
+        dialog.setOnShowListener(null)
+
+        dialog.show()
+
+        verify(listener, never()).onShow(any())
+    }
+
+    @Test
+    fun dialogHiddenNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.hide()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dialogDismissNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.dismiss()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun dismissListenerCalled_ifShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.show()
+        dialog.dismiss()
+
+        verify(listener).onDismiss(dialog)
+    }
+
+    @Test
+    fun dismissListenerNotCalled_ifNotShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.dismiss()
+
+        verify(listener, never()).onDismiss(any())
+    }
+
+    @Test
+    fun dismissListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+        dialog.setOnDismissListener(null)
+
+        dialog.show()
+        dialog.dismiss()
+
+        verify(listener, never()).onDismiss(any())
+    }
+
+    @Test
+    fun cancelListenerCalled_showing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener).onCancel(dialog)
+    }
+
+    @Test
+    fun cancelListenerCalled_notShowing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+
+        dialog.cancel()
+
+        verify(listener).onCancel(dialog)
+    }
+
+    @Test
+    fun dismissCalledOnCancel_showing() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnDismissListener = mock()
+        dialog.setOnDismissListener(listener)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener).onDismiss(dialog)
+    }
+
+    @Test
+    fun dialogCancelNotShowing() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.cancel()
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun cancelListenerRemoved() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnCancelListener = mock()
+        dialog.setOnCancelListener(listener)
+        dialog.setOnCancelListener(null)
+
+        dialog.show()
+        dialog.cancel()
+
+        verify(listener, never()).onCancel(any())
+    }
+
+    @Test
+    fun positiveButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        verify(listener).onClick(dialog, BUTTON_POSITIVE)
+    }
+
+    @Test
+    fun positiveButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun negativeButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener).onClick(dialog, DialogInterface.BUTTON_NEGATIVE)
+    }
+
+    @Test
+    fun negativeButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun neutralButtonClick() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+    }
+
+    @Test
+    fun neutralButtonListener_noCalledWhenClickOtherButtons() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        verify(listener, never()).onClick(any(), anyInt())
+    }
+
+    @Test
+    fun sameClickListenerCalledCorrectly() {
+        val dialog = TestableAlertDialog(context)
+        val listener: DialogInterface.OnClickListener = mock()
+        dialog.setButton(BUTTON_POSITIVE, "", listener)
+        dialog.setButton(BUTTON_NEUTRAL, "", listener)
+        dialog.setButton(BUTTON_NEGATIVE, "", listener)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+        dialog.clickButton(BUTTON_NEGATIVE)
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        val inOrder = inOrder(listener)
+        inOrder.verify(listener).onClick(dialog, BUTTON_POSITIVE)
+        inOrder.verify(listener).onClick(dialog, BUTTON_NEGATIVE)
+        inOrder.verify(listener).onClick(dialog, BUTTON_NEUTRAL)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun clickBadButton() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.clickButton(10000)
+    }
+
+    @Test
+    fun clickButtonDismisses_positive() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_POSITIVE)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun clickButtonDismisses_negative() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEGATIVE)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+
+    @Test
+    fun clickButtonDismisses_neutral() {
+        val dialog = TestableAlertDialog(context)
+
+        dialog.show()
+        dialog.clickButton(BUTTON_NEUTRAL)
+
+        assertThat(dialog.isShowing).isFalse()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 379bb28..30dc0d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -108,7 +108,6 @@
     private FeatureFlags mFeatureFlags;
 
     FakeSystemClock mFakeSystemClock = new FakeSystemClock();
-    FakeExecutor mFakeMainExecutor = new FakeExecutor(mFakeSystemClock);
     FakeExecutor mFakeBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
 
     private CountDownLatch mEventCountdown;
@@ -163,7 +162,7 @@
     }
 
     private ImageWallpaper createImageWallpaper() {
-        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
             @Override
             public Engine onCreateEngine() {
                 return new GLEngine(mHandler) {
@@ -242,7 +241,7 @@
 
 
     private ImageWallpaper createImageWallpaperCanvas() {
-        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor, mFakeMainExecutor) {
+        return new ImageWallpaper(mFeatureFlags, mFakeBackgroundExecutor) {
             @Override
             public Engine onCreateEngine() {
                 return new CanvasEngine() {
@@ -315,11 +314,10 @@
         assertThat(mFakeBackgroundExecutor.numPending()).isAtLeast(1);
 
         int n = 0;
-        while (mFakeBackgroundExecutor.numPending() + mFakeMainExecutor.numPending() >= 1) {
+        while (mFakeBackgroundExecutor.numPending() >= 1) {
             n++;
             assertThat(n).isAtMost(10);
             mFakeBackgroundExecutor.runNextReady();
-            mFakeMainExecutor.runNextReady();
             mFakeSystemClock.advanceTime(1000);
         }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
new file mode 100644
index 0000000..3767fbe
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/MemoryTrackingTestCase.java
@@ -0,0 +1,63 @@
+/*
+ * 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;
+
+import android.os.Debug;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Convenience class for grabbing a heap dump after a test class is run.
+ *
+ * To use:
+ * - locally edit your test class to inherit from MemoryTrackingTestCase instead of SysuiTestCase
+ * - Watch the logcat with tag MEMORY to see the path to the .ahprof file
+ * - adb pull /path/to/something.ahprof
+ * - Download ahat from https://sites.google.com/corp/google.com/ahat/home
+ * - java -jar ~/Downloads/ahat-1.7.2.jar something.hprof
+ * - Watch output for next steps
+ * - Profit and fix leaks!
+ */
+public class MemoryTrackingTestCase extends SysuiTestCase {
+    private static File sFilesDir = null;
+    private static String sLatestTestClassName = null;
+
+    @Before public void grabFilesDir() {
+        if (sFilesDir == null) {
+            sFilesDir = mContext.getFilesDir();
+        }
+        sLatestTestClassName = getClass().getName();
+    }
+
+    @AfterClass
+    public static void dumpHeap() throws IOException {
+        if (sFilesDir == null) {
+            Log.e("MEMORY", "Somehow no test cases??");
+            return;
+        }
+        mockitoTearDown();
+        Log.w("MEMORY", "about to dump heap");
+        File path = new File(sFilesDir, sLatestTestClassName + ".ahprof");
+        Debug.dumpHprofData(path.getPath());
+        Log.w("MEMORY", "did it!  Location: " + path);
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index b31f119..ced7955 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -77,7 +77,5 @@
         dialogDismissedListeners.remove(listener)
     }
 
-    override fun shouldUpdateFooterVisibility(): Boolean = false
-
     override fun visibleButtonsCount(): Int = 0
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
index 21e16a1..8a10bf06 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/DeviceConfigProxyFake.java
@@ -81,11 +81,6 @@
         properties.get(namespace).put(name, value);
     }
 
-    @Override
-    public void enforceReadPermission(String namespace) {
-        // no-op
-    }
-
     private Properties propsForNamespaceAndName(String namespace, String name) {
         if (mProperties.containsKey(namespace) && mProperties.get(namespace).containsKey(name)) {
             return new Properties.Builder(namespace)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt
new file mode 100644
index 0000000..4d79554
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/TestableAlertDialog.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.util
+
+import android.app.AlertDialog
+import android.content.Context
+import android.content.DialogInterface
+import java.lang.IllegalArgumentException
+
+/**
+ * [AlertDialog] that is easier to test. Due to [AlertDialog] being a class and not an interface,
+ * there are some things that cannot be avoided, like the creation of a [Handler] on the main thread
+ * (and therefore needing a prepared [Looper] in the test).
+ *
+ * It bypasses calls to show, clicks on buttons, cancel and dismiss so it all can happen bounded in
+ * the test. It tries to be as close in behavior as a real [AlertDialog].
+ *
+ * It will only call [onCreate] as part of its lifecycle, but not any of the other lifecycle methods
+ * in [Dialog].
+ *
+ * In order to test clicking on buttons, use [clickButton] instead of calling [View.callOnClick] on
+ * the view returned by [getButton] to bypass the internal [Handler].
+ */
+class TestableAlertDialog(context: Context) : AlertDialog(context) {
+
+    private var _onDismissListener: DialogInterface.OnDismissListener? = null
+    private var _onCancelListener: DialogInterface.OnCancelListener? = null
+    private var _positiveButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _negativeButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _neutralButtonClickListener: DialogInterface.OnClickListener? = null
+    private var _onShowListener: DialogInterface.OnShowListener? = null
+    private var _dismissOverride: Runnable? = null
+
+    private var showing = false
+    private var visible = false
+    private var created = false
+
+    override fun show() {
+        if (!created) {
+            created = true
+            onCreate(null)
+        }
+        if (isShowing) return
+        showing = true
+        visible = true
+        _onShowListener?.onShow(this)
+    }
+
+    override fun hide() {
+        visible = false
+    }
+
+    override fun isShowing(): Boolean {
+        return visible && showing
+    }
+
+    override fun dismiss() {
+        if (!showing) {
+            return
+        }
+        if (_dismissOverride != null) {
+            _dismissOverride?.run()
+            return
+        }
+        _onDismissListener?.onDismiss(this)
+        showing = false
+    }
+
+    override fun cancel() {
+        _onCancelListener?.onCancel(this)
+        dismiss()
+    }
+
+    override fun setOnDismissListener(listener: DialogInterface.OnDismissListener?) {
+        _onDismissListener = listener
+    }
+
+    override fun setOnCancelListener(listener: DialogInterface.OnCancelListener?) {
+        _onCancelListener = listener
+    }
+
+    override fun setOnShowListener(listener: DialogInterface.OnShowListener?) {
+        _onShowListener = listener
+    }
+
+    override fun takeCancelAndDismissListeners(
+        msg: String?,
+        cancel: DialogInterface.OnCancelListener?,
+        dismiss: DialogInterface.OnDismissListener?
+    ): Boolean {
+        _onCancelListener = cancel
+        _onDismissListener = dismiss
+        return true
+    }
+
+    override fun setButton(
+        whichButton: Int,
+        text: CharSequence?,
+        listener: DialogInterface.OnClickListener?
+    ) {
+        super.setButton(whichButton, text, listener)
+        when (whichButton) {
+            DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener = listener
+            DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener = listener
+            DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener = listener
+            else -> Unit
+        }
+    }
+
+    /**
+     * Click one of the buttons in the [AlertDialog] and call the corresponding listener.
+     *
+     * Button ids are from [DialogInterface].
+     */
+    fun clickButton(whichButton: Int) {
+        val listener =
+            when (whichButton) {
+                DialogInterface.BUTTON_POSITIVE -> _positiveButtonClickListener
+                DialogInterface.BUTTON_NEGATIVE -> _negativeButtonClickListener
+                DialogInterface.BUTTON_NEUTRAL -> _neutralButtonClickListener
+                else -> throw IllegalArgumentException("Wrong button $whichButton")
+            }
+        listener?.onClick(this, whichButton)
+        dismiss()
+    }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index b568186..52fb0a7 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
 import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.updates.name
 
 /** Maps fold updates to unfold transition progress using DynamicAnimation. */
 class PhysicsBasedUnfoldTransitionProgressProvider(
@@ -117,7 +118,7 @@
         }
 
         if (DEBUG) {
-            Log.d(TAG, "onFoldUpdate = $update")
+            Log.d(TAG, "onFoldUpdate = ${update.name()}")
             Trace.traceCounter(Trace.TRACE_TAG_APP, "fold_update", update)
         }
     }
diff --git a/proto/src/OWNERS b/proto/src/OWNERS
index abd08de..ccff624 100644
--- a/proto/src/OWNERS
+++ b/proto/src/OWNERS
@@ -2,3 +2,4 @@
 per-file wifi.proto = file:/wifi/OWNERS
 per-file camera.proto = file:/services/core/java/com/android/server/camera/OWNERS
 per-file system_messages.proto = file:/core/res/OWNERS
+per-file altitude.proto = file:/location/OWNERS
diff --git a/proto/src/altitude.proto b/proto/src/altitude.proto
new file mode 100644
index 0000000..1010f67
--- /dev/null
+++ b/proto/src/altitude.proto
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.internal.location.altitude;
+
+option java_package = "com.android.internal.location.altitude";
+option java_multiple_files = true;
+
+// Defines parameters for a spherically projected geoid map and corresponding
+// tile management.
+message MapParamsProto {
+  // Defines the resolution of the map in terms of an S2 level.
+  optional int32 map_s2_level = 1;
+  // Defines the resolution of the tiles in cache in terms of an S2 level.
+  optional int32 cache_tile_s2_level = 2;
+  // Defines the resolution of the tiles on disk in terms of an S2 level.
+  optional int32 disk_tile_s2_level = 3;
+  // Defines the `a` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_a_meters = 4;
+  // Defines the `b` coefficient in the expression `a * map_value + b` used to
+  // calculate a geoid height in meters.
+  optional double model_b_meters = 5;
+  // Defines the root mean square error in meters of the geoid height.
+  optional double model_rmse_meters = 6;
+}
+
+// A single tile associating values in the unit interval [0, 1] to map cells.
+message S2TileProto {
+  // The S2 token associated with the common parent of all map cells in this
+  // tile.
+  optional string tile_key = 1;
+
+  // Encoded data that merge into a value in the unit interval [0, 1] for each
+  // map cell in this tile.
+  optional bytes byte_buffer = 2;
+  optional bytes byte_jpeg = 3;
+  optional bytes byte_png = 4;
+}
diff --git a/services/Android.bp b/services/Android.bp
index f6570e9..3f3ba06 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -187,6 +187,7 @@
         "framework-tethering.stubs.module_lib",
         "service-art.stubs.system_server",
         "service-permission.stubs.system_server",
+        "service-rkp.stubs.system_server",
         "service-sdksandbox.stubs.system_server",
     ],
 
diff --git a/services/api/current.txt b/services/api/current.txt
index 834ed2f..da5b1fc 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -57,8 +57,8 @@
 
   public static interface PackageManagerLocal.FilteredSnapshot extends java.lang.AutoCloseable {
     method public void close();
-    method public void forAllPackageStates(@NonNull java.util.function.Consumer<com.android.server.pm.pkg.PackageState>);
     method @Nullable public com.android.server.pm.pkg.PackageState getPackageState(@NonNull String);
+    method @NonNull public java.util.Map<java.lang.String,com.android.server.pm.pkg.PackageState> getPackageStates();
   }
 
   public static interface PackageManagerLocal.UnfilteredSnapshot extends java.lang.AutoCloseable {
@@ -98,6 +98,7 @@
   public interface PackageState {
     method @Nullable public com.android.server.pm.pkg.AndroidPackage getAndroidPackage();
     method public int getAppId();
+    method public int getHiddenApiEnforcementPolicy();
     method @NonNull public String getPackageName();
     method @Nullable public String getPrimaryCpuAbi();
     method @Nullable public String getSeInfo();
@@ -128,13 +129,6 @@
 
 }
 
-package com.android.server.pm.snapshot {
-
-  public interface PackageDataSnapshot {
-  }
-
-}
-
 package com.android.server.role {
 
   public interface RoleServicePlatformHelper {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index f1ba5ff..5e68d52 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -16,8 +16,7 @@
 
 package com.android.server.companion.virtual;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
-import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION;
+import static android.companion.virtual.VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_CAN_DISPLAY_ON_REMOTE_DEVICES;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -26,10 +25,10 @@
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.app.compat.CompatChanges;
-import android.companion.AssociationRequest;
 import android.companion.virtual.VirtualDeviceManager.ActivityListener;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.VirtualDeviceParams.ActivityPolicy;
+import android.companion.virtual.VirtualDeviceParams.RecentsPolicy;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.ComponentName;
@@ -127,10 +126,10 @@
     @GuardedBy("mGenericWindowPolicyControllerLock")
     private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
             new ArraySet<>();
-    @Nullable
-    private final @AssociationRequest.DeviceProfile String mDeviceProfile;
     @Nullable private final SecureWindowCallback mSecureWindowCallback;
     @Nullable private final List<String> mDisplayCategories;
+    @RecentsPolicy
+    private final int mDefaultRecentsPolicy;
 
     /**
      * Creates a window policy controller that is generic to the different use cases of virtual
@@ -156,7 +155,7 @@
      *   launching.
      * @param secureWindowCallback Callback that is called when a secure window shows on the
      *   virtual display.
-     * @param deviceProfile The {@link AssociationRequest.DeviceProfile} of this virtual device.
+     * @param defaultRecentsPolicy a policy to indicate how to handle activities in recents.
      */
     public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
@@ -169,8 +168,8 @@
             @NonNull PipBlockedCallback pipBlockedCallback,
             @NonNull ActivityBlockedCallback activityBlockedCallback,
             @NonNull SecureWindowCallback secureWindowCallback,
-            @AssociationRequest.DeviceProfile String deviceProfile,
-            @NonNull List<String> displayCategories) {
+            @NonNull List<String> displayCategories,
+            @RecentsPolicy int defaultRecentsPolicy) {
         super();
         mAllowedUsers = allowedUsers;
         mAllowedCrossTaskNavigations = new ArraySet<>(allowedCrossTaskNavigations);
@@ -181,10 +180,10 @@
         mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
-        mDeviceProfile = deviceProfile;
         mPipBlockedCallback = pipBlockedCallback;
         mSecureWindowCallback = secureWindowCallback;
         mDisplayCategories = displayCategories;
+        mDefaultRecentsPolicy = defaultRecentsPolicy;
     }
 
     /**
@@ -318,18 +317,8 @@
     }
 
     @Override
-    public boolean canShowTasksInRecents() {
-        if (mDeviceProfile == null) {
-            return true;
-        }
-        // TODO(b/234075973) : Remove this once proper API is ready.
-        switch (mDeviceProfile) {
-            case DEVICE_PROFILE_AUTOMOTIVE_PROJECTION:
-                return false;
-            case DEVICE_PROFILE_APP_STREAMING:
-            default:
-                return true;
-        }
+    public boolean canShowTasksInHostDeviceRecents() {
+        return (mDefaultRecentsPolicy & RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS) != 0;
     }
 
     @Override
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 96c71e5..0cea3d0 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -78,9 +78,8 @@
     final Object mLock;
 
     /* Token -> file descriptor associations. */
-    @VisibleForTesting
     @GuardedBy("mLock")
-    final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
+    private final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
 
     private final Handler mHandler;
     private final NativeWrapper mNativeWrapper;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index ef93810..5819861 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -679,8 +679,8 @@
                             this::onEnteringPipBlocked,
                             this::onActivityBlocked,
                             this::onSecureWindowShown,
-                            mAssociationInfo.getDeviceProfile(),
-                            displayCategories);
+                            displayCategories,
+                            mParams.getDefaultRecentsPolicy());
             gwpc.registerRunningAppsChangedListener(/* listener= */ this);
             return gwpc;
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 2b62f69..f07d285 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -47,6 +47,7 @@
 import android.util.ExceptionUtils;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.Display;
 import android.widget.Toast;
 
 import com.android.internal.annotations.GuardedBy;
@@ -385,7 +386,32 @@
         @Override // BinderCall
         @VirtualDeviceParams.DevicePolicy
         public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
-            return mLocalService.getDevicePolicy(deviceId, policyType);
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mVirtualDevices.size(); i++) {
+                    final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
+                    if (device.getDeviceId() == deviceId) {
+                        return device.getDevicePolicy(policyType);
+                    }
+                }
+            }
+            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+        }
+
+
+        @Override // Binder call
+        public int getDeviceIdForDisplayId(int displayId) {
+            if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) {
+                return VirtualDeviceManager.DEFAULT_DEVICE_ID;
+            }
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mVirtualDevices.size(); i++) {
+                    VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
+                    if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) {
+                        return virtualDevice.getDeviceId();
+                    }
+                }
+            }
+            return VirtualDeviceManager.DEFAULT_DEVICE_ID;
         }
 
         @Nullable
@@ -469,20 +495,6 @@
         }
 
         @Override
-        @VirtualDeviceParams.DevicePolicy
-        public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
-            synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mVirtualDevices.size(); i++) {
-                    final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return device.getDevicePolicy(policyType);
-                    }
-                }
-            }
-            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
-        }
-
-        @Override
         public void onVirtualDisplayCreated(int displayId) {
             final VirtualDisplayListener[] listeners;
             synchronized (mVirtualDeviceManagerLock) {
@@ -543,19 +555,6 @@
         }
 
         @Override
-        public boolean isAppOwnerOfAnyVirtualDevice(int uid) {
-            synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    if (mVirtualDevices.valueAt(i).getOwnerUid() == uid) {
-                        return true;
-                    }
-                }
-                return false;
-            }
-        }
-
-        @Override
         public boolean isAppRunningOnAnyVirtualDevice(int uid) {
             synchronized (mVirtualDeviceManagerLock) {
                 int size = mVirtualDevices.size();
diff --git a/services/core/Android.bp b/services/core/Android.bp
index a61a61b..1e1d610 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@
         "framework-tethering.stubs.module_lib",
         "service-art.stubs.system_server",
         "service-permission.stubs.system_server",
+        "service-rkp.stubs.system_server",
         "service-sdksandbox.stubs.system_server",
     ],
     plugins: ["ImmutabilityAnnotationProcessor"],
@@ -144,6 +145,7 @@
 
     static_libs: [
         "android.hardware.authsecret-V1.0-java",
+        "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java",
         "android.hardware.boot-V1.1-java",
         "android.hardware.boot-V1.2-java",
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 6cd7ce8..cbe6091 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -476,6 +476,7 @@
                 }
 
                 private void printPackageMeasurements(PackageInfo packageInfo,
+                                                      boolean useSha256,
                                                       final PrintWriter pw) {
                     Map<Integer, byte[]> contentDigests = computeApkContentDigest(
                             packageInfo.applicationInfo.sourceDir);
@@ -485,6 +486,14 @@
                         return;
                     }
 
+                    if (useSha256) {
+                        byte[] fileBuff = PackageUtils.createLargeFileBuffer();
+                        String hexEncodedSha256Digest =
+                                PackageUtils.computeSha256DigestForLargeFile(
+                                        packageInfo.applicationInfo.sourceDir, fileBuff);
+                        pw.print(hexEncodedSha256Digest + ",");
+                    }
+
                     for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
                         Integer algorithmId = entry.getKey();
                         byte[] contentDigest = entry.getValue();
@@ -497,6 +506,7 @@
                 }
 
                 private void printPackageInstallationInfo(PackageInfo packageInfo,
+                                                          boolean useSha256,
                                                           final PrintWriter pw) {
                     pw.println("--- Package Installation Info ---");
                     pw.println("Current install location: "
@@ -507,11 +517,13 @@
                         pw.println("|--> Pre-installed package install location: "
                                 + origPackageFilepath);
 
-                        // TODO(b/259347186): revive this with the proper cmd options.
-                        /*
-                        String digest = PackageUtils.computeSha256DigestForLargeFile(
-                        origPackageFilepath, PackageUtils.createLargeFileBuffer());
-                         */
+                        if (useSha256) {
+                            String sha256Digest = PackageUtils.computeSha256DigestForLargeFile(
+                                    origPackageFilepath, PackageUtils.createLargeFileBuffer());
+                            pw.println("|--> Pre-installed package SHA-256 digest: "
+                                    + sha256Digest);
+                        }
+
 
                         Map<Integer, byte[]> contentDigests = computeApkContentDigest(
                                 origPackageFilepath);
@@ -530,7 +542,9 @@
                         }
                     }
                     pw.println("First install time (ms): " + packageInfo.firstInstallTime);
-                    pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+                    pw.println("Last update time (ms):   " + packageInfo.lastUpdateTime);
+                    // TODO(b/261493591): Determination of whether a package is preinstalled can be
+                    // made more robust
                     boolean isPreloaded = (packageInfo.firstInstallTime
                             == packageInfo.lastUpdateTime);
                     pw.println("Is preloaded: " + isPreloaded);
@@ -562,6 +576,8 @@
                     }
                     pw.println("--- Package Signer Info ---");
                     pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+                    pw.println("Signing key has been rotated: "
+                            + signerInfo.hasPastSigningCertificates());
                     Signature[] packageSigners = signerInfo.getApkContentsSigners();
                     for (Signature packageSigner : packageSigners) {
                         byte[] packageSignerDigestBytes =
@@ -575,8 +591,31 @@
                         } catch (CertificateException e) {
                             Slog.e(TAG,
                                     "Failed to obtain public key of signer for cert with hash: "
-                                    + packageSignerDigestHextring);
-                            e.printStackTrace();
+                                    + packageSignerDigestHextring, e);
+                        }
+                    }
+
+                    if (!signerInfo.hasMultipleSigners()
+                            && signerInfo.hasPastSigningCertificates()) {
+                        pw.println("== Signing Cert Lineage (Excluding The Most Recent) ==");
+                        pw.println("(Certs are sorted in the order of rotation, beginning with the "
+                                   + "original signing cert)");
+                        Signature[] signingCertHistory = signerInfo.getSigningCertificateHistory();
+                        for (int i = 0; i < (signingCertHistory.length - 1); i++) {
+                            Signature signature = signingCertHistory[i];
+                            byte[] signatureDigestBytes = PackageUtils.computeSha256DigestBytes(
+                                    signature.toByteArray());
+                            String certHashHexString = HexEncoding.encodeToString(
+                                    signatureDigestBytes, false);
+                            pw.println("  ++ Signer cert #" + (i + 1) + " ++");
+                            pw.println("  Cert SHA256-digest: " + certHashHexString);
+                            try {
+                                PublicKey publicKey = signature.getPublicKey();
+                                pw.println("  Signing key algorithm: " + publicKey.getAlgorithm());
+                            } catch (CertificateException e) {
+                                Slog.e(TAG, "Failed to obtain public key of signer for cert "
+                                        + "with hash: " + certHashHexString, e);
+                            }
                         }
                     }
 
@@ -615,7 +654,7 @@
                     pw.println("Component factory: "
                             + packageInfo.applicationInfo.appComponentFactory);
                     pw.println("Process name: " + packageInfo.applicationInfo.processName);
-                    pw.println("Task affinity : " + packageInfo.applicationInfo.taskAffinity);
+                    pw.println("Task affinity: " + packageInfo.applicationInfo.taskAffinity);
                     pw.println("UID: " + packageInfo.applicationInfo.uid);
                     pw.println("Shared UID: " + packageInfo.sharedUserId);
 
@@ -669,15 +708,35 @@
 
                 }
 
+                private void printHeadersHelper(@NonNull String packageType,
+                                          boolean useSha256,
+                                          @NonNull final PrintWriter pw) {
+                    pw.print(packageType + " Info [Format: package_name,package_version,");
+                    if (useSha256) {
+                        pw.print("package_sha256_digest,");
+                    }
+                    pw.print("content_digest_algorithm:content_digest]:\n");
+                }
+
                 private int printAllApexs() {
                     final PrintWriter pw = getOutPrintWriter();
                     boolean verbose = false;
+                    boolean useSha256 = false;
+                    boolean printHeaders = true;
                     String opt;
                     while ((opt = getNextOption()) != null) {
                         switch (opt) {
                             case "-v":
+                            case "--verbose":
                                 verbose = true;
                                 break;
+                            case "-o":
+                            case "--old":
+                                useSha256 = true;
+                                break;
+                            case "--no-headers":
+                                printHeaders = false;
+                                break;
                             default:
                                 pw.println("ERROR: Unknown option: " + opt);
                                 return 1;
@@ -690,23 +749,17 @@
                         return -1;
                     }
 
-                    if (!verbose) {
-                        pw.println("APEX Info [Format: package_name,package_version,"
-                                // TODO(b/259347186): revive via special cmd line option
-                                //+ "package_sha256_digest,"
-                                + "content_digest_algorithm:content_digest]:");
+                    if (!verbose && printHeaders) {
+                        printHeadersHelper("APEX", useSha256, pw);
                     }
                     for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
-                        if (verbose) {
-                            pw.println("APEX Info [Format: package_name,package_version,"
-                                    // TODO(b/259347186): revive via special cmd line option
-                                    //+ "package_sha256_digest,"
-                                    + "content_digest_algorithm:content_digest]:");
+                        if (verbose && printHeaders) {
+                            printHeadersHelper("APEX", useSha256, pw);
                         }
                         String packageName = packageInfo.packageName;
                         pw.print(packageName + ","
                                 + packageInfo.getLongVersionCode() + ",");
-                        printPackageMeasurements(packageInfo, pw);
+                        printPackageMeasurements(packageInfo, useSha256, pw);
 
                         if (verbose) {
                             ModuleInfo moduleInfo;
@@ -718,7 +771,7 @@
                                 pw.println("Is a module: false");
                             }
 
-                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageInstallationInfo(packageInfo, useSha256, pw);
                             printPackageSignerDetails(packageInfo.signingInfo, pw);
                             pw.println("");
                         }
@@ -729,12 +782,22 @@
                 private int printAllModules() {
                     final PrintWriter pw = getOutPrintWriter();
                     boolean verbose = false;
+                    boolean useSha256 = false;
+                    boolean printHeaders = true;
                     String opt;
                     while ((opt = getNextOption()) != null) {
                         switch (opt) {
                             case "-v":
+                            case "--verbose":
                                 verbose = true;
                                 break;
+                            case "-o":
+                            case "--old":
+                                useSha256 = true;
+                                break;
+                            case "--no-headers":
+                                printHeaders = false;
+                                break;
                             default:
                                 pw.println("ERROR: Unknown option: " + opt);
                                 return 1;
@@ -747,32 +810,25 @@
                         return -1;
                     }
 
-                    if (!verbose) {
-                        pw.println("Module Info [Format: package_name,package_version,"
-                                // TODO(b/259347186): revive via special cmd line option
-                                //+ "package_sha256_digest,"
-                                + "content_digest_algorithm:content_digest]:");
+                    if (!verbose && printHeaders) {
+                        printHeadersHelper("Module", useSha256, pw);
                     }
                     for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
                         String packageName = module.getPackageName();
-                        if (verbose) {
-                            pw.println("Module Info [Format: package_name,package_version,"
-                                    // TODO(b/259347186): revive via special cmd line option
-                                    //+ "package_sha256_digest,"
-                                    + "content_digest_algorithm:content_digest]:");
+                        if (verbose && printHeaders) {
+                            printHeadersHelper("Module", useSha256, pw);
                         }
                         try {
                             PackageInfo packageInfo = pm.getPackageInfo(packageName,
                                     PackageManager.MATCH_APEX
                                             | PackageManager.GET_SIGNING_CERTIFICATES);
-                            //pw.print("package:");
                             pw.print(packageInfo.packageName + ",");
                             pw.print(packageInfo.getLongVersionCode() + ",");
-                            printPackageMeasurements(packageInfo, pw);
+                            printPackageMeasurements(packageInfo, useSha256, pw);
 
                             if (verbose) {
                                 printModuleDetails(module, pw);
-                                printPackageInstallationInfo(packageInfo, pw);
+                                printPackageInstallationInfo(packageInfo, useSha256, pw);
                                 printPackageSignerDetails(packageInfo.signingInfo, pw);
                                 pw.println("");
                             }
@@ -793,41 +849,45 @@
                     final PrintWriter pw = getOutPrintWriter();
                     boolean verbose = false;
                     boolean printLibraries = false;
+                    boolean useSha256 = false;
+                    boolean printHeaders = true;
                     String opt;
                     while ((opt = getNextOption()) != null) {
                         switch (opt) {
                             case "-v":
+                            case "--verbose":
                                 verbose = true;
                                 break;
                             case "-l":
                                 printLibraries = true;
                                 break;
+                            case "-o":
+                            case "--old":
+                                useSha256 = true;
+                                break;
+                            case "--no-headers":
+                                printHeaders = false;
+                                break;
                             default:
                                 pw.println("ERROR: Unknown option: " + opt);
                                 return 1;
                         }
                     }
 
-                    if (!verbose) {
-                        pw.println("MBA Info [Format: package_name,package_version,"
-                                // TODO(b/259347186): revive via special cmd line option
-                                //+ "package_sha256_digest,"
-                                + "content_digest_algorithm:content_digest]:");
+                    if (!verbose && printHeaders) {
+                        printHeadersHelper("MBA", useSha256, pw);
                     }
                     for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
-                        if (verbose) {
-                            pw.println("MBA Info [Format: package_name,package_version,"
-                                    // TODO(b/259347186): revive via special cmd line option
-                                    //+ "package_sha256_digest,"
-                                    + "content_digest_algorithm:content_digest]:");
+                        if (verbose && printHeaders) {
+                            printHeadersHelper("MBA", useSha256, pw);
                         }
                         pw.print(packageInfo.packageName + ",");
                         pw.print(packageInfo.getLongVersionCode() + ",");
-                        printPackageMeasurements(packageInfo, pw);
+                        printPackageMeasurements(packageInfo, useSha256, pw);
 
                         if (verbose) {
                             printAppDetails(packageInfo, printLibraries, pw);
-                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageInstallationInfo(packageInfo, useSha256, pw);
                             printPackageSignerDetails(packageInfo.signingInfo, pw);
                             pw.println("");
                         }
@@ -894,27 +954,39 @@
                 private void printHelpMenu() {
                     final PrintWriter pw = getOutPrintWriter();
                     pw.println("Transparency manager (transparency) commands:");
-                    pw.println("    help");
-                    pw.println("        Print this help text.");
+                    pw.println("  help");
+                    pw.println("    Print this help text.");
                     pw.println("");
-                    pw.println("    get image_info [-a]");
-                    pw.println("        Print information about loaded image (firmware). Options:");
-                    pw.println("            -a: lists all other identifiable partitions.");
+                    pw.println("  get image_info [-a]");
+                    pw.println("    Print information about loaded image (firmware). Options:");
+                    pw.println("        -a: lists all other identifiable partitions.");
                     pw.println("");
-                    pw.println("    get apex_info [-v]");
-                    pw.println("        Print information about installed APEXs on device.");
-                    pw.println("            -v: lists more verbose information about each APEX.");
+                    pw.println("  get apex_info [-o] [-v] [--no-headers]");
+                    pw.println("    Print information about installed APEXs on device.");
+                    pw.println("      -o: also uses the old digest scheme (SHA256) to compute "
+                               + "APEX hashes. WARNING: This can be a very slow and CPU-intensive "
+                               + "computation.");
+                    pw.println("      -v: lists more verbose information about each APEX.");
+                    pw.println("      --no-headers: does not print the header if specified");
                     pw.println("");
-                    pw.println("    get module_info [-v]");
-                    pw.println("        Print information about installed modules on device.");
-                    pw.println("            -v: lists more verbose information about each module.");
+                    pw.println("  get module_info [-o] [-v] [--no-headers]");
+                    pw.println("    Print information about installed modules on device.");
+                    pw.println("      -o: also uses the old digest scheme (SHA256) to compute "
+                               + "module hashes. WARNING: This can be a very slow and "
+                               + "CPU-intensive computation.");
+                    pw.println("      -v: lists more verbose information about each module.");
+                    pw.println("      --no-headers: does not print the header if specified");
                     pw.println("");
-                    pw.println("    get mba_info [-v] [-l]");
-                    pw.println("        Print information about installed mobile bundle apps "
+                    pw.println("  get mba_info [-o] [-v] [-l] [--no-headers]");
+                    pw.println("    Print information about installed mobile bundle apps "
                                + "(MBAs on device).");
-                    pw.println("            -v: lists more verbose information about each app.");
-                    pw.println("            -l: lists shared library info. This will only be "
-                               + "listed with -v");
+                    pw.println("      -o: also uses the old digest scheme (SHA256) to compute "
+                               + "MBA hashes. WARNING: This can be a very slow and CPU-intensive "
+                               + "computation.");
+                    pw.println("      -v: lists more verbose information about each app.");
+                    pw.println("      -l: lists shared library info. (This option only works "
+                               + "when -v option is also specified)");
+                    pw.println("      --no-headers: does not print the header if specified");
                     pw.println("");
                 }
 
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index a2a7dd3..a654925 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -148,6 +148,10 @@
             Receiver r = new Receiver(listener);
             try {
                 listener.asBinder().linkToDeath(r, 0);
+                final Country country = detectCountry();
+                if (country != null) {
+                    listener.onCountryDetected(country);
+                }
                 mReceivers.put(listener.asBinder(), r);
                 if (mReceivers.size() == 1) {
                     Slog.d(TAG, "The first listener is added");
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 5d54b6c..fc26f09 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -17,9 +17,6 @@
 package com.android.server;
 
 import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
-import static android.Manifest.permission.NETWORK_SETTINGS;
-import static android.Manifest.permission.OBSERVE_NETWORK_POLICY;
-import static android.Manifest.permission.SHUTDOWN;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
 import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
@@ -63,6 +60,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.INetworkManagementService;
+import android.os.PermissionEnforcer;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -230,6 +228,7 @@
      */
     private NetworkManagementService(
             Context context, Dependencies deps) {
+        super(PermissionEnforcer.fromContext(context));
         mContext = context;
         mDeps = deps;
 
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 6b6351f..6435869 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -672,6 +672,7 @@
     private static final int H_COMPLETE_UNLOCK_USER = 14;
     private static final int H_VOLUME_STATE_CHANGED = 15;
     private static final int H_CLOUD_MEDIA_PROVIDER_CHANGED = 16;
+    private static final int H_SECURE_KEYGUARD_STATE_CHANGED = 17;
 
     class StorageManagerServiceHandler extends Handler {
         public StorageManagerServiceHandler(Looper looper) {
@@ -819,6 +820,14 @@
                     }
                     break;
                 }
+                case H_SECURE_KEYGUARD_STATE_CHANGED: {
+                    try {
+                        mVold.onSecureKeyguardStateChanged((boolean) msg.obj);
+                    } catch (Exception e) {
+                        Slog.wtf(TAG, e);
+                    }
+                    break;
+                }
             }
         }
     }
@@ -836,7 +845,15 @@
                 if (Intent.ACTION_USER_ADDED.equals(action)) {
                     final UserManager um = mContext.getSystemService(UserManager.class);
                     final int userSerialNumber = um.getUserSerialNumber(userId);
-                    mVold.onUserAdded(userId, userSerialNumber);
+                    final UserInfo userInfo = um.getUserInfo(userId);
+                    if (userInfo.isCloneProfile()) {
+                        // Only clone profiles share storage with their parent
+                        mVold.onUserAdded(userId, userSerialNumber,
+                                userInfo.profileGroupId /* sharesStorageWithUserId */);
+                    } else {
+                        mVold.onUserAdded(userId, userSerialNumber,
+                                -1 /* shareStorageWithUserId */);
+                    }
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     synchronized (mLock) {
                         final int size = mVolumes.size();
@@ -1050,7 +1067,11 @@
 
                 // Tell vold about all existing and started users
                 for (UserInfo user : users) {
-                    mVold.onUserAdded(user.id, user.serialNumber);
+                    if (user.isCloneProfile()) {
+                        mVold.onUserAdded(user.id, user.serialNumber, user.profileGroupId);
+                    } else {
+                        mVold.onUserAdded(user.id, user.serialNumber, -1);
+                    }
                 }
                 for (int userId : systemUnlockedUsers) {
                     mVold.onUserStarted(userId);
@@ -1242,12 +1263,12 @@
     public void onKeyguardStateChanged(boolean isShowing) {
         // Push down current secure keyguard status so that we ignore malicious
         // USB devices while locked.
-        mSecureKeyguardShowing = isShowing
+        boolean isSecureKeyguardShowing = isShowing
                 && mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
-        try {
-            mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
-        } catch (Exception e) {
-            Slog.wtf(TAG, e);
+        if (mSecureKeyguardShowing != isSecureKeyguardShowing) {
+            mSecureKeyguardShowing = isSecureKeyguardShowing;
+            mHandler.obtainMessage(H_SECURE_KEYGUARD_STATE_CHANGED, mSecureKeyguardShowing)
+                    .sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index bd90d85..32afcca 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1041,7 +1041,7 @@
             String callingFeatureId, IPhoneStateListener callback,
             int[] events, boolean notifyNow) {
         Set<Integer> eventList = Arrays.stream(events).boxed().collect(Collectors.toSet());
-        listen(renounceFineLocationAccess, renounceFineLocationAccess, callingPackage,
+        listen(renounceFineLocationAccess, renounceCoarseLocationAccess, callingPackage,
                 callingFeatureId, callback, eventList, notifyNow, subId);
     }
 
@@ -1606,7 +1606,7 @@
                             if (DBG) {
                                 log("notifyServiceStateForSubscriber: callback.onSSC r=" + r
                                         + " subId=" + subId + " phoneId=" + phoneId
-                                        + " state=" + state);
+                                        + " state=" + stateToSend);
                             }
                             r.callback.onServiceStateChanged(stateToSend);
                         } catch (RemoteException ex) {
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0d672bd..fa62b89 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2939,6 +2939,8 @@
 
     void unscheduleShortFgsTimeoutLocked(ServiceRecord sr) {
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+        mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG,
+                sr);
         mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_SHORT_FGS_TIMEOUT_MSG, sr);
     }
 
@@ -2981,6 +2983,9 @@
     void onShortFgsTimeout(ServiceRecord sr) {
         synchronized (mAm) {
             if (!sr.shouldTriggerShortFgsTimeout()) {
+                if (DEBUG_SHORT_SERVICE) {
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS timed out: " + sr);
+                }
                 return;
             }
             Slog.e(TAG_SERVICE, "Short FGS timed out: " + sr);
@@ -2989,10 +2994,19 @@
             } catch (RemoteException e) {
                 // TODO(short-service): Anything to do here?
             }
-            // Schedule the ANR timeout.
-            final Message msg = mAm.mHandler.obtainMessage(
-                    ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
-            mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
+            // Schedule the procstate demotion timeout and ANR timeout.
+            {
+                final Message msg = mAm.mHandler.obtainMessage(
+                        ActivityManagerService.SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG, sr);
+                mAm.mHandler.sendMessageAtTime(
+                        msg, sr.getShortFgsInfo().getProcStateDemoteTime());
+            }
+
+            {
+                final Message msg = mAm.mHandler.obtainMessage(
+                        ActivityManagerService.SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG, sr);
+                mAm.mHandler.sendMessageAtTime(msg, sr.getShortFgsInfo().getAnrTime());
+            }
         }
     }
 
@@ -3010,6 +3024,21 @@
         }
     }
 
+    void onShortFgsProcstateTimeout(ServiceRecord sr) {
+        synchronized (mAm) {
+            if (!sr.shouldDemoteShortFgsProcState()) {
+                if (DEBUG_SHORT_SERVICE) {
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS procstate demotion: " + sr);
+                }
+                return;
+            }
+
+            Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr);
+
+            mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT);
+        }
+    }
+
     void onShortFgsAnrTimeout(ServiceRecord sr) {
         final String reason = "A foreground service of FOREGROUND_SERVICE_TYPE_SHORT_SERVICE"
                 + " did not stop within a timeout: " + sr.getComponentName();
@@ -3021,6 +3050,9 @@
             tr.mLatencyTracker.waitingOnAMSLockEnded();
 
             if (!sr.shouldTriggerShortFgsAnr()) {
+                if (DEBUG_SHORT_SERVICE) {
+                    Slog.d(TAG_SERVICE, "[STALE] Short FGS ANR'ed: " + sr);
+                }
                 return;
             }
 
@@ -7713,4 +7745,35 @@
             Slog.e(TAG, "stopForegroundServiceDelegateLocked delegate does not exist");
         }
     }
+
+    private static void getClientPackages(ServiceRecord sr, ArraySet<String> output) {
+        var connections = sr.getConnections();
+        for (int conni = connections.size() - 1; conni >= 0; conni--) {
+            var connl = connections.valueAt(conni);
+            for (int i = 0, size = connl.size(); i < size; i++) {
+                var conn = connl.get(i);
+                if (conn.binding.client != null) {
+                    output.add(conn.binding.client.info.packageName);
+                }
+            }
+        }
+    }
+
+    /**
+     * Return all client package names of a service.
+     */
+    ArraySet<String> getClientPackagesLocked(@NonNull String servicePackageName) {
+        var results = new ArraySet<String>();
+        int[] users = mAm.mUserController.getUsers();
+        for (int ui = 0; ui < users.length; ui++) {
+            ArrayMap<ComponentName, ServiceRecord> alls = getServicesLocked(users[ui]);
+            for (int i = 0, size = alls.size(); i < size; i++) {
+                ServiceRecord sr = alls.valueAt(i);
+                if (sr.name.getPackageName().equals(servicePackageName)) {
+                    getClientPackages(sr, results);
+                }
+            }
+        }
+        return results;
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 2d69667..70f07a9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -513,7 +513,7 @@
 
     // Allow app just moving from TOP to FOREGROUND_SERVICE to stay in a higher adj value for
     // this long.
-    public long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
+    public volatile long TOP_TO_FGS_GRACE_DURATION = DEFAULT_TOP_TO_FGS_GRACE_DURATION;
 
     /**
      * Allow app just leaving TOP with an already running ALMOST_PERCEPTIBLE service to stay in
@@ -1142,6 +1142,8 @@
                             case KEY_LOW_SWAP_THRESHOLD_PERCENT:
                                 updateLowSwapThresholdPercent();
                                 break;
+                            case KEY_TOP_TO_FGS_GRACE_DURATION:
+                                updateTopToFgsGraceDuration();
                             default:
                                 break;
                         }
@@ -1359,8 +1361,6 @@
                     DEFAULT_PROCESS_START_ASYNC);
             MEMORY_INFO_THROTTLE_TIME = mParser.getLong(KEY_MEMORY_INFO_THROTTLE_TIME,
                     DEFAULT_MEMORY_INFO_THROTTLE_TIME);
-            TOP_TO_FGS_GRACE_DURATION = mParser.getDurationMillis(KEY_TOP_TO_FGS_GRACE_DURATION,
-                    DEFAULT_TOP_TO_FGS_GRACE_DURATION);
             TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION = mParser.getDurationMillis(
                     KEY_TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION,
                     DEFAULT_TOP_TO_ALMOST_PERCEPTIBLE_GRACE_DURATION);
@@ -1790,6 +1790,13 @@
                 DEFAULT_LOW_SWAP_THRESHOLD_PERCENT);
     }
 
+    private void updateTopToFgsGraceDuration() {
+        TOP_TO_FGS_GRACE_DURATION = DeviceConfig.getLong(
+                DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                KEY_TOP_TO_FGS_GRACE_DURATION,
+                DEFAULT_TOP_TO_FGS_GRACE_DURATION);
+    }
+
     private void updateMinAssocLogDuration() {
         MIN_ASSOC_LOG_DURATION = DeviceConfig.getLong(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, KEY_MIN_ASSOC_LOG_DURATION,
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 7566bab..e7e2081 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -137,6 +137,8 @@
 import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_SYSTEM;
 import static com.android.server.net.NetworkPolicyManagerInternal.updateBlockedReasonsWithProcState;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION;
@@ -328,6 +330,7 @@
 import android.util.ArraySet;
 import android.util.EventLog;
 import android.util.FeatureFlagUtils;
+import android.util.IndentingPrintWriter;
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
@@ -759,6 +762,9 @@
     final AppErrors mAppErrors;
     final PackageWatchdog mPackageWatchdog;
 
+    @GuardedBy("mDeliveryGroupPolicyIgnoredActions")
+    private final ArraySet<String> mDeliveryGroupPolicyIgnoredActions = new ArraySet();
+
     /**
      * Uids of apps with current active camera sessions.  Access synchronized on
      * the IntArray instance itself, and no other locks must be acquired while that
@@ -1538,7 +1544,8 @@
     static final int DISPATCH_SENDING_BROADCAST_EVENT = 74;
     static final int DISPATCH_BINDING_SERVICE_EVENT = 75;
     static final int SERVICE_SHORT_FGS_TIMEOUT_MSG = 76;
-    static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 77;
+    static final int SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG = 77;
+    static final int SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG = 78;
 
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
 
@@ -1876,6 +1883,9 @@
                 case SERVICE_SHORT_FGS_TIMEOUT_MSG: {
                     mServices.onShortFgsTimeout((ServiceRecord) msg.obj);
                 } break;
+                case SERVICE_SHORT_FGS_PROCSTATE_TIMEOUT_MSG: {
+                    mServices.onShortFgsProcstateTimeout((ServiceRecord) msg.obj);
+                } break;
                 case SERVICE_SHORT_FGS_ANR_TIMEOUT_MSG: {
                     mServices.onShortFgsAnrTimeout((ServiceRecord) msg.obj);
                 } break;
@@ -12667,7 +12677,7 @@
      * @param platformCompat the instance of platform compat
      */
     private static void filterNonExportedComponents(Intent intent, int callingUid,
-            List query, PlatformCompat platformCompat, String callerPackage) {
+            List query, PlatformCompat platformCompat, String callerPackage, String resolvedType) {
         if (query == null
                 || intent.getPackage() != null
                 || intent.getComponent() != null
@@ -12694,19 +12704,24 @@
             } else {
                 continue;
             }
-            if (!platformCompat.isChangeEnabledByUid(
-                    IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS, callingUid)) {
-                Slog.w(TAG, "Non-exported component not filtered out "
-                        + "(will be filtered out once the app targets U+)- intent: "
-                        + intent.getAction() + ", component: "
-                        + componentInfo + ", sender: "
-                        + callerPackage);
+            boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
+                    ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
+                    callingUid);
+            String[] categories = intent.getCategories() == null ? new String[0]
+                    : intent.getCategories().toArray(String[]::new);
+            FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
+                    FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
+                    callingUid,
+                    componentInfo,
+                    callerPackage,
+                    intent.getAction(),
+                    categories,
+                    resolvedType,
+                    intent.getScheme(),
+                    hasToBeExportedToMatch);
+            if (!hasToBeExportedToMatch) {
                 return;
             }
-            Slog.w(TAG, "Non-exported component filtered out - intent: "
-                    + intent.getAction() + ", component: "
-                    + componentInfo + ", sender: "
-                    + callerPackage);
             query.remove(i);
         }
     }
@@ -14620,7 +14635,7 @@
         }
 
         filterNonExportedComponents(intent, callingUid, registeredReceivers,
-                mPlatformCompat, callerPackage);
+                mPlatformCompat, callerPackage, resolvedType);
         int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
         if (!ordered && NR > 0 && !mEnableModernQueue) {
             // If we are not serializing this broadcast, then send the
@@ -14726,7 +14741,7 @@
                 || resultTo != null) {
             BroadcastQueue queue = broadcastQueueForIntent(intent);
             filterNonExportedComponents(intent, callingUid, receivers,
-                    mPlatformCompat, callerPackage);
+                    mPlatformCompat, callerPackage, resolvedType);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
                     requiredPermissions, excludedPermissions, excludedPackages, appOp, brOptions,
@@ -16566,14 +16581,14 @@
     @Override
     public boolean startUserInBackgroundWithListener(final int userId,
                 @Nullable IProgressListener unlockListener) {
-        return mUserController.startUser(userId, /* foreground */ false, unlockListener);
+        return mUserController.startUser(userId, USER_START_MODE_BACKGROUND, unlockListener);
     }
 
     @Override
     public boolean startUserInForegroundWithListener(final int userId,
             @Nullable IProgressListener unlockListener) {
         // Permission check done inside UserController.
-        return mUserController.startUser(userId, /* foreground */ true, unlockListener);
+        return mUserController.startUser(userId, USER_START_MODE_FOREGROUND, unlockListener);
     }
 
     @Override
@@ -18191,6 +18206,13 @@
                 mServices.stopForegroundServiceDelegateLocked(connection);
             }
         }
+
+        @Override
+        public ArraySet<String> getClientPackages(String servicePackageName) {
+            synchronized (ActivityManagerService.this) {
+                return mServices.getClientPackagesLocked(servicePackageName);
+            }
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18322,14 +18344,47 @@
         }
     }
 
-    public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
+    public void waitForBroadcastBarrier(@Nullable PrintWriter pw, boolean flushBroadcastLoopers) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
-        BroadcastLoopers.waitForIdle(pw);
+        if (flushBroadcastLoopers) {
+            BroadcastLoopers.waitForBarrier(pw);
+        }
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForBarrier(pw);
         }
     }
 
+    void setIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
+        Objects.requireNonNull(broadcastAction);
+        enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+        synchronized (mDeliveryGroupPolicyIgnoredActions) {
+            mDeliveryGroupPolicyIgnoredActions.add(broadcastAction);
+        }
+    }
+
+    void clearIgnoreDeliveryGroupPolicy(@NonNull String broadcastAction) {
+        Objects.requireNonNull(broadcastAction);
+        enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+        synchronized (mDeliveryGroupPolicyIgnoredActions) {
+            mDeliveryGroupPolicyIgnoredActions.remove(broadcastAction);
+        }
+    }
+
+    boolean shouldIgnoreDeliveryGroupPolicy(@Nullable String broadcastAction) {
+        if (broadcastAction == null) {
+            return false;
+        }
+        synchronized (mDeliveryGroupPolicyIgnoredActions) {
+            return mDeliveryGroupPolicyIgnoredActions.contains(broadcastAction);
+        }
+    }
+
+    void dumpDeliveryGroupPolicyIgnoredActions(IndentingPrintWriter ipw) {
+        synchronized (mDeliveryGroupPolicyIgnoredActions) {
+            ipw.println(mDeliveryGroupPolicyIgnoredActions);
+        }
+    }
+
     @Override
     @ReasonCode
     public int getBackgroundRestrictionExemptionReason(int uid) {
@@ -18486,7 +18541,7 @@
 
     @Override
     public int restartUserInBackground(final int userId) {
-        return mUserController.restartUser(userId, /* foreground */ false);
+        return mUserController.restartUser(userId, USER_START_MODE_BACKGROUND);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 0b94798..94c15ba 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -345,6 +345,10 @@
                     return runWaitForBroadcastIdle(pw);
                 case "wait-for-broadcast-barrier":
                     return runWaitForBroadcastBarrier(pw);
+                case "set-ignore-delivery-group-policy":
+                    return runSetIgnoreDeliveryGroupPolicy(pw);
+                case "clear-ignore-delivery-group-policy":
+                    return runClearIgnoreDeliveryGroupPolicy(pw);
                 case "compat":
                     return runCompat(pw);
                 case "refresh-settings-cache":
@@ -2006,12 +2010,6 @@
     }
 
     int runSwitchUser(PrintWriter pw) throws RemoteException {
-        UserManager userManager = mInternal.mContext.getSystemService(UserManager.class);
-        final int userSwitchable = userManager.getUserSwitchability();
-        if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
-            getErrPrintWriter().println("Error: " + userSwitchable);
-            return -1;
-        }
         boolean wait = false;
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -2024,6 +2022,14 @@
         }
 
         int userId = Integer.parseInt(getNextArgRequired());
+
+        UserManager userManager = mInternal.mContext.getSystemService(UserManager.class);
+        final int userSwitchable = userManager.getUserSwitchability(UserHandle.of(userId));
+        if (userSwitchable != UserManager.SWITCHABILITY_STATUS_OK) {
+            getErrPrintWriter().println("Error: UserSwitchabilityResult=" + userSwitchable);
+            return -1;
+        }
+
         boolean switched;
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "shell_runSwitchUser");
         try {
@@ -3131,7 +3137,29 @@
     }
 
     int runWaitForBroadcastBarrier(PrintWriter pw) throws RemoteException {
-        mInternal.waitForBroadcastBarrier(pw);
+        boolean flushBroadcastLoopers = false;
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--flush-broadcast-loopers")) {
+                flushBroadcastLoopers = true;
+            } else {
+                getErrPrintWriter().println("Error: Unknown option: " + opt);
+                return -1;
+            }
+        }
+        mInternal.waitForBroadcastBarrier(pw, flushBroadcastLoopers);
+        return 0;
+    }
+
+    int runSetIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException {
+        final String broadcastAction = getNextArgRequired();
+        mInternal.setIgnoreDeliveryGroupPolicy(broadcastAction);
+        return 0;
+    }
+
+    int runClearIgnoreDeliveryGroupPolicy(PrintWriter pw) throws RemoteException {
+        final String broadcastAction = getNextArgRequired();
+        mInternal.clearIgnoreDeliveryGroupPolicy(broadcastAction);
         return 0;
     }
 
@@ -4019,7 +4047,10 @@
                     + "background.");
             pw.println("  set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
             pw.println("         Start/stop an app's foreground service delegate.");
-            pw.println();
+            pw.println("  set-ignore-delivery-group-policy <ACTION>");
+            pw.println("         Start ignoring delivery group policy set for a broadcast action");
+            pw.println("  clear-ignore-delivery-group-policy <ACTION>");
+            pw.println("         Stop ignoring delivery group policy set for a broadcast action");
             Intent.printIntentArgsHelp(pw, "");
         }
     }
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 1eebd01..f5d1c10 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -153,12 +153,27 @@
             "bcast_extra_running_urgent_process_queues";
     private static final int DEFAULT_EXTRA_RUNNING_URGENT_PROCESS_QUEUES = 1;
 
+    /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive urgent
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
     public int MAX_CONSECUTIVE_URGENT_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES;
     private static final String KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES =
             "bcast_max_consecutive_urgent_dispatches";
     private static final int DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES = 3;
 
     /**
+     * For {@link BroadcastQueueModernImpl}: Maximum number of consecutive normal
+     * broadcast dispatches allowed before letting broadcasts in lower priority queue
+     * to be scheduled in order to avoid starvation.
+     */
+    public int MAX_CONSECUTIVE_NORMAL_DISPATCHES = DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES;
+    private static final String KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES =
+            "bcast_max_consecutive_normal_dispatches";
+    private static final int DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES = 10;
+
+    /**
      * For {@link BroadcastQueueModernImpl}: Maximum number of active broadcasts
      * to dispatch to a "running" process queue before we retire them back to
      * being "runnable" to give other processes a chance to run.
@@ -341,6 +356,9 @@
             MAX_CONSECUTIVE_URGENT_DISPATCHES = getDeviceConfigInt(
                     KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
                     DEFAULT_MAX_CONSECUTIVE_URGENT_DISPATCHES);
+            MAX_CONSECUTIVE_NORMAL_DISPATCHES = getDeviceConfigInt(
+                    KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    DEFAULT_MAX_CONSECUTIVE_NORMAL_DISPATCHES);
             MAX_RUNNING_ACTIVE_BROADCASTS = getDeviceConfigInt(KEY_MAX_RUNNING_ACTIVE_BROADCASTS,
                     DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
             MAX_PENDING_BROADCASTS = getDeviceConfigInt(KEY_MAX_PENDING_BROADCASTS,
@@ -396,6 +414,10 @@
                     TimeUtils.formatDuration(DELAY_URGENT_MILLIS)).println();
             pw.print(KEY_MAX_HISTORY_COMPLETE_SIZE, MAX_HISTORY_COMPLETE_SIZE).println();
             pw.print(KEY_MAX_HISTORY_SUMMARY_SIZE, MAX_HISTORY_SUMMARY_SIZE).println();
+            pw.print(KEY_MAX_CONSECUTIVE_URGENT_DISPATCHES,
+                    MAX_CONSECUTIVE_URGENT_DISPATCHES).println();
+            pw.print(KEY_MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+                    MAX_CONSECUTIVE_NORMAL_DISPATCHES).println();
             pw.decreaseIndent();
             pw.println();
         }
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
index b828720..a5535cb 100644
--- a/services/core/java/com/android/server/am/BroadcastLoopers.java
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
@@ -30,6 +31,7 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
+import java.util.function.BiConsumer;
 
 /**
  * Collection of {@link Looper} that are known to be used for broadcast dispatch
@@ -73,19 +75,44 @@
      * still in the future are ignored for the purposes of the idle test.
      */
     public static void waitForIdle(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            final MessageQueue queue = looper.getQueue();
+            queue.addIdleHandler(() -> {
+                latch.countDown();
+                return false;
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to handle currently waiting messages.
+     * Note that {@link Message#when} still in the future are ignored for the purposes
+     * of the idle test.
+     */
+    public static void waitForBarrier(@Nullable PrintWriter pw) {
+        waitForCondition(pw, (looper, latch) -> {
+            (new Handler(looper)).post(() -> {
+                latch.countDown();
+            });
+        });
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to meet a certain condition.
+     */
+    private static void waitForCondition(@Nullable PrintWriter pw,
+            @NonNull BiConsumer<Looper, CountDownLatch> condition) {
         final CountDownLatch latch;
         synchronized (sLoopers) {
             final int N = sLoopers.size();
             latch = new CountDownLatch(N);
             for (int i = 0; i < N; i++) {
-                final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+                final Looper looper = sLoopers.valueAt(i);
+                final MessageQueue queue = looper.getQueue();
                 if (queue.isIdle()) {
                     latch.countDown();
                 } else {
-                    queue.addIdleHandler(() -> {
-                        latch.countDown();
-                        return false;
-                    });
+                    condition.accept(looper, latch);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 66d7fc9..632f614 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -153,6 +153,12 @@
     private int mActiveCountConsecutiveUrgent;
 
     /**
+     * Number of consecutive normal broadcasts that have been dispatched
+     * since the last offload dispatch.
+     */
+    private int mActiveCountConsecutiveNormal;
+
+    /**
      * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
@@ -164,6 +170,8 @@
     private int mCountInstrumented;
     private int mCountManifest;
 
+    private boolean mPrioritizeEarliest;
+
     private @UptimeMillisLong long mRunnableAt = Long.MAX_VALUE;
     private @Reason int mRunnableAtReason = REASON_EMPTY;
     private boolean mRunnableAtInvalidated;
@@ -551,48 +559,75 @@
      * Will thrown an exception if there are no pending broadcasts; relies on
      * {@link #isEmpty()} being false.
      */
-    SomeArgs removeNextBroadcast() {
+    private @Nullable SomeArgs removeNextBroadcast() {
         final ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
         if (queue == mPendingUrgent) {
             mActiveCountConsecutiveUrgent++;
-        } else {
+        } else if (queue == mPending) {
             mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal++;
+        } else if (queue == mPendingOffload) {
+            mActiveCountConsecutiveUrgent = 0;
+            mActiveCountConsecutiveNormal = 0;
         }
-        return queue.removeFirst();
+        return !isQueueEmpty(queue) ? queue.removeFirst() : null;
     }
 
     @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast() {
-        ArrayDeque<SomeArgs> nextUrgent = mPendingUrgent.isEmpty() ? null : mPendingUrgent;
-        ArrayDeque<SomeArgs> nextNormal = null;
-        if (!mPending.isEmpty()) {
-            nextNormal = mPending;
-        } else if (!mPendingOffload.isEmpty()) {
-            nextNormal = mPendingOffload;
+        final ArrayDeque<SomeArgs> nextNormal = queueForNextBroadcast(
+                mPending, mPendingOffload,
+                mActiveCountConsecutiveNormal, constants.MAX_CONSECUTIVE_NORMAL_DISPATCHES);
+        final ArrayDeque<SomeArgs> nextBroadcastQueue = queueForNextBroadcast(
+                mPendingUrgent, nextNormal,
+                mActiveCountConsecutiveUrgent, constants.MAX_CONSECUTIVE_URGENT_DISPATCHES);
+        return nextBroadcastQueue;
+    }
+
+    private @Nullable ArrayDeque<SomeArgs> queueForNextBroadcast(
+            @Nullable ArrayDeque<SomeArgs> highPriorityQueue,
+            @Nullable ArrayDeque<SomeArgs> lowPriorityQueue,
+            int consecutiveHighPriorityCount,
+            int maxHighPriorityDispatchLimit) {
+        // nothing high priority pending, no further decisionmaking
+        if (isQueueEmpty(highPriorityQueue)) {
+            return lowPriorityQueue;
         }
-        // nothing urgent pending, no further decisionmaking
-        if (nextUrgent == null) {
-            return nextNormal;
-        }
-        // nothing but urgent pending, also no further decisionmaking
-        if (nextNormal == null) {
-            return nextUrgent;
+        // nothing but high priority pending, also no further decisionmaking
+        if (isQueueEmpty(lowPriorityQueue)) {
+            return highPriorityQueue;
         }
 
-        // Starvation mitigation: although we prioritize urgent broadcasts by default,
-        // we allow non-urgent deliveries to make steady progress even if urgent
-        // broadcasts are arriving faster than they can be dispatched.
+        // Starvation mitigation: although we prioritize high priority queues by default,
+        // we allow low priority queues to make steady progress even if broadcasts in
+        // high priority queue are arriving faster than they can be dispatched.
         //
-        // We do not try to defer to the next non-urgent broadcast if that broadcast
+        // We do not try to defer to the next broadcast in low priority queues if that broadcast
         // is ordered and still blocked on delivery to other recipients.
-        final SomeArgs nextNormalArgs = nextNormal.peekFirst();
-        final BroadcastRecord rNormal = (BroadcastRecord) nextNormalArgs.arg1;
-        final int nextNormalIndex = nextNormalArgs.argi1;
-        final BroadcastRecord rUrgent = (BroadcastRecord) nextUrgent.peekFirst().arg1;
-        final boolean canTakeNormal =
-                mActiveCountConsecutiveUrgent >= constants.MAX_CONSECUTIVE_URGENT_DISPATCHES
-                        && rNormal.enqueueTime <= rUrgent.enqueueTime
-                        && !blockedOnOrderedDispatch(rNormal, nextNormalIndex);
-        return canTakeNormal ? nextNormal : nextUrgent;
+        final SomeArgs nextLPArgs = lowPriorityQueue.peekFirst();
+        final BroadcastRecord nextLPRecord = (BroadcastRecord) nextLPArgs.arg1;
+        final int nextLPRecordIndex = nextLPArgs.argi1;
+        final BroadcastRecord nextHPRecord = (BroadcastRecord) highPriorityQueue.peekFirst().arg1;
+        final boolean shouldConsiderLPQueue = (mPrioritizeEarliest
+                || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit);
+        final boolean isLPQueueEligible = shouldConsiderLPQueue
+                && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime
+                && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex);
+        return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue;
+    }
+
+    private static boolean isQueueEmpty(@Nullable ArrayDeque<SomeArgs> queue) {
+        return (queue == null || queue.isEmpty());
+    }
+
+    /**
+     * When {@code prioritizeEarliest} is set to {@code true}, then earliest enqueued
+     * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts
+     * waiting. This is typically used in case there are callers waiting for "barrier" to be
+     * reached.
+     */
+    @VisibleForTesting
+    void setPrioritizeEarliest(boolean prioritizeEarliest) {
+        mPrioritizeEarliest = prioritizeEarliest;
     }
 
     /**
@@ -600,13 +635,13 @@
      */
     @Nullable SomeArgs peekNextBroadcast() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? queue.peekFirst() : null;
+        return !isQueueEmpty(queue) ? queue.peekFirst() : null;
     }
 
     @VisibleForTesting
     @Nullable BroadcastRecord peekNextBroadcastRecord() {
         ArrayDeque<SomeArgs> queue = queueForNextBroadcast();
-        return (queue != null) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
+        return !isQueueEmpty(queue) ? (BroadcastRecord) queue.peekFirst().arg1 : null;
     }
 
     /**
@@ -761,6 +796,7 @@
      */
     private void updateRunnableAt() {
         final SomeArgs next = peekNextBroadcast();
+        mRunnableAtInvalidated = false;
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 5d5dbbb..eb5c03b 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -91,6 +91,7 @@
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 /**
@@ -220,10 +221,19 @@
 
     private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
+        enqueueFinishReceiver(queue, queue.getActive(), queue.getActiveIndex(),
+                deliveryState, reason);
+    }
+
+    private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+            @NonNull BroadcastRecord r, int index,
+            @DeliveryState int deliveryState, @NonNull String reason) {
         final SomeArgs args = SomeArgs.obtain();
         args.arg1 = queue;
         args.argi1 = deliveryState;
         args.arg2 = reason;
+        args.arg3 = r;
+        args.argi2 = index;
         mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
     }
 
@@ -271,8 +281,10 @@
                     final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
                     final int deliveryState = args.argi1;
                     final String reason = (String) args.arg2;
+                    final BroadcastRecord r = (BroadcastRecord) args.arg3;
+                    final int index = args.argi2;
                     args.recycle();
-                    finishReceiverLocked(queue, deliveryState, reason);
+                    finishReceiverLocked(queue, deliveryState, reason, r, index);
                 }
                 return true;
             }
@@ -636,6 +648,9 @@
     }
 
     private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
+        if (mService.shouldIgnoreDeliveryGroupPolicy(r.intent.getAction())) {
+            return;
+        }
         final int policy = (r.options != null)
                 ? r.options.getDeliveryGroupPolicy() : BroadcastOptions.DELIVERY_GROUP_POLICY_ALL;
         final BroadcastConsumer broadcastConsumer;
@@ -729,10 +744,8 @@
     private void scheduleReceiverWarmLocked(@NonNull BroadcastProcessQueue queue) {
         checkState(queue.isActive(), "isActive");
 
-        final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
-        final Object receiver = r.receivers.get(index);
 
         if (r.terminalCount == 0) {
             r.dispatchTime = SystemClock.uptimeMillis();
@@ -740,26 +753,40 @@
             r.dispatchClockTime = System.currentTimeMillis();
         }
 
-        // If someone already finished this broadcast, finish immediately
+        if (maybeSkipReceiver(queue, r, index)) {
+            return;
+        }
+        dispatchReceivers(queue, r, index);
+    }
+
+    /**
+     * Examine a receiver and possibly skip it.  The method returns true if the receiver is
+     * skipped (and therefore no more work is required).
+     */
+    private boolean maybeSkipReceiver(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
         final int oldDeliveryState = getDeliveryState(r, index);
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
+
+        // If someone already finished this broadcast, finish immediately
         if (isDeliveryStateTerminal(oldDeliveryState)) {
             enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
-            return;
+            return true;
         }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
-            return;
+            return true;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
-            return;
+            return true;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
-            return;
+            return true;
         }
 
         // Ignore registered receivers from a previous PID
@@ -767,12 +794,29 @@
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
             enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
                     "BroadcastFilter for mismatched PID");
-            return;
+            return true;
         }
+        // The receiver was not handled in this method.
+        return false;
+    }
+
+    /**
+     * Return true if this receiver should be assumed to have been delivered.
+     */
+    private boolean isAssumedDelivered(BroadcastRecord r, int index) {
+        return (r.receivers.get(index) instanceof BroadcastFilter) && !r.ordered;
+    }
+
+    /**
+     * A receiver is about to be dispatched.  Start ANR timers, if necessary.
+     */
+    private void dispatchReceivers(BroadcastProcessQueue queue, BroadcastRecord r, int index) {
+        final ProcessRecord app = queue.app;
+        final Object receiver = r.receivers.get(index);
 
         // Skip ANR tracking early during boot, when requested, or when we
         // immediately assume delivery success
-        final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+        final boolean assumeDelivered = isAssumedDelivered(r, index);
         if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
@@ -805,6 +849,7 @@
         setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
                 "scheduleReceiverWarmLocked");
 
+        final Intent receiverIntent = r.getReceiverIntent(receiver);
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
             try {
@@ -920,6 +965,19 @@
         return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
     }
 
+    /**
+     * Return true if there are more broadcasts in the queue and the queue is runnable.
+     */
+    private boolean shouldContinueScheduling(@NonNull BroadcastProcessQueue queue) {
+        // If we've made reasonable progress, periodically retire ourselves to
+        // avoid starvation of other processes and stack overflow when a
+        // broadcast is immediately finished without waiting
+        final boolean shouldRetire =
+                (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+
+        return queue.isRunnable() && queue.isProcessWarm() && !shouldRetire;
+    }
+
     private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
             @DeliveryState int deliveryState, @NonNull String reason) {
         if (!queue.isActive()) {
@@ -927,10 +985,21 @@
             return false;
         }
 
-        final int cookie = traceBegin("finishReceiver");
-        final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
+        return finishReceiverLocked(queue, deliveryState, reason, r, index);
+    }
+
+    private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState, @NonNull String reason,
+            BroadcastRecord r, int index) {
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
+
+        final int cookie = traceBegin("finishReceiver");
+        final ProcessRecord app = queue.app;
         final Object receiver = r.receivers.get(index);
 
         setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
@@ -945,18 +1014,11 @@
             mLocalHandler.removeMessages(MSG_DELIVERY_TIMEOUT_HARD, queue);
         }
 
-        // If we've made reasonable progress, periodically retire ourselves to
-        // avoid starvation of other processes and stack overflow when a
-        // broadcast is immediately finished without waiting
-        final boolean shouldRetire =
-                (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
-
-        final boolean res;
-        if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
+        final boolean res = shouldContinueScheduling(queue);
+        if (res) {
             // We're on a roll; move onto the next broadcast for this process
             queue.makeActiveNextPending();
             scheduleReceiverWarmLocked(queue);
-            res = true;
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
@@ -970,7 +1032,6 @@
             // Tell other OS components that app is not actively running, giving
             // a chance to update OOM adjustment
             notifyStoppedRunning(queue);
-            res = false;
         }
         traceEnd(cookie);
         return res;
@@ -1167,6 +1228,17 @@
         return didSomething;
     }
 
+    private void forEachQueue(@NonNull Consumer<BroadcastProcessQueue> consumer) {
+        for (int i = 0; i < mProcessQueues.size(); ++i) {
+            BroadcastProcessQueue leaf = mProcessQueues.valueAt(i);
+            while (leaf != null) {
+                consumer.accept(leaf);
+                updateRunnableList(leaf);
+                leaf = leaf.processNameNext;
+            }
+        }
+    }
+
     @Override
     public void start(@NonNull ContentResolver resolver) {
         mFgConstants.startObserving(mHandler, resolver);
@@ -1225,12 +1297,19 @@
         final CountDownLatch latch = new CountDownLatch(1);
         synchronized (mService) {
             mWaitingFor.add(Pair.create(condition, latch));
+            forEachQueue(q -> q.setPrioritizeEarliest(true));
         }
         enqueueUpdateRunningList();
         try {
             latch.await();
         } catch (InterruptedException e) {
             throw new RuntimeException(e);
+        } finally {
+            synchronized (mService) {
+                if (mWaitingFor.isEmpty()) {
+                    forEachQueue(q -> q.setPrioritizeEarliest(false));
+                }
+            }
         }
     }
 
@@ -1442,10 +1521,10 @@
 
     private void notifyFinishBroadcast(@NonNull BroadcastRecord r) {
         mService.notifyBroadcastFinishedLocked(r);
-        mHistory.addBroadcastToHistoryLocked(r);
-
         r.finishTime = SystemClock.uptimeMillis();
         r.nextReceiver = r.receivers.size();
+        mHistory.addBroadcastToHistoryLocked(r);
+
         BroadcastQueueImpl.logBootCompletedBroadcastCompletionLatencyIfPossible(r);
 
         if (r.intent.getComponent() == null && r.intent.getPackage() == null
@@ -1609,6 +1688,12 @@
         ipw.decreaseIndent();
         ipw.println();
 
+        ipw.println(" Broadcasts with ignored delivery group policies:");
+        ipw.increaseIndent();
+        mService.dumpDeliveryGroupPolicyIgnoredActions(ipw);
+        ipw.decreaseIndent();
+        ipw.println();
+
         if (dumpConstants) {
             mConstants.dump(ipw);
         }
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 16055b9..d2fb7b5 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -22,6 +22,10 @@
 import static android.os.Process.PROC_SPACE_TERM;
 import static android.os.Process.SYSTEM_UID;
 
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_CHECK_URI_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_FRAMEWORK_PERMISSION;
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED;
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
 import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
@@ -46,6 +50,7 @@
 import android.content.Context;
 import android.content.IContentProvider;
 import android.content.Intent;
+import android.content.PermissionChecker;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -1003,7 +1008,11 @@
                 };
                 mService.mHandler.postDelayed(providerNotResponding, 1000);
                 try {
-                    return holder.provider.getType(uri);
+                    final String type = holder.provider.getType(uri);
+                    if (type != null) {
+                        backgroundLogging(callingUid, callingPid, userId, uri, holder, type);
+                    }
+                    return type;
                 } finally {
                     mService.mHandler.removeCallbacks(providerNotResponding);
                     // We need to clear the identity to call removeContentProviderExternalUnchecked
@@ -1060,6 +1069,10 @@
                         Binder.restoreCallingIdentity(identity);
                     }
                     resultCallback.sendResult(result);
+                    final String type = result.getPairValue();
+                    if (type != null) {
+                        backgroundLogging(callingUid, callingPid, userId, uri, holder, type);
+                    }
                 }));
             } else {
                 resultCallback.sendResult(Bundle.EMPTY);
@@ -1070,6 +1083,63 @@
         }
     }
 
+    private void backgroundLogging(int callingUid, int callingPid, int userId, Uri uri,
+            ContentProviderHolder holder, String type) {
+        // Push the logging code in a different handlerThread.
+        try {
+            mService.mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    logGetTypeData(callingUid, callingPid, userId,
+                            uri, holder, type);
+                }
+            });
+        } catch (Exception e) {
+            // To ensure logging does not break the getType calls.
+        }
+    }
+
+    // Utility function to log the getTypeData calls
+    private void logGetTypeData(int callingUid, int callingPid, int userId, Uri uri,
+            ContentProviderHolder holder, String type) {
+        try {
+            boolean checkUser = true;
+            final String authority = uri.getAuthority();
+            if (isAuthorityRedirectedForCloneProfile(authority)) {
+                UserManagerInternal umInternal =
+                        LocalServices.getService(UserManagerInternal.class);
+                UserInfo userInfo = umInternal.getUserInfo(userId);
+
+                if (userInfo != null && userInfo.isCloneProfile()) {
+                    userId = umInternal.getProfileParentId(userId);
+                    checkUser = false;
+                }
+            }
+            final ProviderInfo cpi = holder.info;
+            final AttributionSource attributionSource =
+                    new AttributionSource.Builder(callingUid).build();
+            final String permissionCheck =
+                    checkContentProviderPermission(cpi, callingPid, callingUid,
+                            userId, checkUser, null);
+            if (permissionCheck != null) {
+                FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                        GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_FRAMEWORK_PERMISSION,
+                        callingUid, authority, type);
+            } else if (cpi.forceUriPermissions
+                    && holder.provider.checkUriPermission(attributionSource,
+                            uri, callingUid, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                            != PermissionChecker.PERMISSION_GRANTED) {
+                FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                        GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_CHECK_URI_PERMISSION,
+                        callingUid, authority, type);
+            }
+        } catch (Exception e) {
+            FrameworkStatsLog.write(GET_TYPE_ACCESSED_WITHOUT_PERMISSION,
+                    GET_TYPE_ACCESSED_WITHOUT_PERMISSION__LOCATION__AM_ERROR, callingUid,
+                    uri.getAuthority(), type);
+        }
+    }
+
     private boolean canClearIdentity(int callingPid, int callingUid, int userId) {
         if (UserHandle.getUserId(callingUid) == userId) {
             return true;
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 66a8bab..02265ac2 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -163,6 +163,7 @@
     static final int OOM_ADJ_REASON_ALLOWLIST = 10;
     static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11;
     static final int OOM_ADJ_REASON_PROCESS_END = 12;
+    static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13;
 
     @IntDef(prefix = {"OOM_ADJ_REASON_"},
             value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER,
@@ -170,7 +171,8 @@
                     OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE,
                     OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER,
                     OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST,
-                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END})
+                    OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END,
+                    OOM_ADJ_REASON_SHORT_FGS_TIMEOUT})
     @Retention(RetentionPolicy.SOURCE)
     public @interface OomAdjReason {}
 
@@ -202,6 +204,7 @@
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_BEGIN;
             case OOM_ADJ_REASON_PROCESS_END:
                 return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END;
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: // TODO(short-service) add value to AppProtoEnums
             default:
                 return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO;
         }
@@ -236,6 +239,8 @@
                 return OOM_ADJ_REASON_METHOD + "_processBegin";
             case OOM_ADJ_REASON_PROCESS_END:
                 return OOM_ADJ_REASON_METHOD + "_processEnd";
+            case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT:
+                return OOM_ADJ_REASON_METHOD + "_shortFgs";
             default:
                 return "_unknown";
         }
diff --git a/services/core/java/com/android/server/am/ProcessStatsService.java b/services/core/java/com/android/server/am/ProcessStatsService.java
index 438a2d43..bac9253 100644
--- a/services/core/java/com/android/server/am/ProcessStatsService.java
+++ b/services/core/java/com/android/server/am/ProcessStatsService.java
@@ -634,7 +634,6 @@
             if (files != null) {
                 String highWaterMarkStr =
                         DateFormat.format("yyyy-MM-dd-HH-mm-ss", highWaterMarkMs).toString();
-                ProcessStats stats = new ProcessStats(false);
                 for (int i = files.size() - 1; i >= 0; i--) {
                     String fileName = files.get(i);
                     try {
@@ -647,7 +646,7 @@
                                     new File(fileName),
                                     ParcelFileDescriptor.MODE_READ_ONLY);
                             InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-                            stats.reset();
+                            final ProcessStats stats = new ProcessStats(false);
                             stats.read(is);
                             is.close();
                             if (stats.mTimePeriodStartClock > newHighWaterMark) {
diff --git a/services/core/java/com/android/server/am/SameProcessApplicationThread.java b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
index a3c0111..62fd6e9 100644
--- a/services/core/java/com/android/server/am/SameProcessApplicationThread.java
+++ b/services/core/java/com/android/server/am/SameProcessApplicationThread.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.app.IApplicationThread;
+import android.app.ReceiverInfo;
 import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -26,6 +27,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -70,4 +72,20 @@
             }
         });
     }
+
+    @Override
+    public void scheduleReceiverList(List<ReceiverInfo> info) {
+        for (int i = 0; i < info.size(); i++) {
+            ReceiverInfo r = info.get(i);
+            if (r.registered) {
+                scheduleRegisteredReceiver(r.receiver, r.intent,
+                        r.resultCode, r.data, r.extras, r.ordered, r.sticky,
+                        r.sendingUser, r.processState);
+            } else {
+                scheduleReceiver(r.intent, r.activityInfo, r.compatInfo,
+                        r.resultCode, r.data, r.extras, r.sync,
+                        r.sendingUser, r.processState);
+            }
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index ef195aa..05726f4 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1416,7 +1416,21 @@
                 || !mShortFgsInfo.isCurrent()) {
             return false;
         }
-        return mShortFgsInfo.getTimeoutTime() < SystemClock.uptimeMillis();
+        return mShortFgsInfo.getTimeoutTime() <= SystemClock.uptimeMillis();
+    }
+
+    /**
+     * @return true if it's a short FGS's procstate should be demoted.
+     */
+    public boolean shouldDemoteShortFgsProcState() {
+        if (!isAppAlive()) {
+            return false;
+        }
+        if (!this.startRequested || !isShortFgs() || mShortFgsInfo == null
+                || !mShortFgsInfo.isCurrent()) {
+            return false;
+        }
+        return mShortFgsInfo.getProcStateDemoteTime() <= SystemClock.uptimeMillis();
     }
 
     /**
@@ -1431,7 +1445,7 @@
                 || !mShortFgsInfo.isCurrent()) {
             return false;
         }
-        return mShortFgsInfo.getAnrTime() < SystemClock.uptimeMillis();
+        return mShortFgsInfo.getAnrTime() <= SystemClock.uptimeMillis();
     }
 
     private boolean isAppAlive() {
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index a7e95fb..280256f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -46,6 +46,11 @@
 import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND_VISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
+import static com.android.server.pm.UserManagerInternal.userAssignmentResultToString;
+import static com.android.server.pm.UserManagerInternal.userStartModeToString;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -119,6 +124,7 @@
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
+import com.android.server.pm.UserManagerInternal.UserStartMode;
 import com.android.server.pm.UserManagerService;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -903,14 +909,14 @@
         });
     }
 
-    int restartUser(final int userId, final boolean foreground) {
+    int restartUser(final int userId, @UserStartMode int userStartMode) {
         return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
                 /* stopUserCallback= */ null, new KeyEvictedCallback() {
                     @Override
                     public void keyEvicted(@UserIdInt int userId) {
                         // Post to the same handler that this callback is called from to ensure
                         // the user cleanup is complete before restarting.
-                        mHandler.post(() -> UserController.this.startUser(userId, foreground));
+                        mHandler.post(() -> UserController.this.startUser(userId, userStartMode));
                     }
                 });
     }
@@ -949,9 +955,8 @@
     int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
             final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
-        if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
-            throw new IllegalArgumentException("Can't stop system user " + userId);
-        }
+        Preconditions.checkArgument(userId >= 0, "Invalid user id %d", userId);
+
         enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
             return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
@@ -1268,7 +1273,7 @@
                 if (userStart.userId == userId) {
                     Slogf.i(TAG, "resumePendingUserStart for" + userStart);
                     mHandler.post(() -> startUser(userStart.userId,
-                            userStart.isForeground, userStart.unlockListener));
+                            userStart.userStartMode, userStart.unlockListener));
 
                     handledUserStarts.add(userStart);
                 }
@@ -1451,7 +1456,7 @@
         for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
             // NOTE: this method is setting the profiles of the current user - which is always
             // assigned to the default display
-            startUser(profilesToStart.get(i).id, /* foreground= */ false);
+            startUser(profilesToStart.get(i).id, USER_START_MODE_BACKGROUND_VISIBLE);
         }
         if (i < profilesToStartSize) {
             Slogf.w(TAG, "More profiles than MAX_RUNNING_USERS");
@@ -1493,18 +1498,20 @@
             return false;
         }
 
-        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, /* foreground= */ false,
-                /* unlockListener= */ null);
+        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY,
+                USER_START_MODE_BACKGROUND_VISIBLE, /* unlockListener= */ null);
     }
 
     @VisibleForTesting
-    boolean startUser(final @UserIdInt int userId, final boolean foreground) {
-        return startUser(userId, foreground, null);
+    boolean startUser(@UserIdInt int userId, @UserStartMode int userStartMode) {
+        return startUser(userId, userStartMode, /* unlockListener= */ null);
     }
 
     /**
      * Start user, if its not already running.
-     * <p>The user will be brought to the foreground, if {@code foreground} parameter is set.
+     *
+     * <p>The user will be brought to the foreground, if {@code userStartMode} parameter is
+     * set to {@link UserManagerInternal#USER_START_MODE_FOREGROUND}
      * When starting the user, multiple intents will be broadcast in the following order:</p>
      * <ul>
      *     <li>{@link Intent#ACTION_USER_STARTED} - sent to registered receivers of the new user
@@ -1530,17 +1537,15 @@
      * </ul>
      *
      * @param userId ID of the user to start
-     * @param foreground true if user should be brought to the foreground
+     * @param userStartMode user starting mode
      * @param unlockListener Listener to be informed when the user has started and unlocked.
      * @return true if the user has been successfully started
      */
-    boolean startUser(
-            final @UserIdInt int userId,
-            final boolean foreground,
+    boolean startUser(@UserIdInt int userId, @UserStartMode int userStartMode,
             @Nullable IProgressListener unlockListener) {
         checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "startUser");
 
-        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, foreground, unlockListener);
+        return startUserNoChecks(userId, Display.DEFAULT_DISPLAY, userStartMode, unlockListener);
     }
 
     /**
@@ -1573,7 +1578,7 @@
                 "Cannot use DEFAULT_DISPLAY");
 
         try {
-            return startUserNoChecks(userId, displayId, /* foreground= */ false,
+            return startUserNoChecks(userId, displayId, USER_START_MODE_BACKGROUND_VISIBLE,
                     /* unlockListener= */ null);
         } catch (RuntimeException e) {
             Slogf.e(TAG, "startUserOnSecondaryDisplay(%d, %d) failed: %s", userId, displayId, e);
@@ -1581,26 +1586,29 @@
         }
     }
 
-    private boolean startUserNoChecks(@UserIdInt int userId, int displayId, boolean foreground,
-            @Nullable IProgressListener unlockListener) {
+    private boolean startUserNoChecks(@UserIdInt int userId, int displayId,
+            @UserStartMode int userStartMode, @Nullable IProgressListener unlockListener) {
         TimingsTraceAndSlog t = new TimingsTraceAndSlog();
 
         t.traceBegin("UserController.startUser-" + userId
                 + (displayId == Display.DEFAULT_DISPLAY ? "" : "-display-" + displayId)
-                + "-" + (foreground ? "fg" : "bg"));
+                + "-" + (userStartMode == USER_START_MODE_FOREGROUND ? "fg" : "bg")
+                + "-start-mode-" + userStartMode);
         try {
-            return startUserInternal(userId, displayId, foreground, unlockListener, t);
+            return startUserInternal(userId, displayId, userStartMode, unlockListener, t);
         } finally {
             t.traceEnd();
         }
     }
 
-    private boolean startUserInternal(@UserIdInt int userId, int displayId, boolean foreground,
-            @Nullable IProgressListener unlockListener, @NonNull TimingsTraceAndSlog t) {
+    private boolean startUserInternal(@UserIdInt int userId, int displayId,
+            @UserStartMode int userStartMode, @Nullable IProgressListener unlockListener,
+            TimingsTraceAndSlog t) {
         if (DEBUG_MU) {
-            Slogf.i(TAG, "Starting user %d on display %d%s", userId, displayId,
-                    foreground ? " in foreground" : "");
+            Slogf.i(TAG, "Starting user %d on display %d with mode  %s", userId, displayId,
+                    userStartModeToString(userStartMode));
         }
+        boolean foreground = userStartMode == USER_START_MODE_FOREGROUND;
 
         boolean onSecondaryDisplay = displayId != Display.DEFAULT_DISPLAY;
         if (onSecondaryDisplay) {
@@ -1665,13 +1673,13 @@
 
             t.traceBegin("assignUserToDisplayOnStart");
             int result = mInjector.getUserManagerInternal().assignUserToDisplayOnStart(userId,
-                    userInfo.profileGroupId, foreground, displayId);
+                    userInfo.profileGroupId, userStartMode, displayId);
             t.traceEnd();
 
-            if (result == UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE) {
+            if (result == USER_ASSIGNMENT_RESULT_FAILURE) {
                 Slogf.e(TAG, "%s user(%d) / display (%d) assignment failed: %s",
-                        (foreground ? "fg" : "bg"), userId, displayId,
-                        UserManagerInternal.userAssignmentResultToString(result));
+                        userStartModeToString(userStartMode), userId, displayId,
+                        userAssignmentResultToString(result));
                 return false;
             }
 
@@ -1701,8 +1709,8 @@
                 } else if (uss.state == UserState.STATE_SHUTDOWN) {
                     Slogf.i(TAG, "User #" + userId
                             + " is shutting down - will start after full shutdown");
-                    mPendingUserStarts.add(new PendingUserStart(userId,
-                            foreground, unlockListener));
+                    mPendingUserStarts.add(new PendingUserStart(userId, userStartMode,
+                            unlockListener));
                     t.traceEnd(); // updateStartedUserArrayStarting
                     return true;
                 }
@@ -1863,8 +1871,8 @@
     /**
      * Start user, if its not already running, and bring it to foreground.
      */
-    void startUserInForeground(final int targetUserId) {
-        boolean success = startUser(targetUserId, /* foreground */ true);
+    void startUserInForeground(@UserIdInt int targetUserId) {
+        boolean success = startUser(targetUserId, USER_START_MODE_FOREGROUND);
         if (!success) {
             mInjector.getWindowManager().setSwitchingUser(false);
         }
@@ -3434,13 +3442,13 @@
      */
     private static class PendingUserStart {
         public final @UserIdInt int userId;
-        public final boolean isForeground;
+        public final @UserStartMode int userStartMode;
         public final IProgressListener unlockListener;
 
-        PendingUserStart(int userId, boolean foreground,
+        PendingUserStart(int userId, @UserStartMode int userStartMode,
                 IProgressListener unlockListener) {
             this.userId = userId;
-            this.isForeground = foreground;
+            this.userStartMode = userStartMode;
             this.unlockListener = unlockListener;
         }
 
@@ -3448,7 +3456,7 @@
         public String toString() {
             return "PendingUserStart{"
                     + "userId=" + userId
-                    + ", isForeground=" + isForeground
+                    + ", userStartMode=" + userStartModeToString(userStartMode)
                     + ", unlockListener=" + unlockListener
                     + '}';
         }
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index cda18b0..46d3ff1 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -524,8 +524,8 @@
         private final ArrayMap<Integer, GameModeConfiguration> mModeConfigs = new ArrayMap<>();
         // if adding new properties or make any of the below overridable, the method
         // copyAndApplyOverride should be updated accordingly
-        private boolean mPerfModeOptedIn = false;
-        private boolean mBatteryModeOptedIn = false;
+        private boolean mPerfModeOverridden = false;
+        private boolean mBatteryModeOverridden = false;
         private boolean mAllowDownscale = true;
         private boolean mAllowAngle = true;
         private boolean mAllowFpsOverride = true;
@@ -542,8 +542,8 @@
                         PackageManager.GET_META_DATA, userId);
                 if (!parseInterventionFromXml(packageManager, ai, packageName)
                             && ai.metaData != null) {
-                    mPerfModeOptedIn = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
-                    mBatteryModeOptedIn = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
+                    mPerfModeOverridden = ai.metaData.getBoolean(METADATA_PERFORMANCE_MODE_ENABLE);
+                    mBatteryModeOverridden = ai.metaData.getBoolean(METADATA_BATTERY_MODE_ENABLE);
                     mAllowDownscale = ai.metaData.getBoolean(METADATA_WM_ALLOW_DOWNSCALE, true);
                     mAllowAngle = ai.metaData.getBoolean(METADATA_ANGLE_ALLOW_ANGLE, true);
                 }
@@ -595,9 +595,9 @@
                     } else {
                         final TypedArray array = resources.obtainAttributes(attributeSet,
                                 com.android.internal.R.styleable.GameModeConfig);
-                        mPerfModeOptedIn = array.getBoolean(
+                        mPerfModeOverridden = array.getBoolean(
                                 GameModeConfig_supportsPerformanceGameMode, false);
-                        mBatteryModeOptedIn = array.getBoolean(
+                        mBatteryModeOverridden = array.getBoolean(
                                 GameModeConfig_supportsBatteryGameMode,
                                 false);
                         mAllowDownscale = array.getBoolean(GameModeConfig_allowGameDownscaling,
@@ -610,8 +610,8 @@
                 }
             } catch (NameNotFoundException | XmlPullParserException | IOException ex) {
                 // set flag back to default values when parsing fails
-                mPerfModeOptedIn = false;
-                mBatteryModeOptedIn = false;
+                mPerfModeOverridden = false;
+                mBatteryModeOverridden = false;
                 mAllowDownscale = true;
                 mAllowAngle = true;
                 mAllowFpsOverride = true;
@@ -667,8 +667,8 @@
 
             GameModeConfiguration(KeyValueListParser parser) {
                 mGameMode = parser.getInt(MODE_KEY, GameManager.GAME_MODE_UNSUPPORTED);
-                // isGameModeOptedIn() returns if an app will handle all of the changes necessary
-                // for a particular game mode. If so, the Android framework (i.e.
+                // willGamePerformOptimizations() returns if an app will handle all of the changes
+                // necessary for a particular game mode. If so, the Android framework (i.e.
                 // GameManagerService) will not do anything for the app (like window scaling or
                 // using ANGLE).
                 mScaling = !mAllowDownscale || willGamePerformOptimizations(mGameMode)
@@ -775,8 +775,8 @@
          * "com.android.app.gamemode.battery.enabled" with a value of "true"
          */
         public boolean willGamePerformOptimizations(@GameMode int gameMode) {
-            return (mBatteryModeOptedIn && gameMode == GameManager.GAME_MODE_BATTERY)
-                    || (mPerfModeOptedIn && gameMode == GameManager.GAME_MODE_PERFORMANCE);
+            return (mBatteryModeOverridden && gameMode == GameManager.GAME_MODE_BATTERY)
+                    || (mPerfModeOverridden && gameMode == GameManager.GAME_MODE_PERFORMANCE);
         }
 
         private int getAvailableGameModesBitfield() {
@@ -787,10 +787,10 @@
                     field |= modeToBitmask(mode);
                 }
             }
-            if (mBatteryModeOptedIn) {
+            if (mBatteryModeOverridden) {
                 field |= modeToBitmask(GameManager.GAME_MODE_BATTERY);
             }
-            if (mPerfModeOptedIn) {
+            if (mPerfModeOverridden) {
                 field |= modeToBitmask(GameManager.GAME_MODE_PERFORMANCE);
             }
             return field;
@@ -814,14 +814,14 @@
         }
 
         /**
-         * Get an array of a package's opted-in game modes.
+         * Get an array of a package's overridden game modes.
          */
-        public @GameMode int[] getOptedInGameModes() {
-            if (mBatteryModeOptedIn && mPerfModeOptedIn) {
+        public @GameMode int[] getOverriddenGameModes() {
+            if (mBatteryModeOverridden && mPerfModeOverridden) {
                 return new int[]{GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_PERFORMANCE};
-            } else if (mBatteryModeOptedIn) {
+            } else if (mBatteryModeOverridden) {
                 return new int[]{GameManager.GAME_MODE_BATTERY};
-            } else if (mPerfModeOptedIn) {
+            } else if (mPerfModeOverridden) {
                 return new int[]{GameManager.GAME_MODE_PERFORMANCE};
             } else {
                 return new int[]{};
@@ -864,18 +864,18 @@
 
         public boolean isActive() {
             synchronized (mModeConfigLock) {
-                return mModeConfigs.size() > 0 || mBatteryModeOptedIn || mPerfModeOptedIn;
+                return mModeConfigs.size() > 0 || mBatteryModeOverridden || mPerfModeOverridden;
             }
         }
 
         GamePackageConfiguration copyAndApplyOverride(GamePackageConfiguration overrideConfig) {
             GamePackageConfiguration copy = new GamePackageConfiguration(mPackageName);
             // if a game mode is overridden, we treat it with the highest priority and reset any
-            // opt-in game modes so that interventions are always executed.
-            copy.mPerfModeOptedIn = mPerfModeOptedIn && !(overrideConfig != null
+            // overridden game modes so that interventions are always executed.
+            copy.mPerfModeOverridden = mPerfModeOverridden && !(overrideConfig != null
                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE)
                     != null);
-            copy.mBatteryModeOptedIn = mBatteryModeOptedIn && !(overrideConfig != null
+            copy.mBatteryModeOverridden = mBatteryModeOverridden && !(overrideConfig != null
                     && overrideConfig.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY)
                     != null);
 
@@ -1092,12 +1092,12 @@
         final @GameMode int activeGameMode = getGameModeFromSettingsUnchecked(packageName, userId);
         final GamePackageConfiguration config = getConfig(packageName, userId);
         if (config != null) {
-            final @GameMode int[] optedInGameModes = config.getOptedInGameModes();
+            final @GameMode int[] overriddenGameModes = config.getOverriddenGameModes();
             final @GameMode int[] availableGameModes = config.getAvailableGameModes();
             GameModeInfo.Builder gameModeInfoBuilder = new GameModeInfo.Builder()
                     .setActiveGameMode(activeGameMode)
                     .setAvailableGameModes(availableGameModes)
-                    .setOptedInGameModes(optedInGameModes)
+                    .setOverriddenGameModes(overriddenGameModes)
                     .setDownscalingAllowed(config.mAllowDownscale)
                     .setFpsOverrideAllowed(config.mAllowFpsOverride);
             for (int gameMode : availableGameModes) {
@@ -2059,7 +2059,7 @@
                 if (atomTag == FrameworkStatsLog.GAME_MODE_INFO) {
                     data.add(
                             FrameworkStatsLog.buildStatsEvent(FrameworkStatsLog.GAME_MODE_INFO, uid,
-                                    gameModesToStatsdGameModes(config.getOptedInGameModes()),
+                                    gameModesToStatsdGameModes(config.getOverriddenGameModes()),
                                     gameModesToStatsdGameModes(config.getAvailableGameModes())));
                 } else if (atomTag == FrameworkStatsLog.GAME_MODE_CONFIGURATION) {
                     for (int gameMode : config.getAvailableGameModes()) {
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
index 8d1da71..587fb04 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceImpl.java
@@ -218,7 +218,7 @@
     }
 
     @Override
-    public boolean arePackageModesDefault(String packageMode, @UserIdInt int userId) {
+    public boolean arePackageModesDefault(@NonNull String packageMode, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             if (packageModes == null) {
@@ -490,15 +490,16 @@
     }
 
     @Override
-    public SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps) {
+    public SparseBooleanArray evalForegroundUidOps(int uid,
+            @Nullable SparseBooleanArray foregroundOps) {
         synchronized (mLock) {
             return evalForegroundOps(mUidModes.get(uid), foregroundOps);
         }
     }
 
     @Override
-    public SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId) {
+    public SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId) {
         synchronized (mLock) {
             ArrayMap<String, SparseIntArray> packageModes = mUserPackageModes.get(userId, null);
             return evalForegroundOps(packageModes == null ? null : packageModes.get(packageName),
@@ -537,8 +538,8 @@
     }
 
     @Override
-    public boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage,
-            PrintWriter printWriter) {
+    public boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter) {
         boolean needSep = false;
         if (mOpModeWatchers.size() > 0) {
             boolean printedHeader = false;
diff --git a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
index d8d0d48..ef3e368 100644
--- a/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceInterface.java
@@ -103,7 +103,7 @@
      * @param packageName package name.
      * @param userId user id associated with the package.
      */
-    boolean arePackageModesDefault(String packageName, @UserIdInt int userId);
+    boolean arePackageModesDefault(@NonNull String packageName, @UserIdInt int userId);
 
     /**
      * Stop tracking app-op modes for all uid and packages.
@@ -184,7 +184,7 @@
      * @param foregroundOps boolean array where app-ops that have MODE_FOREGROUND are marked true.
      * @return  foregroundOps.
      */
-    SparseBooleanArray evalForegroundUidOps(int uid, SparseBooleanArray foregroundOps);
+    SparseBooleanArray evalForegroundUidOps(int uid, @Nullable SparseBooleanArray foregroundOps);
 
     /**
      * Go over the list of app-ops for the package name and mark app-ops with MODE_FOREGROUND in
@@ -194,8 +194,8 @@
      * @param userId user id associated with the package.
      * @return foregroundOps.
      */
-    SparseBooleanArray evalForegroundPackageOps(String packageName,
-            SparseBooleanArray foregroundOps, @UserIdInt int userId);
+    SparseBooleanArray evalForegroundPackageOps(@NonNull String packageName,
+            @Nullable SparseBooleanArray foregroundOps, @UserIdInt int userId);
 
     /**
      * Dump op mode and package mode listeners and their details.
@@ -205,5 +205,6 @@
      * @param dumpPackage if not null and if dumpOp is -1, dumps watchers for the package name.
      * @param printWriter writer to dump to.
      */
-    boolean dumpListeners(int dumpOp, int dumpUid, String dumpPackage, PrintWriter printWriter);
+    boolean dumpListeners(int dumpOp, int dumpUid, @Nullable String dumpPackage,
+            @NonNull PrintWriter printWriter);
 }
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..4436002
--- /dev/null
+++ b/services/core/java/com/android/server/appop/AppOpsCheckingServiceTracingDecorator.java
@@ -0,0 +1,279 @@
+/*
+ * 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 SparseIntArray getNonDefaultUidModes(int uid) {
+        Trace.traceBegin(TRACE_TAG,
+                "TaggedTracingAppOpsCheckingServiceInterfaceImpl#getNonDefaultUidModes");
+        try {
+            return mService.getNonDefaultUidModes(uid);
+        } 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/AppOpsServiceImpl.java b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
index 70f3bcc..c3d2717 100644
--- a/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsServiceImpl.java
@@ -44,6 +44,7 @@
 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;
@@ -130,6 +131,8 @@
 import com.android.server.LocalServices;
 import com.android.server.LockGuard;
 import com.android.server.SystemServerInitThreadPool;
+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.component.ParsedAttribution;
 
@@ -163,12 +166,30 @@
     static final String TAG = "AppOps";
     static final boolean DEBUG = false;
 
+    /**
+     * 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
+     * {@link #upgradeLocked(int)} below. The first version was 1.
      */
-    private static final int CURRENT_VERSION = 1;
+    @VisibleForTesting
+    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;
 
     // Write at most every 30 minutes.
     static final long WRITE_DELAY = DEBUG ? 1000 : 30 * 60 * 1000;
@@ -828,8 +849,8 @@
             mSwitchedOps.put(switchCode,
                     ArrayUtils.appendInt(mSwitchedOps.get(switchCode), switchedCode));
         }
-        mAppOpsServiceInterface =
-                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps);
+        mAppOpsServiceInterface = new AppOpsCheckingServiceTracingDecorator(
+                new AppOpsCheckingServiceImpl(this, this, handler, context, mSwitchedOps));
         mAppOpsRestrictions = new AppOpsRestrictionsImpl(context, handler,
                 mAppOpsServiceInterface);
 
@@ -936,6 +957,10 @@
 
     @Override
     public void systemReady() {
+        synchronized (this) {
+            upgradeLocked(mVersionAtBoot);
+        }
+
         mConstants.startMonitoring(mContext.getContentResolver());
         mHistoricalRegistry.systemReady(mContext.getContentResolver());
 
@@ -3191,7 +3216,6 @@
 
     @Override
     public void readState() {
-        int oldVersion = NO_VERSION;
         synchronized (mFile) {
             synchronized (this) {
                 FileInputStream stream;
@@ -3216,7 +3240,7 @@
                         throw new IllegalStateException("no start tag found");
                     }
 
-                    oldVersion = parser.getAttributeInt(null, "v", NO_VERSION);
+                    mVersionAtBoot = parser.getAttributeInt(null, "v", NO_VERSION);
 
                     int outerDepth = parser.getDepth();
                     while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -3261,12 +3285,11 @@
                 }
             }
         }
-        synchronized (this) {
-            upgradeLocked(oldVersion);
-        }
     }
 
-    private void upgradeRunAnyInBackgroundLocked() {
+    @VisibleForTesting
+    @GuardedBy("this")
+    void upgradeRunAnyInBackgroundLocked() {
         for (int i = 0; i < mUidStates.size(); i++) {
             final UidState uidState = mUidStates.valueAt(i);
             if (uidState == null) {
@@ -3303,8 +3326,45 @@
         }
     }
 
+    /**
+     * 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.
+        }
+    }
+
+    @GuardedBy("this")
     private void upgradeLocked(int oldVersion) {
-        if (oldVersion >= CURRENT_VERSION) {
+        if (oldVersion == NO_FILE_VERSION || oldVersion >= CURRENT_VERSION) {
             return;
         }
         Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION);
@@ -3313,6 +3373,9 @@
                 upgradeRunAnyInBackgroundLocked();
                 // fall through
             case 1:
+                upgradeScheduleExactAlarmLocked();
+                // fall through
+            case 2:
                 // for future upgrades
         }
         scheduleFastWriteLocked();
diff --git a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
index 5ebe811..1d1a9e7 100644
--- a/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
+++ b/services/core/java/com/android/server/appop/OnOpModeChangedListener.java
@@ -22,7 +22,7 @@
  * Listener for mode changes, encapsulates methods that should be triggered in the event of a mode
  * change.
  */
-abstract class OnOpModeChangedListener {
+public abstract class OnOpModeChangedListener {
 
     // Constant meaning that any UID should be matched when dispatching callbacks
     private static final int UID_ANY = -2;
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 2fe06094..418027f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -1998,4 +1998,13 @@
             return mDeviceInventory.getDeviceSensorUuid(device);
         }
     }
+
+    void dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(AudioDeviceInfo info) {
+        // Currently, only media usage will be allowed to set preferred mixer attributes
+        mAudioService.dispatchPreferredMixerAttributesChanged(
+                new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_MEDIA).build(),
+                info.getId(),
+                null /*mixerAttributes*/);
+    }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index c8f282f..34457b0 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -22,6 +22,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioDevicePort;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -532,6 +533,18 @@
                 .set(MediaMetrics.Property.STATE,
                         wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
                                 ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
+        AudioDeviceInfo info = null;
+        if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
+                && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
+                        wdcs.mAttributes.getInternalType())) {
+            for (AudioDeviceInfo deviceInfo : AudioManager.getDevicesStatic(
+                    AudioManager.GET_DEVICES_OUTPUTS)) {
+                if (deviceInfo.getInternalType() == wdcs.mAttributes.getInternalType()) {
+                    info = deviceInfo;
+                    break;
+                }
+            }
+        }
         synchronized (mDevicesLock) {
             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
                     && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(type)) {
@@ -556,6 +569,11 @@
             if (type == AudioSystem.DEVICE_OUT_HDMI) {
                 mDeviceBroker.checkVolumeCecOnHdmiConnection(wdcs.mState, wdcs.mCaller);
             }
+            if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
+                    && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains(
+                            wdcs.mAttributes.getInternalType())) {
+                mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info);
+            }
             sendDeviceConnectionIntent(type, wdcs.mState,
                     wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName());
             updateAudioRoutes(type, wdcs.mState);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 8aa898e..66770cb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -27,6 +27,7 @@
 import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
 import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
 
+import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE;
 import static com.android.server.utils.EventLogger.Event.ALOGE;
 import static com.android.server.utils.EventLogger.Event.ALOGI;
 import static com.android.server.utils.EventLogger.Event.ALOGW;
@@ -42,12 +43,10 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
-import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
 import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
 import android.bluetooth.BluetoothAdapter;
@@ -86,8 +85,10 @@
 import android.media.AudioFocusInfo;
 import android.media.AudioFocusRequest;
 import android.media.AudioFormat;
+import android.media.AudioHalVersionInfo;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
+import android.media.AudioMixerAttributes;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
@@ -104,6 +105,7 @@
 import android.media.IDeviceVolumeBehaviorDispatcher;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
+import android.media.IPreferredMixerAttributesDispatcher;
 import android.media.IRecordingConfigDispatcher;
 import android.media.IRingtonePlayer;
 import android.media.ISpatializerCallback;
@@ -165,7 +167,6 @@
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
-import android.util.MathUtils;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -257,6 +258,9 @@
     /** Debug communication route */
     protected static final boolean DEBUG_COMM_RTE = false;
 
+    /** Debug log sound fx (touchsounds...) in dumpsys */
+    protected static final boolean DEBUG_LOG_SOUND_FX = false;
+
     /** How long to delay before persisting a change in volume/ringer mode. */
     private static final int PERSIST_DELAY = 500;
 
@@ -328,7 +332,7 @@
     private static final int SENDMSG_QUEUE = 2;
 
     // AudioHandler messages
-    private static final int MSG_SET_DEVICE_VOLUME = 0;
+    /*package*/ static final int MSG_SET_DEVICE_VOLUME = 0;
     private static final int MSG_PERSIST_VOLUME = 1;
     private static final int MSG_PERSIST_VOLUME_GROUP = 2;
     private static final int MSG_PERSIST_RINGER_MODE = 3;
@@ -338,13 +342,8 @@
     private static final int MSG_SET_FORCE_USE = 8;
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
     private static final int MSG_SET_ALL_VOLUMES = 10;
-    private static final int MSG_CHECK_MUSIC_ACTIVE = 11;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = 12;
-    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED = 13;
-    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = 14;
     private static final int MSG_UNLOAD_SOUND_EFFECTS = 15;
     private static final int MSG_SYSTEM_READY = 16;
-    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = 17;
     private static final int MSG_UNMUTE_STREAM = 18;
     private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 19;
     private static final int MSG_INDICATE_SYSTEM_READY = 20;
@@ -378,6 +377,11 @@
     private static final int MSG_ROTATION_UPDATE = 48;
     private static final int MSG_FOLD_UPDATE = 49;
     private static final int MSG_RESET_SPATIALIZER = 50;
+    private static final int MSG_NO_LOG_FOR_PLAYER_I = 51;
+    private static final int MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES = 52;
+
+    /** Messages handled by the {@link SoundDoseHelper}. */
+    /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
 
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
@@ -394,6 +398,9 @@
     // List of empty UIDs used to reset the active assistant list
     private static final int[] NO_ACTIVE_ASSISTANT_SERVICE_UIDS = new int[0];
 
+    // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
+    private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
+
     /** @see AudioSystemThread */
     private AudioSystemThread mAudioSystemThread;
     /** @see AudioHandler */
@@ -405,6 +412,10 @@
         return mStreamStates[stream].getIndex(device);
     }
 
+    /*package*/ VolumeStreamState getVssVolumeForStream(int stream) {
+        return mStreamStates[stream];
+    }
+
     /*package*/ int getMaxVssVolumeForStream(int stream) {
         return mStreamStates[stream].getMaxIndex();
     }
@@ -833,11 +844,6 @@
 
     private int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
 
-    // Used when safe volume warning message display is requested by setStreamVolume(). In this
-    // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
-    // and used later when/if disableSafeMediaVolume() is called.
-    private StreamVolumeCommand mPendingVolumeCommand;
-
     private PowerManager.WakeLock mAudioEventWakeLock;
 
     private final MediaFocusControl mMediaFocusControl;
@@ -889,6 +895,8 @@
     private boolean mNavigationRepeatSoundEffectsEnabled;
     private boolean mHomeSoundEffectEnabled;
 
+    private final SoundDoseHelper mSoundDoseHelper;
+
     @GuardedBy("mSettingsLock")
     private int mCurrentImeUid;
 
@@ -1016,7 +1024,7 @@
         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mAudioEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleAudioEvent");
 
-        mSfxHelper = new SoundEffectsHelper(mContext);
+        mSfxHelper = new SoundEffectsHelper(mContext, playerBase -> ignorePlayerLogs(playerBase));
 
         final boolean headTrackingDefault = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_spatial_audio_head_tracking_enabled_default);
@@ -1179,6 +1187,9 @@
             mAudioHandler = new AudioHandler(looper);
         }
 
+        mSoundDoseHelper = new SoundDoseHelper(this, mContext, mAudioHandler, mSettings,
+                mVolumeController);
+
         AudioSystem.setErrorCallback(mAudioSystemCallback);
 
         updateAudioHalPids();
@@ -1194,16 +1205,6 @@
                 new String("AudioService ctor"),
                 0);
 
-        mSafeMediaVolumeState = mSettings.getGlobalInt(mContentResolver,
-                                            Settings.Global.AUDIO_SAFE_VOLUME_STATE,
-                                            SAFE_MEDIA_VOLUME_NOT_CONFIGURED);
-        // The default safe volume index read here will be replaced by the actual value when
-        // the mcc is read by onConfigureSafeVolume()
-        mSafeMediaVolumeIndex = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_safe_media_volume_index) * 10;
-
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-
         mUseFixedVolume = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_useFixedVolume);
 
@@ -1280,9 +1281,7 @@
         // persistent data
         initVolumeGroupStates();
 
-        // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
-        // relies on audio policy having correct ranges for volume indexes.
-        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+        mSoundDoseHelper.initSafeUsbMediaVolumeIndex();
 
         // Call setRingerModeInt() to apply correct mute
         // state on streams affected by ringer mode.
@@ -1437,14 +1436,7 @@
 
         mNm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        sendMsg(mAudioHandler,
-                MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED,
-                SENDMSG_REPLACE,
-                0,
-                0,
-                TAG,
-                SystemProperties.getBoolean("audio.safemedia.bypass", false) ?
-                        0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
+        mSoundDoseHelper.configureSafeMediaVolume(/*forced=*/true, TAG);
 
         initA11yMonitoring();
 
@@ -1500,6 +1492,18 @@
     }
 
     //-----------------------------------------------------------------
+    // Communicate to PlayackActivityMonitor whether to log or not
+    // the sound FX activity (useful for removing touch sounds in the activity logs)
+    void ignorePlayerLogs(@NonNull PlayerBase playerToIgnore) {
+        if (DEBUG_LOG_SOUND_FX) {
+            return;
+        }
+        sendMsg(mAudioHandler, MSG_NO_LOG_FOR_PLAYER_I, SENDMSG_REPLACE,
+                /*arg1, piid of the player*/ playerToIgnore.getPlayerIId(),
+                /*arg2 ignored*/ 0, /*obj ignored*/ null, /*delay*/ 0);
+    }
+
+    //-----------------------------------------------------------------
     // monitoring requests for volume range initialization
     @Override // AudioSystemAdapter.OnVolRangeInitRequestListener
     public void onVolumeRangeInitRequestFromNative() {
@@ -1970,8 +1974,8 @@
             @AudioService.ConnectionState int state, String caller) {
         if (state == AudioService.CONNECTION_STATE_CONNECTED) {
             // DEVICE_OUT_HDMI is now connected
-            if (mSafeMediaVolumeDevices.contains(AudioSystem.DEVICE_OUT_HDMI)) {
-                scheduleMusicActiveCheck();
+            if (mSoundDoseHelper.safeDevicesContains(AudioSystem.DEVICE_OUT_HDMI)) {
+                mSoundDoseHelper.scheduleMusicActiveCheck();
             }
 
             if (isPlatformTelevision()) {
@@ -3266,10 +3270,7 @@
             return;
         }
 
-        // reset any pending volume command
-        synchronized (mSafeMediaVolumeStateLock) {
-            mPendingVolumeCommand = null;
-        }
+        mSoundDoseHelper.invalidatPendingVolumeCommand();
 
         flags &= ~AudioManager.FLAG_FIXED_VOLUME;
         if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
@@ -3278,10 +3279,8 @@
             // Always toggle between max safe volume and 0 for fixed volume devices where safe
             // volume is enforced, and max and 0 for the others.
             // This is simulated by stepping by the full allowed volume range
-            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
-                    mSafeMediaVolumeDevices.contains(device)) {
-                step = safeMediaVolumeIndex(device);
-            } else {
+            step = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+            if (step < 0) {
                 step = streamState.getMaxIndex();
             }
             if (aliasIndex != 0) {
@@ -3354,10 +3353,10 @@
                         }
                     }
                 }
-            } else if ((direction == AudioManager.ADJUST_RAISE) &&
-                    !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) {
+            } else if ((direction == AudioManager.ADJUST_RAISE)
+                    && mSoundDoseHelper.raiseVolumeDisplaySafeMediaVolume(streamTypeAlias,
+                            aliasIndex + step, device, flags)) {
                 Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex);
-                mVolumeController.postDisplaySafeVolumeWarning(flags);
             } else if (!isFullVolumeDevice(device)
                     && (streamState.adjustIndex(direction * step, device, caller,
                             hasModifyAudioSettings)
@@ -3528,29 +3527,6 @@
         Binder.restoreCallingIdentity(identity);
     }
 
-    // StreamVolumeCommand contains the information needed to defer the process of
-    // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
-    static class StreamVolumeCommand {
-        public final int mStreamType;
-        public final int mIndex;
-        public final int mFlags;
-        public final int mDevice;
-
-        StreamVolumeCommand(int streamType, int index, int flags, int device) {
-            mStreamType = streamType;
-            mIndex = index;
-            mFlags = flags;
-            mDevice = device;
-        }
-
-        @Override
-        public String toString() {
-            return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
-                    .append(mIndex).append(",flags=").append(mFlags).append(",device=")
-                    .append(mDevice).append('}').toString();
-        }
-    }
-
     private int getNewRingerMode(int stream, int index, int flags) {
         // setRingerMode does nothing if the device is single volume,so the value would be unchanged
         if (mIsSingleVolume) {
@@ -3598,7 +3574,7 @@
         return false;
     }
 
-    private void onSetStreamVolume(int streamType, int index, int flags, int device,
+     /*package*/ void onSetStreamVolume(int streamType, int index, int flags, int device,
             String caller, boolean hasModifyAudioSettings) {
         final int stream = mStreamVolumeAlias[streamType];
         setStreamVolumeInt(stream, index, device, false, caller, hasModifyAudioSettings);
@@ -3942,7 +3918,7 @@
             updateHearingAidVolumeOnVoiceActivityUpdate();
         }
         if (mMediaPlaybackActive.getAndSet(mediaActive) != mediaActive && mediaActive) {
-            scheduleMusicActiveCheck();
+            mSoundDoseHelper.scheduleMusicActiveCheck();
         }
         // Update playback active state for all apps in audio mode stack.
         // When the audio mode owner becomes active, replace any delayed MSG_UPDATE_AUDIO_MODE
@@ -4188,71 +4164,64 @@
             return;
         }
 
-        synchronized (mSafeMediaVolumeStateLock) {
-            // reset any pending volume command
-            mPendingVolumeCommand = null;
+        mSoundDoseHelper.invalidatPendingVolumeCommand();
 
-            oldIndex = streamState.getIndex(device);
+        oldIndex = streamState.getIndex(device);
 
-            index = rescaleIndex(index * 10, streamType, streamTypeAlias);
+        index = rescaleIndex(index * 10, streamType, streamTypeAlias);
 
-            if (streamTypeAlias == AudioSystem.STREAM_MUSIC
-                    && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
-                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                if (DEBUG_VOL) {
-                    Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
-                            + "stream=" + streamType);
+        if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+                && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
+                && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+            if (DEBUG_VOL) {
+                Log.d(TAG, "setStreamVolume postSetAvrcpAbsoluteVolumeIndex index=" + index
+                        + "stream=" + streamType);
+            }
+            mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
+        } else if (isAbsoluteVolumeDevice(device)
+                && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
+            AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
+
+            dispatchAbsoluteVolumeChanged(streamType, info, index);
+        }
+
+        if (AudioSystem.isLeAudioDeviceType(device)
+                && streamType == getBluetoothContextualVolumeStream()
+                && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
+            if (DEBUG_VOL) {
+                Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
+                        + index + " stream=" + streamType);
+            }
+            mDeviceBroker.postSetLeAudioVolumeIndex(index, mStreamStates[streamType].getMaxIndex(),
+                    streamType);
+        }
+
+        if (device == AudioSystem.DEVICE_OUT_HEARING_AID
+                && streamType == getBluetoothContextualVolumeStream()) {
+            Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
+                    + " stream=" + streamType);
+            mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
+        }
+
+        flags &= ~AudioManager.FLAG_FIXED_VOLUME;
+        if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
+            flags |= AudioManager.FLAG_FIXED_VOLUME;
+
+            // volume is either 0 or max allowed for fixed volume devices
+            if (index != 0) {
+                index = mSoundDoseHelper.getSafeMediaVolumeIndex(device);
+                if (index < 0) {
+                    index = streamState.getMaxIndex();
                 }
-                mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
-            } else if (isAbsoluteVolumeDevice(device)
-                    && ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
-                AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
-
-                dispatchAbsoluteVolumeChanged(streamType, info, index);
-            }
-
-            if (AudioSystem.isLeAudioDeviceType(device)
-                    && streamType == getBluetoothContextualVolumeStream()
-                    && (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
-                if (DEBUG_VOL) {
-                    Log.d(TAG, "adjustSreamVolume postSetLeAudioVolumeIndex index="
-                            + index + " stream=" + streamType);
-                }
-                mDeviceBroker.postSetLeAudioVolumeIndex(index,
-                    mStreamStates[streamType].getMaxIndex(), streamType);
-            }
-
-            if (device == AudioSystem.DEVICE_OUT_HEARING_AID
-                    && streamType == getBluetoothContextualVolumeStream()) {
-                Log.i(TAG, "setStreamVolume postSetHearingAidVolumeIndex index=" + index
-                        + " stream=" + streamType);
-                mDeviceBroker.postSetHearingAidVolumeIndex(index, streamType);
-            }
-
-            flags &= ~AudioManager.FLAG_FIXED_VOLUME;
-            if (streamTypeAlias == AudioSystem.STREAM_MUSIC && isFixedVolumeDevice(device)) {
-                flags |= AudioManager.FLAG_FIXED_VOLUME;
-
-                // volume is either 0 or max allowed for fixed volume devices
-                if (index != 0) {
-                    if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE &&
-                            mSafeMediaVolumeDevices.contains(device)) {
-                        index = safeMediaVolumeIndex(device);
-                    } else {
-                        index = streamState.getMaxIndex();
-                    }
-                }
-            }
-
-            if (!checkSafeMediaVolume(streamTypeAlias, index, device)) {
-                mVolumeController.postDisplaySafeVolumeWarning(flags);
-                mPendingVolumeCommand = new StreamVolumeCommand(
-                                                    streamType, index, flags, device);
-            } else {
-                onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
-                index = mStreamStates[streamType].getIndex(device);
             }
         }
+
+        if (!mSoundDoseHelper.willDisplayWarningAfterCheckVolume(streamType, index, device,
+                flags)) {
+            onSetStreamVolume(streamType, index, flags, device, caller, hasModifyAudioSettings);
+            index = mStreamStates[streamType].getIndex(device);
+        }
+
         synchronized (mHdmiClientLock) {
             if (streamTypeAlias == AudioSystem.STREAM_MUSIC
                     && (oldIndex != index)) {
@@ -5829,14 +5798,8 @@
         checkAllAliasStreamVolumes();
         checkMuteAffectedStreams();
 
-        synchronized (mSafeMediaVolumeStateLock) {
-            mMusicActiveMs = MathUtils.constrain(mSettings.getSecureIntForUser(mContentResolver,
-                    Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0, UserHandle.USER_CURRENT),
-                    0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
-            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
-                enforceSafeMediaVolume(TAG);
-            }
-        }
+        mSoundDoseHelper.restoreMusicActiveMs();
+        mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG);
 
         readVolumeGroupsSettings();
 
@@ -6172,139 +6135,6 @@
         return mContentResolver;
     }
 
-    private void scheduleMusicActiveCheck() {
-        synchronized (mSafeMediaVolumeStateLock) {
-            cancelMusicActiveCheck();
-            mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
-                REQUEST_CODE_CHECK_MUSIC_ACTIVE,
-                new Intent(ACTION_CHECK_MUSIC_ACTIVE),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-            mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                    SystemClock.elapsedRealtime()
-                    + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
-        }
-    }
-
-    private void cancelMusicActiveCheck() {
-        synchronized (mSafeMediaVolumeStateLock) {
-            if (mMusicActiveIntent != null) {
-                mAlarmManager.cancel(mMusicActiveIntent);
-                mMusicActiveIntent = null;
-            }
-        }
-    }
-    private void onCheckMusicActive(String caller) {
-        synchronized (mSafeMediaVolumeStateLock) {
-            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
-                int device = getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                if (mSafeMediaVolumeDevices.contains(device)
-                        && mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)) {
-                    scheduleMusicActiveCheck();
-                    int index = mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(device);
-                    if (index > safeMediaVolumeIndex(device)) {
-                        // Approximate cumulative active music time
-                        long curTimeMs = SystemClock.elapsedRealtime();
-                        if (mLastMusicActiveTimeMs != 0) {
-                            mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
-                        }
-                        mLastMusicActiveTimeMs = curTimeMs;
-                        Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
-                        if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
-                            setSafeMediaVolumeEnabled(true, caller);
-                            mMusicActiveMs = 0;
-                        }
-                        saveMusicActiveMs();
-                    }
-                } else {
-                    cancelMusicActiveCheck();
-                    mLastMusicActiveTimeMs = 0;
-                }
-            }
-        }
-    }
-
-    private void saveMusicActiveMs() {
-        mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
-    }
-
-    private int getSafeUsbMediaVolumeIndex() {
-        // determine UI volume index corresponding to the wanted safe gain in dBFS
-        int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
-        int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
-
-        mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
-                com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
-
-        while (Math.abs(max - min) > 1) {
-            int index = (max + min) / 2;
-            float gainDB = AudioSystem.getStreamVolumeDB(
-                    AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
-            if (Float.isNaN(gainDB)) {
-                //keep last min in case of read error
-                break;
-            } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
-                min = index;
-                break;
-            } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
-                min = index;
-            } else {
-                max = index;
-            }
-        }
-        return min * 10;
-    }
-
-    private void onConfigureSafeVolume(boolean force, String caller) {
-        synchronized (mSafeMediaVolumeStateLock) {
-            int mcc = mContext.getResources().getConfiguration().mcc;
-            if ((mMcc != mcc) || ((mMcc == 0) && force)) {
-                mSafeMediaVolumeIndex = mContext.getResources().getInteger(
-                        com.android.internal.R.integer.config_safe_media_volume_index) * 10;
-
-                mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
-
-                boolean safeMediaVolumeEnabled =
-                        SystemProperties.getBoolean("audio.safemedia.force", false)
-                        || mContext.getResources().getBoolean(
-                                com.android.internal.R.bool.config_safe_media_volume_enabled);
-
-                boolean safeMediaVolumeBypass =
-                        SystemProperties.getBoolean("audio.safemedia.bypass", false);
-
-                // The persisted state is either "disabled" or "active": this is the state applied
-                // next time we boot and cannot be "inactive"
-                int persistedState;
-                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass) {
-                    persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
-                    // The state can already be "inactive" here if the user has forced it before
-                    // the 30 seconds timeout for forced configuration. In this case we don't reset
-                    // it to "active".
-                    if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
-                        if (mMusicActiveMs == 0) {
-                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
-                            enforceSafeMediaVolume(caller);
-                        } else {
-                            // We have existing playback time recorded, already confirmed.
-                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
-                            mLastMusicActiveTimeMs = 0;
-                        }
-                    }
-                } else {
-                    persistedState = SAFE_MEDIA_VOLUME_DISABLED;
-                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
-                }
-                mMcc = mcc;
-                sendMsg(mAudioHandler,
-                        MSG_PERSIST_SAFE_VOLUME_STATE,
-                        SENDMSG_QUEUE,
-                        persistedState,
-                        0,
-                        null,
-                        0);
-            }
-        }
-    }
-
     ///////////////////////////////////////////////////////////////////////////
     // Internal methods
     ///////////////////////////////////////////////////////////////////////////
@@ -6622,9 +6452,13 @@
                     return AudioSystem.STREAM_RING;
                 } else if (wasStreamActiveRecently(
                         AudioSystem.STREAM_NOTIFICATION, sStreamOverrideDelayMs)) {
-                    if (DEBUG_VOL)
-                        Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION stream active");
-                    return AudioSystem.STREAM_NOTIFICATION;
+                        if (DEBUG_VOL) {
+                            Log.v(
+                                    TAG,
+                                    "getActiveStreamType: Forcing STREAM_NOTIFICATION stream"
+                                            + " active");
+                        }
+                        return AudioSystem.STREAM_NOTIFICATION;
                 } else {
                     if (DEBUG_VOL) {
                         Log.v(TAG, "getActiveStreamType: Forcing DEFAULT_VOL_STREAM_NO_PLAYBACK("
@@ -6735,6 +6569,20 @@
         handler.sendMessageAtTime(handler.obtainMessage(msg, arg1, arg2, obj), time);
     }
 
+    private static void sendBundleMsg(Handler handler, int msg,
+            int existingMsgPolicy, int arg1, int arg2, Object obj, Bundle bundle, int delay) {
+        if (existingMsgPolicy == SENDMSG_REPLACE) {
+            handler.removeMessages(msg);
+        } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
+            return;
+        }
+
+        final long time = SystemClock.uptimeMillis() + delay;
+        Message message = handler.obtainMessage(msg, arg1, arg2, obj);
+        message.setData(bundle);
+        handler.sendMessageAtTime(message, time);
+    }
+
     boolean checkAudioSettingsPermission(String method) {
         if (callingOrSelfHasAudioSettingsPermission()) {
             return true;
@@ -7690,7 +7538,7 @@
     //  2   mSetModeLock
     //  3     mSettingsLock
     //  4       VolumeStreamState.class
-    private class VolumeStreamState {
+    /*package*/ class VolumeStreamState {
         private final int mStreamType;
         private int mIndexMin;
         // min index when user doesn't have permission to change audio settings
@@ -8343,8 +8191,8 @@
         final VolumeStreamState streamState = mStreamStates[update.mStreamType];
         if (update.hasVolumeIndex()) {
             int index = update.getVolumeIndex();
-            if (!checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
-                index = safeMediaVolumeIndex(update.mDevice);
+            if (!mSoundDoseHelper.checkSafeMediaVolume(update.mStreamType, index, update.mDevice)) {
+                index = mSoundDoseHelper.safeMediaVolumeIndex(update.mDevice);
             }
             streamState.setIndex(index, update.mDevice, update.mCaller,
                     // trusted as index is always validated before message is posted
@@ -8394,7 +8242,7 @@
     }
 
     /** Handles internal volume messages in separate volume thread. */
-    private class AudioHandler extends Handler {
+    /*package*/ class AudioHandler extends Handler {
 
         AudioHandler() {
             super();
@@ -8441,12 +8289,6 @@
             mSettings.putGlobalInt(mContentResolver, Settings.Global.MODE_RINGER, ringerMode);
         }
 
-        private void onPersistSafeVolumeState(int state) {
-            mSettings.putGlobalInt(mContentResolver,
-                    Settings.Global.AUDIO_SAFE_VOLUME_STATE,
-                    state);
-        }
-
         private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc,
                 @AudioManager.VolumeAdjustment int direction) {
             try {
@@ -8564,19 +8406,6 @@
                     mSpatializerHelper.reset(/* featureEnabled */ mHasSpatializerEffect);
                     break;
 
-                case MSG_CHECK_MUSIC_ACTIVE:
-                    onCheckMusicActive((String) msg.obj);
-                    break;
-
-                case MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED:
-                case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
-                    onConfigureSafeVolume((msg.what == MSG_CONFIGURE_SAFE_MEDIA_VOLUME_FORCED),
-                            (String) msg.obj);
-                    break;
-                case MSG_PERSIST_SAFE_VOLUME_STATE:
-                    onPersistSafeVolumeState(msg.arg1);
-                    break;
-
                 case MSG_SYSTEM_READY:
                     onSystemReady();
                     break;
@@ -8589,13 +8418,6 @@
                     onAccessoryPlugMediaUnmute(msg.arg1);
                     break;
 
-                case MSG_PERSIST_MUSIC_ACTIVE_MS:
-                    final int musicActiveMs = msg.arg1;
-                    mSettings.putSecureIntForUser(mContentResolver,
-                            Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
-                            UserHandle.USER_CURRENT);
-                    break;
-
                 case MSG_UNMUTE_STREAM:
                     onUnmuteStream(msg.arg1, msg.arg2);
                     break;
@@ -8721,6 +8543,20 @@
                     // fold parameter format: "device_folded=x" where x is one of on, off
                     mAudioSystem.setParameters((String) msg.obj);
                     break;
+
+                case MSG_NO_LOG_FOR_PLAYER_I:
+                    mPlaybackMonitor.ignorePlayerIId(msg.arg1);
+                    break;
+
+                case MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES:
+                    onDispatchPreferredMixerAttributesChanged(msg.getData(), msg.arg1);
+                    break;
+
+                default:
+                    if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
+                        // msg could be for the SoundDoseHelper
+                        mSoundDoseHelper.handleMessage(msg);
+                    }
             }
         }
     }
@@ -8837,8 +8673,8 @@
     /** only public for mocking/spying, do not call outside of AudioService */
     @VisibleForTesting
     public void checkMusicActive(int deviceType, String caller) {
-        if (mSafeMediaVolumeDevices.contains(deviceType)) {
-            scheduleMusicActiveCheck();
+        if (mSoundDoseHelper.safeDevicesContains(deviceType)) {
+            mSoundDoseHelper.scheduleMusicActiveCheck();
         }
     }
 
@@ -8972,7 +8808,8 @@
                     }
                 }
             } else if (action.equals(ACTION_CHECK_MUSIC_ACTIVE)) {
-                onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE);
+                mSoundDoseHelper.onCheckMusicActive(ACTION_CHECK_MUSIC_ACTIVE,
+                        mAudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0));
             }
         }
     } // end class AudioServiceBroadcastReceiver
@@ -9858,13 +9695,7 @@
             // reading new configuration "safely" (i.e. under try catch) in case anything
             // goes wrong.
             Configuration config = context.getResources().getConfiguration();
-            sendMsg(mAudioHandler,
-                    MSG_CONFIGURE_SAFE_MEDIA_VOLUME,
-                    SENDMSG_REPLACE,
-                    0,
-                    0,
-                    TAG,
-                    0);
+            mSoundDoseHelper.configureSafeMediaVolume(/*forced*/false, TAG);
 
             boolean cameraSoundForced = readCameraSoundForced();
             synchronized (mSettingsLock) {
@@ -9922,143 +9753,10 @@
         return mDeviceBroker.startWatchingRoutes(observer);
     }
 
-
-    //==========================================================================================
-    // Safe media volume management.
-    // MUSIC stream volume level is limited when headphones are connected according to safety
-    // regulation. When the user attempts to raise the volume above the limit, a warning is
-    // displayed and the user has to acknowlegde before the volume is actually changed.
-    // The volume index corresponding to the limit is stored in config_safe_media_volume_index
-    // property. Platforms with a different limit must set this property accordingly in their
-    // overlay.
-    //==========================================================================================
-
-    // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
-    // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
-    // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
-    // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
-    // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
-    // (when user opts out).
-    private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
-    private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
-    private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
-    private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
-    private int mSafeMediaVolumeState;
-    private final Object mSafeMediaVolumeStateLock = new Object();
-
-    private int mMcc = 0;
-    // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
-    private int mSafeMediaVolumeIndex;
-    // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
-    // property, divided by 100.0.
-    private float mSafeUsbMediaVolumeDbfs;
-    // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
-    // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
-    // flinger mixer.
-    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
-    // amplification when both effects are on with all band gains at maximum.
-    // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
-    // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
-    private int mSafeUsbMediaVolumeIndex;
-    // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
-    /*package*/ final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
-            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
-                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
-    // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
-    // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
-    // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
-    private int mMusicActiveMs;
-    private long mLastMusicActiveTimeMs = 0;
-    private PendingIntent mMusicActiveIntent = null;
-    private AlarmManager mAlarmManager;
-
-    private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
-    private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
-    private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;  // 30s after boot completed
-    // check playback or record activity every 6 seconds for UIDs owning mode IN_COMMUNICATION
-    private static final int CHECK_MODE_FOR_UID_PERIOD_MS = 6000;
-
-    private static final String ACTION_CHECK_MUSIC_ACTIVE =
-            AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
-    private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
-
-    private int safeMediaVolumeIndex(int device) {
-        if (!mSafeMediaVolumeDevices.contains(device)) {
-            return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
-        }
-        if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
-            return mSafeUsbMediaVolumeIndex;
-        } else {
-            return mSafeMediaVolumeIndex;
-        }
-    }
-
-    private void setSafeMediaVolumeEnabled(boolean on, String caller) {
-        synchronized (mSafeMediaVolumeStateLock) {
-            if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) &&
-                    (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_DISABLED)) {
-                if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
-                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
-                    enforceSafeMediaVolume(caller);
-                } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
-                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
-                    mMusicActiveMs = 1;  // nonzero = confirmed
-                    mLastMusicActiveTimeMs = 0;
-                    saveMusicActiveMs();
-                    scheduleMusicActiveCheck();
-                }
-            }
-        }
-    }
-
-    private void enforceSafeMediaVolume(String caller) {
-        VolumeStreamState streamState = mStreamStates[AudioSystem.STREAM_MUSIC];
-        Set<Integer> devices = mSafeMediaVolumeDevices;
-
-        for (int device : devices) {
-            int index = streamState.getIndex(device);
-            if (index > safeMediaVolumeIndex(device)) {
-                streamState.setIndex(safeMediaVolumeIndex(device), device, caller,
-                            true /*hasModifyAudioSettings*/);
-                sendMsg(mAudioHandler,
-                        MSG_SET_DEVICE_VOLUME,
-                        SENDMSG_QUEUE,
-                        device,
-                        0,
-                        streamState,
-                        0);
-            }
-        }
-    }
-
-    private boolean checkSafeMediaVolume(int streamType, int index, int device) {
-        synchronized (mSafeMediaVolumeStateLock) {
-            if ((mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)
-                    && (mStreamVolumeAlias[streamType] == AudioSystem.STREAM_MUSIC)
-                    && (mSafeMediaVolumeDevices.contains(device))
-                    && (index > safeMediaVolumeIndex(device))) {
-                return false;
-            }
-            return true;
-        }
-    }
-
     @Override
     public void disableSafeMediaVolume(String callingPackage) {
         enforceVolumeController("disable the safe media volume");
-        synchronized (mSafeMediaVolumeStateLock) {
-            final long identity = Binder.clearCallingIdentity();
-            setSafeMediaVolumeEnabled(false, callingPackage);
-            Binder.restoreCallingIdentity(identity);
-            if (mPendingVolumeCommand != null) {
-                onSetStreamVolume(mPendingVolumeCommand.mStreamType,
-                                  mPendingVolumeCommand.mIndex,
-                                  mPendingVolumeCommand.mFlags,
-                                  mPendingVolumeCommand.mDevice,
-                                  callingPackage, true /*hasModifyAudioSettings*/);
-                mPendingVolumeCommand = null;
-            }
-        }
+        mSoundDoseHelper.disableSafeMediaVolume(callingPackage);
     }
 
     //==========================================================================================
@@ -10386,15 +10084,8 @@
 
         pw.println("\nOther state:");
         pw.print("  mVolumeController="); pw.println(mVolumeController);
-        pw.print("  mSafeMediaVolumeState=");
-        pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
-        pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
-        pw.print("  mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
-        pw.print("  mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+        mSoundDoseHelper.dump(pw);
         pw.print("  sIndependentA11yVolume="); pw.println(sIndependentA11yVolume);
-        pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
-        pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
-        pw.print("  mMcc="); pw.println(mMcc);
         pw.print("  mCameraSoundForced="); pw.println(isCameraSoundForced());
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
         pw.print("  mVolumePolicy="); pw.println(mVolumePolicy);
@@ -10495,16 +10186,6 @@
     private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
             + MediaMetrics.SEPARATOR;
 
-    private static String safeMediaVolumeStateToString(int state) {
-        switch(state) {
-            case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
-            case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
-            case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
-            case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
-        }
-        return null;
-    }
-
     // Inform AudioFlinger of our device's low RAM attribute
     private static void readAndSetLowRamDevice()
     {
@@ -10584,7 +10265,14 @@
         }
     }
 
-    public class VolumeController {
+    /** Interface used for enforcing the safe hearing standard. */
+    public interface ISafeHearingVolumeController {
+        /** Displays an instructional safeguard as required by the safe hearing standard. */
+        void postDisplaySafeVolumeWarning(int flags);
+    }
+
+    /** Wrapper which encapsulates the {@link IVolumeController} functionality. */
+    public class VolumeController implements ISafeHearingVolumeController {
         private static final String TAG = "VolumeController";
 
         private IVolumeController mController;
@@ -10671,6 +10359,7 @@
             return "VolumeController(" + asBinder() + ",mVisible=" + mVisible + ")";
         }
 
+        @Override
         public void postDisplaySafeVolumeWarning(int flags) {
             if (mController == null)
                 return;
@@ -10913,6 +10602,21 @@
     }
 
     /**
+     * Called by an AudioPolicyProxy when the client dies.
+     * Checks if an active playback for media use case is currently routed to one of the
+     * remote submix devices owned by this dynamic policy and broadcasts a becoming noisy
+     * intend in this case.
+     * @param addresses list of remote submix device addresses to check.
+     */
+    private void onPolicyClientDeath(List<String> addresses) {
+        for (String address : addresses) {
+            if (mPlaybackMonitor.hasActiveMediaPlaybackOnSubmixWithAddress(address)) {
+                mDeviceBroker.postBroadcastBecomingNoisy();
+                return;
+            }
+        }
+    }
+    /**
      * Apps with MODIFY_AUDIO_ROUTING can register any policy.
      * Apps with an audio capable MediaProjection are allowed to register a RENDER|LOOPBACK policy
      * as those policy do not modify the audio routing.
@@ -11284,15 +10988,16 @@
         return mMediaFocusControl.sendFocusLoss(focusLoser);
     }
 
-    private static final String[] HAL_VERSIONS =
-            new String[] {"7.1", "7.0", "6.0", "5.0", "4.0", "2.0"};
-
-    /** @see AudioManager#getHalVersion */
-    public @Nullable String getHalVersion() {
-        for (String version : HAL_VERSIONS) {
+    /**
+     * @see AudioManager#getHalVersion
+     */
+    public @Nullable AudioHalVersionInfo getHalVersion() {
+        for (AudioHalVersionInfo version : AudioHalVersionInfo.VERSIONS) {
             try {
+                // TODO: check AIDL service.
+                String versionStr = version.getMajorVersion() + "." + version.getMinorVersion();
                 HwBinder.getService(
-                        String.format("android.hardware.audio@%s::IDevicesFactory", version),
+                        String.format("android.hardware.audio@%s::IDevicesFactory", versionStr),
                         "default");
                 return version;
             } catch (NoSuchElementException e) {
@@ -11311,6 +11016,125 @@
         }
     }
 
+    /**
+     * @see AudioManager#setPreferredMixerAttributes(
+     *      AudioAttributes, AudioDeviceInfo, AudioMixerAttributes)
+     */
+    public int setPreferredMixerAttributes(AudioAttributes attributes,
+            int portId, AudioMixerAttributes mixerAttributes) {
+        Objects.requireNonNull(attributes);
+        Objects.requireNonNull(mixerAttributes);
+        if (!checkAudioSettingsPermission("setPreferredMixerAttributes()")) {
+            return AudioSystem.PERMISSION_DENIED;
+        }
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+        int status = AudioSystem.SUCCESS;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final String logString = TextUtils.formatSimple(
+                    "setPreferredMixerAttributes u/pid:%d/%d attr:%s mixerAttributes:%s portId:%d",
+                    uid, pid, attributes.toString(), mixerAttributes.toString(), portId);
+            sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
+
+            status = mAudioSystem.setPreferredMixerAttributes(
+                    attributes, portId, uid, mixerAttributes);
+            if (status == AudioSystem.SUCCESS) {
+                dispatchPreferredMixerAttributesChanged(attributes, portId, mixerAttributes);
+            } else {
+                Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString));
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return status;
+    }
+
+    /**
+     * @see AudioManager#clearPreferredMixerAttributes(AudioAttributes, AudioDeviceInfo)
+     */
+    public int clearPreferredMixerAttributes(AudioAttributes attributes, int portId) {
+        Objects.requireNonNull(attributes);
+        if (!checkAudioSettingsPermission("clearPreferredMixerAttributes()")) {
+            return AudioSystem.PERMISSION_DENIED;
+        }
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
+        int status = AudioSystem.SUCCESS;
+        final long token = Binder.clearCallingIdentity();
+        try {
+            final String logString = TextUtils.formatSimple(
+                    "clearPreferredMixerAttributes u/pid:%d/%d attr:%s",
+                    uid, pid, attributes.toString());
+            sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
+
+            status = mAudioSystem.clearPreferredMixerAttributes(attributes, portId, uid);
+            if (status == AudioSystem.SUCCESS) {
+                dispatchPreferredMixerAttributesChanged(attributes, portId, null /*mixerAttr*/);
+            } else {
+                Log.e(TAG, TextUtils.formatSimple("Error %d in %s)", status, logString));
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return status;
+    }
+
+    void dispatchPreferredMixerAttributesChanged(
+            AudioAttributes attr, int deviceId, AudioMixerAttributes mixerAttr) {
+        Bundle bundle = new Bundle();
+        bundle.putParcelable(KEY_AUDIO_ATTRIBUTES, attr);
+        bundle.putParcelable(KEY_AUDIO_MIXER_ATTRIBUTES, mixerAttr);
+        sendBundleMsg(mAudioHandler, MSG_DISPATCH_PREFERRED_MIXER_ATTRIBUTES, SENDMSG_QUEUE,
+                deviceId, 0, null, bundle, 0);
+    }
+
+    final RemoteCallbackList<IPreferredMixerAttributesDispatcher> mPrefMixerAttrDispatcher =
+            new RemoteCallbackList<IPreferredMixerAttributesDispatcher>();
+    private static final String KEY_AUDIO_ATTRIBUTES = "audio_attributes";
+    private static final String KEY_AUDIO_MIXER_ATTRIBUTES = "audio_mixer_attributes";
+
+    /** @see AudioManager#addOnPreferredMixerAttributesChangedListener(
+     *       Executor, AudioManager.OnPreferredMixerAttributesChangedListener)
+     */
+    public void registerPreferredMixerAttributesDispatcher(
+            @Nullable IPreferredMixerAttributesDispatcher dispatcher) {
+        if (dispatcher == null) {
+            return;
+        }
+        mPrefMixerAttrDispatcher.register(dispatcher);
+    }
+
+    /** @see AudioManager#removeOnPreferredMixerAttributesChangedListener(
+     *       AudioManager.OnPreferredMixerAttributesChangedListener)
+     */
+    public void unregisterPreferredMixerAttributesDispatcher(
+            @Nullable IPreferredMixerAttributesDispatcher dispatcher) {
+        if (dispatcher == null) {
+            return;
+        }
+        mPrefMixerAttrDispatcher.unregister(dispatcher);
+    }
+
+    protected void onDispatchPreferredMixerAttributesChanged(Bundle data, int deviceId) {
+        final int nbDispathers = mPrefMixerAttrDispatcher.beginBroadcast();
+        final AudioAttributes attr = data.getParcelable(
+                KEY_AUDIO_ATTRIBUTES, AudioAttributes.class);
+        final AudioMixerAttributes mixerAttr = data.getParcelable(
+                KEY_AUDIO_MIXER_ATTRIBUTES, AudioMixerAttributes.class);
+        for (int i = 0; i < nbDispathers; i++) {
+            try {
+                mPrefMixerAttrDispatcher.getBroadcastItem(i)
+                        .dispatchPrefMixerAttributesChanged(attr, deviceId, mixerAttr);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Can't call dispatchPrefMixerAttributesChanged() "
+                        + "IPreferredMixerAttributesDispatcher "
+                        + mPrefMixerAttrDispatcher.getBroadcastItem(i).asBinder(), e);
+            }
+        }
+        mPrefMixerAttrDispatcher.finishBroadcast();
+    }
+
     private final Object mExtVolumeControllerLock = new Object();
     private IAudioPolicyCallback mExtVolumeController;
     private void setExtVolumeController(IAudioPolicyCallback apc) {
@@ -11386,8 +11210,8 @@
     }
 
     public List<AudioRecordingConfiguration> getActiveRecordingConfigurations() {
-        final boolean isPrivileged =
-                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
+        final boolean isPrivileged = Binder.getCallingUid() == Process.SYSTEM_UID
+                || (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(
                         android.Manifest.permission.MODIFY_AUDIO_ROUTING));
         return mRecordMonitor.getActiveRecordingConfigurations(isPrivileged);
     }
@@ -11622,6 +11446,13 @@
         public void binderDied() {
             mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
                     + mPolicyCallback.asBinder() + " died").printLog(TAG)));
+
+            List<String> addresses = new ArrayList<>();
+            for (AudioMix mix : mMixes) {
+                addresses.add(mix.getRegistration());
+            }
+            onPolicyClientDeath(addresses);
+
             release();
         }
 
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index c3754eb..db406a6 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -20,7 +20,9 @@
 import android.annotation.Nullable;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioMixerAttributes;
 import android.media.AudioSystem;
+import android.media.ISoundDoseCallback;
 import android.media.audiopolicy.AudioMix;
 import android.os.SystemClock;
 import android.util.Log;
@@ -495,6 +497,45 @@
     }
 
     /**
+     * Same as {@link AudioSystem#registerSoundDoseCallback(ISoundDoseCallback)}
+     * @param callback
+     * @return
+     */
+    public int registerSoundDoseCallback(ISoundDoseCallback callback) {
+        return AudioSystem.registerSoundDoseCallback(callback);
+    }
+
+    /**
+     * Same as
+     * {@link AudioSystem#setPreferredMixerAttributes(
+     *        AudioAttributes, int, int, AudioMixerAttributes)}
+     * @param attributes
+     * @param mixerAttributes
+     * @param uid
+     * @param portId
+     * @return
+     */
+    public int setPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes,
+            int portId,
+            int uid,
+            @NonNull AudioMixerAttributes mixerAttributes) {
+        return AudioSystem.setPreferredMixerAttributes(attributes, portId, uid, mixerAttributes);
+    }
+
+    /**
+     * Same as {@link AudioSystem#clearPreferredMixerAttributes(AudioAttributes, int, int)}
+     * @param attributes
+     * @param uid
+     * @param portId
+     * @return
+     */
+    public int clearPreferredMixerAttributes(
+            @NonNull AudioAttributes attributes, int portId, int uid) {
+        return AudioSystem.clearPreferredMixerAttributes(attributes, portId, uid);
+    }
+
+    /**
      * Part of AudioService dump
      * @param pw
      */
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 0bc4b20..f35931ca 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
@@ -191,6 +192,18 @@
     }
 
     //=================================================================
+    // Player to ignore (only handling single player, designed for ignoring
+    // in the logs one specific player such as the touch sounds player)
+    @GuardedBy("mPlayerLock")
+    private ArrayList<Integer> mDoNotLogPiidList = new ArrayList<>();
+
+    /*package*/ void ignorePlayerIId(int doNotLogPiid) {
+        synchronized (mPlayerLock) {
+            mDoNotLogPiidList.add(doNotLogPiid);
+        }
+    }
+
+    //=================================================================
     // Track players and their states
     // methods playerAttributes, playerEvent, releasePlayer are all oneway calls
     //  into AudioService. They trigger synchronous dispatchPlaybackChange() which updates
@@ -314,14 +327,18 @@
             Log.v(TAG, TextUtils.formatSimple("playerEvent(piid=%d, event=%s, eventValue=%d)",
                     piid, AudioPlaybackConfiguration.playerStateToString(event), eventValue));
         }
-
-        final boolean change;
+        boolean change;
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (apc == null) {
                 return;
             }
 
+            final boolean doNotLog = mDoNotLogPiidList.contains(piid);
+            if (doNotLog && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
+                // do not log nor dispatch events for "ignored" players other than the release
+                return;
+            }
             sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
@@ -338,7 +355,8 @@
                     }
                 }
             }
-            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+            if (apc.getPlayerType() == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL
+                    && event != AudioPlaybackConfiguration.PLAYER_STATE_RELEASED) {
                 // FIXME SoundPool not ready for state reporting
                 return;
             }
@@ -350,9 +368,15 @@
                 Log.e(TAG, "Error handling event " + event);
                 change = false;
             }
-            if (change && event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                mDuckingManager.checkDuck(apc);
-                mFadingManager.checkFade(apc);
+            if (change) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    mDuckingManager.checkDuck(apc);
+                    mFadingManager.checkFade(apc);
+                }
+                if (doNotLog) {
+                    // do not dispatch events for "ignored" players
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -435,6 +459,10 @@
                 mEventHandler.sendMessage(
                         mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
 
+                if (change && mDoNotLogPiidList.contains(piid)) {
+                    // do not dispatch a change for a "do not log" player
+                    change = false;
+                }
             }
         }
         if (change) {
@@ -542,6 +570,26 @@
         return false;
     }
 
+    /**
+     * Return true if an active playback for media use case is currently routed to
+     * a remote submix device with the supplied address.
+     * @param address
+     */
+    public boolean hasActiveMediaPlaybackOnSubmixWithAddress(@NonNull String address) {
+        synchronized (mPlayerLock) {
+            for (AudioPlaybackConfiguration apc : mPlayers.values()) {
+                AudioDeviceInfo device = apc.getAudioDeviceInfo();
+                if (apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_MEDIA
+                        && apc.isActive() && device != null
+                        && device.getInternalType() == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
+                        && address.equals(device.getAddress())) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     protected void dump(PrintWriter pw) {
         // players
         pw.println("\nPlaybackActivityMonitor dump time: "
@@ -560,6 +608,9 @@
             for (Integer piidInt : piidIntList) {
                 final AudioPlaybackConfiguration apc = mPlayers.get(piidInt);
                 if (apc != null) {
+                    if (mDoNotLogPiidList.contains(apc.getPlayerInterfaceId())) {
+                        pw.print("(not logged)");
+                    }
                     apc.dump(pw);
                 }
             }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
new file mode 100644
index 0000000..ff6a7f1
--- /dev/null
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -0,0 +1,538 @@
+/*
+ * 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.audio;
+
+import static com.android.server.audio.AudioService.MAX_STREAM_VOLUME;
+import static com.android.server.audio.AudioService.MIN_STREAM_VOLUME;
+import static com.android.server.audio.AudioService.MSG_SET_DEVICE_VOLUME;
+import static com.android.server.audio.AudioService.SAFE_MEDIA_VOLUME_MSG_START;
+
+import android.annotation.NonNull;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioSystem;
+import android.media.ISoundDoseCallback;
+import android.media.SoundDoseRecord;
+import android.os.Binder;
+import android.os.Message;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.MathUtils;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.audio.AudioService.AudioHandler;
+import com.android.server.audio.AudioService.ISafeHearingVolumeController;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Safe media volume management.
+ * MUSIC stream volume level is limited when headphones are connected according to safety
+ * regulation. When the user attempts to raise the volume above the limit, a warning is
+ * displayed and the user has to acknowledge before the volume is actually changed.
+ * The volume index corresponding to the limit is stored in config_safe_media_volume_index
+ * property. Platforms with a different limit must set this property accordingly in their
+ * overlay.
+ */
+public class SoundDoseHelper {
+    private static final String TAG = "AS.SoundDoseHelper";
+
+    /*package*/ static final String ACTION_CHECK_MUSIC_ACTIVE =
+            AudioService.class.getSimpleName() + ".CHECK_MUSIC_ACTIVE";
+
+    /** Flag to enable/disable the sound dose computation. */
+    private static final boolean USE_CSD_FOR_SAFE_HEARING = false;
+
+    // mSafeMediaVolumeState indicates whether the media volume is limited over headphones.
+    // It is SAFE_MEDIA_VOLUME_NOT_CONFIGURED at boot time until a network service is connected
+    // or the configure time is elapsed. It is then set to SAFE_MEDIA_VOLUME_ACTIVE or
+    // SAFE_MEDIA_VOLUME_DISABLED according to country option. If not SAFE_MEDIA_VOLUME_DISABLED, it
+    // can be set to SAFE_MEDIA_VOLUME_INACTIVE by calling AudioService.disableSafeMediaVolume()
+    // (when user opts out).
+    private static final int SAFE_MEDIA_VOLUME_NOT_CONFIGURED = 0;
+    private static final int SAFE_MEDIA_VOLUME_DISABLED = 1;
+    private static final int SAFE_MEDIA_VOLUME_INACTIVE = 2;  // confirmed
+    private static final int SAFE_MEDIA_VOLUME_ACTIVE = 3;  // unconfirmed
+
+    private static final int MSG_CONFIGURE_SAFE_MEDIA_VOLUME = SAFE_MEDIA_VOLUME_MSG_START + 1;
+    private static final int MSG_PERSIST_SAFE_VOLUME_STATE = SAFE_MEDIA_VOLUME_MSG_START + 2;
+    private static final int MSG_PERSIST_MUSIC_ACTIVE_MS = SAFE_MEDIA_VOLUME_MSG_START + 3;
+
+    private static final int UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX = (20 * 3600 * 1000); // 20 hours
+
+    // 30s after boot completed
+    private static final int SAFE_VOLUME_CONFIGURE_TIMEOUT_MS = 30000;
+
+    private static final int MUSIC_ACTIVE_POLL_PERIOD_MS = 60000;  // 1 minute polling interval
+    private static final int REQUEST_CODE_CHECK_MUSIC_ACTIVE = 1;
+
+    private int mMcc = 0;
+
+    final Object mSafeMediaVolumeStateLock = new Object();
+    private int mSafeMediaVolumeState;
+
+    // Used when safe volume warning message display is requested by setStreamVolume(). In this
+    // case, the new requested volume, stream type and device are stored in mPendingVolumeCommand
+    // and used later when/if disableSafeMediaVolume() is called.
+    private StreamVolumeCommand mPendingVolumeCommand;
+
+    // mSafeMediaVolumeIndex is the cached value of config_safe_media_volume_index property
+    private int mSafeMediaVolumeIndex;
+    // mSafeUsbMediaVolumeDbfs is the cached value of the config_safe_media_volume_usb_mB
+    // property, divided by 100.0.
+    private float mSafeUsbMediaVolumeDbfs;
+
+    // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
+    // corresponding to a gain of mSafeUsbMediaVolumeDbfs (defaulting to -37dB) in audio
+    // flinger mixer.
+    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+    // amplification when both effects are on with all band gains at maximum.
+    // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
+    // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
+    private int mSafeUsbMediaVolumeIndex;
+    // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
+    private final Set<Integer> mSafeMediaVolumeDevices = new HashSet<>(
+            Arrays.asList(AudioSystem.DEVICE_OUT_WIRED_HEADSET,
+                    AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, AudioSystem.DEVICE_OUT_USB_HEADSET));
+
+
+    // mMusicActiveMs is the cumulative time of music activity since safe volume was disabled.
+    // When this time reaches UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX, the safe media volume is re-enabled
+    // automatically. mMusicActiveMs is rounded to a multiple of MUSIC_ACTIVE_POLL_PERIOD_MS.
+    private int mMusicActiveMs;
+    private long mLastMusicActiveTimeMs = 0;
+    private PendingIntent mMusicActiveIntent = null;
+    private final AlarmManager mAlarmManager;
+
+    @NonNull private final AudioService mAudioService;
+    @NonNull private final SettingsAdapter mSettings;
+    @NonNull private final AudioHandler mAudioHandler;
+    @NonNull private final ISafeHearingVolumeController mVolumeController;
+
+    private final Context mContext;
+
+    private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
+        public void onMomentaryExposure(float currentMel, int deviceId) {
+            Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
+                    + currentMel);
+        }
+
+        public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
+            Log.i(TAG, "onNewCsdValue: " + currentCsd);
+            for (SoundDoseRecord record : records) {
+                Log.i(TAG, "  new record: csd=" + record.value
+                        + " averageMel=" + record.averageMel + " timestamp=" + record.timestamp
+                        + " duration=" + record.duration);
+            }
+        }
+    };
+
+    SoundDoseHelper(@NonNull AudioService audioService, Context context,
+            @NonNull AudioHandler audioHandler,
+            @NonNull SettingsAdapter settings,
+            @NonNull ISafeHearingVolumeController volumeController) {
+        mAudioService = audioService;
+        mAudioHandler = audioHandler;
+        mSettings = settings;
+        mVolumeController = volumeController;
+
+        mContext = context;
+
+        mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
+                Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
+        if (USE_CSD_FOR_SAFE_HEARING) {
+            AudioSystem.registerSoundDoseCallback(mSoundDoseCallback);
+            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+        }
+
+        // The default safe volume index read here will be replaced by the actual value when
+        // the mcc is read by onConfigureSafeVolume()
+        mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+        mAlarmManager = (AlarmManager) mContext.getSystemService(
+                Context.ALARM_SERVICE);
+    }
+
+    /*package*/ int safeMediaVolumeIndex(int device) {
+        if (!mSafeMediaVolumeDevices.contains(device)) {
+            return MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+        }
+        if (device == AudioSystem.DEVICE_OUT_USB_HEADSET) {
+            return mSafeUsbMediaVolumeIndex;
+        } else {
+            return mSafeMediaVolumeIndex;
+        }
+    }
+
+    /*package*/ void restoreMusicActiveMs() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            mMusicActiveMs = MathUtils.constrain(
+                    mSettings.getSecureIntForUser(mAudioService.getContentResolver(),
+                            Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, 0,
+                            UserHandle.USER_CURRENT),
+                    0, UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX);
+        }
+    }
+
+    /*package*/ void enforceSafeMediaVolumeIfActive(String caller) {
+        synchronized (mSafeMediaVolumeStateLock) {
+            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE) {
+                enforceSafeMediaVolume(caller);
+            }
+        }
+    }
+
+    /*package*/ void enforceSafeMediaVolume(String caller) {
+        AudioService.VolumeStreamState streamState = mAudioService.getVssVolumeForStream(
+                AudioSystem.STREAM_MUSIC);
+        Set<Integer> devices = mSafeMediaVolumeDevices;
+
+        for (int device : devices) {
+            int index = streamState.getIndex(device);
+            int safeIndex = safeMediaVolumeIndex(device);
+            if (index > safeIndex) {
+                streamState.setIndex(safeIndex, device, caller, true /*hasModifyAudioSettings*/);
+                mAudioHandler.sendMessageAtTime(
+                        mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, device, /*arg2=*/0,
+                                streamState), /*delay=*/0);
+            }
+        }
+    }
+
+    /*package*/ boolean checkSafeMediaVolume(int streamType, int index, int device) {
+        boolean result;
+        synchronized (mSafeMediaVolumeStateLock) {
+            result = checkSafeMediaVolume_l(streamType, index, device);
+        }
+        return result;
+    }
+
+    @GuardedBy("mSafeMediaVolumeStateLock")
+    private boolean checkSafeMediaVolume_l(int streamType, int index, int device) {
+        return (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_ACTIVE)
+                    || (AudioService.mStreamVolumeAlias[streamType] != AudioSystem.STREAM_MUSIC)
+                    || (!mSafeMediaVolumeDevices.contains(device))
+                    || (index <= safeMediaVolumeIndex(device))
+                    || USE_CSD_FOR_SAFE_HEARING;
+    }
+
+    /*package*/ boolean willDisplayWarningAfterCheckVolume(int streamType, int index, int device,
+            int flags) {
+        synchronized (mSafeMediaVolumeStateLock) {
+            if (!checkSafeMediaVolume_l(streamType, index, device)) {
+                mVolumeController.postDisplaySafeVolumeWarning(flags);
+                mPendingVolumeCommand = new StreamVolumeCommand(
+                        streamType, index, flags, device);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /*package*/ void disableSafeMediaVolume(String callingPackage) {
+        synchronized (mSafeMediaVolumeStateLock) {
+            final long identity = Binder.clearCallingIdentity();
+            setSafeMediaVolumeEnabled(false, callingPackage);
+            Binder.restoreCallingIdentity(identity);
+
+            if (mPendingVolumeCommand != null) {
+                mAudioService.onSetStreamVolume(mPendingVolumeCommand.mStreamType,
+                        mPendingVolumeCommand.mIndex,
+                        mPendingVolumeCommand.mFlags,
+                        mPendingVolumeCommand.mDevice,
+                        callingPackage, true /*hasModifyAudioSettings*/);
+                mPendingVolumeCommand = null;
+            }
+        }
+    }
+
+    /*package*/ void scheduleMusicActiveCheck() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            cancelMusicActiveCheck();
+            if (!USE_CSD_FOR_SAFE_HEARING) {
+                mMusicActiveIntent = PendingIntent.getBroadcast(mContext,
+                        REQUEST_CODE_CHECK_MUSIC_ACTIVE,
+                        new Intent(ACTION_CHECK_MUSIC_ACTIVE),
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+                mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                        SystemClock.elapsedRealtime()
+                                + MUSIC_ACTIVE_POLL_PERIOD_MS, mMusicActiveIntent);
+            }
+        }
+    }
+
+    /*package*/ void onCheckMusicActive(String caller, boolean isStreamActive) {
+        synchronized (mSafeMediaVolumeStateLock) {
+            if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE) {
+                int device = mAudioService.getDeviceForStream(AudioSystem.STREAM_MUSIC);
+                if (mSafeMediaVolumeDevices.contains(device) && isStreamActive) {
+                    scheduleMusicActiveCheck();
+                    int index = mAudioService.getVssVolumeForDevice(AudioSystem.STREAM_MUSIC,
+                            device);
+                    if (index > safeMediaVolumeIndex(device)) {
+                        // Approximate cumulative active music time
+                        long curTimeMs = SystemClock.elapsedRealtime();
+                        if (mLastMusicActiveTimeMs != 0) {
+                            mMusicActiveMs += (int) (curTimeMs - mLastMusicActiveTimeMs);
+                        }
+                        mLastMusicActiveTimeMs = curTimeMs;
+                        Log.i(TAG, "onCheckMusicActive() mMusicActiveMs: " + mMusicActiveMs);
+                        if (mMusicActiveMs > UNSAFE_VOLUME_MUSIC_ACTIVE_MS_MAX) {
+                            setSafeMediaVolumeEnabled(true, caller);
+                            mMusicActiveMs = 0;
+                        }
+                        saveMusicActiveMs();
+                    }
+                } else {
+                    cancelMusicActiveCheck();
+                    mLastMusicActiveTimeMs = 0;
+                }
+            }
+        }
+    }
+
+    /*package*/ void configureSafeMediaVolume(boolean forced, String caller) {
+        int msg = MSG_CONFIGURE_SAFE_MEDIA_VOLUME;
+        mAudioHandler.removeMessages(msg);
+
+        long time = 0;
+        if (forced) {
+            time = (SystemClock.uptimeMillis() + (SystemProperties.getBoolean(
+                    "audio.safemedia.bypass", false) ? 0 : SAFE_VOLUME_CONFIGURE_TIMEOUT_MS));
+        }
+        mAudioHandler.sendMessageAtTime(
+                mAudioHandler.obtainMessage(msg, /*arg1=*/forced ? 1 : 0, /*arg2=*/0, caller),
+                time);
+    }
+
+    /*package*/ void initSafeUsbMediaVolumeIndex() {
+        // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it
+        // relies on audio policy having correct ranges for volume indexes.
+        mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+    }
+
+    /*package*/ int getSafeMediaVolumeIndex(int device) {
+        if (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE && mSafeMediaVolumeDevices.contains(
+                device)) {
+            return safeMediaVolumeIndex(device);
+        } else {
+            return -1;
+        }
+    }
+
+    /*package*/ boolean raiseVolumeDisplaySafeMediaVolume(int streamType, int index, int device,
+            int flags) {
+        if (checkSafeMediaVolume(streamType, index, device)) {
+            return false;
+        }
+
+        mVolumeController.postDisplaySafeVolumeWarning(flags);
+        return true;
+    }
+
+    /*package*/ boolean safeDevicesContains(int device) {
+        return mSafeMediaVolumeDevices.contains(device);
+    }
+
+    /*package*/ void invalidatPendingVolumeCommand() {
+        synchronized (mSafeMediaVolumeStateLock) {
+            mPendingVolumeCommand = null;
+        }
+    }
+
+    /*package*/ void handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_CONFIGURE_SAFE_MEDIA_VOLUME:
+                onConfigureSafeVolume((msg.arg1 == 1), (String) msg.obj);
+                break;
+            case MSG_PERSIST_SAFE_VOLUME_STATE:
+                onPersistSafeVolumeState(msg.arg1);
+                break;
+            case MSG_PERSIST_MUSIC_ACTIVE_MS:
+                final int musicActiveMs = msg.arg1;
+                mSettings.putSecureIntForUser(mAudioService.getContentResolver(),
+                        Settings.Secure.UNSAFE_VOLUME_MUSIC_ACTIVE_MS, musicActiveMs,
+                        UserHandle.USER_CURRENT);
+                break;
+        }
+
+    }
+
+    /*package*/ void dump(PrintWriter pw) {
+        pw.print("  mSafeMediaVolumeState=");
+        pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
+        pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
+        pw.print("  mSafeUsbMediaVolumeIndex="); pw.println(mSafeUsbMediaVolumeIndex);
+        pw.print("  mSafeUsbMediaVolumeDbfs="); pw.println(mSafeUsbMediaVolumeDbfs);
+        pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
+        pw.print("  mMcc="); pw.println(mMcc);
+        pw.print("  mPendingVolumeCommand="); pw.println(mPendingVolumeCommand);
+    }
+
+    private void onConfigureSafeVolume(boolean force, String caller) {
+        synchronized (mSafeMediaVolumeStateLock) {
+            int mcc = mContext.getResources().getConfiguration().mcc;
+            if ((mMcc != mcc) || ((mMcc == 0) && force)) {
+                mSafeMediaVolumeIndex = mContext.getResources().getInteger(
+                        com.android.internal.R.integer.config_safe_media_volume_index) * 10;
+
+                mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
+
+                boolean safeMediaVolumeEnabled =
+                        SystemProperties.getBoolean("audio.safemedia.force", false)
+                                || mContext.getResources().getBoolean(
+                                com.android.internal.R.bool.config_safe_media_volume_enabled);
+
+                boolean safeMediaVolumeBypass =
+                        SystemProperties.getBoolean("audio.safemedia.bypass", false);
+
+                // The persisted state is either "disabled" or "active": this is the state applied
+                // next time we boot and cannot be "inactive"
+                int persistedState;
+                if (safeMediaVolumeEnabled && !safeMediaVolumeBypass && !USE_CSD_FOR_SAFE_HEARING) {
+                    persistedState = SAFE_MEDIA_VOLUME_ACTIVE;
+                    // The state can already be "inactive" here if the user has forced it before
+                    // the 30 seconds timeout for forced configuration. In this case we don't reset
+                    // it to "active".
+                    if (mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_INACTIVE) {
+                        if (mMusicActiveMs == 0) {
+                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+                            enforceSafeMediaVolume(caller);
+                        } else {
+                            // We have existing playback time recorded, already confirmed.
+                            mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+                            mLastMusicActiveTimeMs = 0;
+                        }
+                    }
+                } else {
+                    persistedState = SAFE_MEDIA_VOLUME_DISABLED;
+                    mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
+                }
+                mMcc = mcc;
+                mAudioHandler.sendMessageAtTime(
+                        mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
+                                persistedState, /*arg2=*/0,
+                                /*obj=*/null), /*delay=*/0);
+            }
+        }
+    }
+
+    @GuardedBy("mSafeMediaVolumeStateLock")
+    private void setSafeMediaVolumeEnabled(boolean on, String caller) {
+        if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && (mSafeMediaVolumeState
+                != SAFE_MEDIA_VOLUME_DISABLED)) {
+            if (on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_INACTIVE)) {
+                mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_ACTIVE;
+                enforceSafeMediaVolume(caller);
+            } else if (!on && (mSafeMediaVolumeState == SAFE_MEDIA_VOLUME_ACTIVE)) {
+                mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_INACTIVE;
+                mMusicActiveMs = 1;  // nonzero = confirmed
+                mLastMusicActiveTimeMs = 0;
+                saveMusicActiveMs();
+                scheduleMusicActiveCheck();
+            }
+        }
+    }
+
+    @GuardedBy("mSafeMediaVolumeStateLock")
+    private void cancelMusicActiveCheck() {
+        if (mMusicActiveIntent != null) {
+            mAlarmManager.cancel(mMusicActiveIntent);
+            mMusicActiveIntent = null;
+        }
+    }
+
+    @GuardedBy("mSafeMediaVolumeStateLock")
+    private void saveMusicActiveMs() {
+        mAudioHandler.obtainMessage(MSG_PERSIST_MUSIC_ACTIVE_MS, mMusicActiveMs, 0).sendToTarget();
+    }
+
+    private int getSafeUsbMediaVolumeIndex() {
+        // determine UI volume index corresponding to the wanted safe gain in dBFS
+        int min = MIN_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+        int max = MAX_STREAM_VOLUME[AudioSystem.STREAM_MUSIC];
+
+        mSafeUsbMediaVolumeDbfs = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_safe_media_volume_usb_mB) / 100.0f;
+
+        while (Math.abs(max - min) > 1) {
+            int index = (max + min) / 2;
+            float gainDB = AudioSystem.getStreamVolumeDB(
+                    AudioSystem.STREAM_MUSIC, index, AudioSystem.DEVICE_OUT_USB_HEADSET);
+            if (Float.isNaN(gainDB)) {
+                //keep last min in case of read error
+                break;
+            } else if (gainDB == mSafeUsbMediaVolumeDbfs) {
+                min = index;
+                break;
+            } else if (gainDB < mSafeUsbMediaVolumeDbfs) {
+                min = index;
+            } else {
+                max = index;
+            }
+        }
+        return min * 10;
+    }
+
+    private void onPersistSafeVolumeState(int state) {
+        mSettings.putGlobalInt(mAudioService.getContentResolver(),
+                Settings.Global.AUDIO_SAFE_VOLUME_STATE,
+                state);
+    }
+
+    private static String safeMediaVolumeStateToString(int state) {
+        switch(state) {
+            case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
+            case SAFE_MEDIA_VOLUME_DISABLED: return "SAFE_MEDIA_VOLUME_DISABLED";
+            case SAFE_MEDIA_VOLUME_INACTIVE: return "SAFE_MEDIA_VOLUME_INACTIVE";
+            case SAFE_MEDIA_VOLUME_ACTIVE: return "SAFE_MEDIA_VOLUME_ACTIVE";
+        }
+        return null;
+    }
+
+    // StreamVolumeCommand contains the information needed to defer the process of
+    // setStreamVolume() in case the user has to acknowledge the safe volume warning message.
+    private static class StreamVolumeCommand {
+        public final int mStreamType;
+        public final int mIndex;
+        public final int mFlags;
+        public final int mDevice;
+
+        StreamVolumeCommand(int streamType, int index, int flags, int device) {
+            mStreamType = streamType;
+            mIndex = index;
+            mFlags = flags;
+            mDevice = device;
+        }
+
+        @Override
+        public String toString() {
+            return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
+                    .append(mIndex).append(",flags=").append(mFlags).append(",device=")
+                    .append(mDevice).append('}').toString();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 79b54eb..8c4efba 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -25,6 +25,7 @@
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnCompletionListener;
 import android.media.MediaPlayer.OnErrorListener;
+import android.media.PlayerBase;
 import android.media.SoundPool;
 import android.os.Environment;
 import android.os.Handler;
@@ -47,6 +48,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Consumer;
 
 /**
  * A helper class for managing sound effects loading / unloading
@@ -109,11 +111,14 @@
     private final int[] mEffects = new int[AudioManager.NUM_SOUND_EFFECTS]; // indexes in mResources
     private SoundPool mSoundPool;
     private SoundPoolLoader mSoundPoolLoader;
+    /** callback to provide handle to the player of the sound effects */
+    private final Consumer<PlayerBase> mPlayerAvailableCb;
 
-    SoundEffectsHelper(Context context) {
+    SoundEffectsHelper(Context context, Consumer<PlayerBase> playerAvailableCb) {
         mContext = context;
         mSfxAttenuationDb = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_soundEffectVolumeDb);
+        mPlayerAvailableCb = playerAvailableCb;
         startWorker();
     }
 
@@ -189,6 +194,7 @@
                         .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                         .build())
                 .build();
+        mPlayerAvailableCb.accept(mSoundPool);
         loadSoundAssets();
 
         mSoundPoolLoader = new SoundPoolLoader();
diff --git a/services/core/java/com/android/server/audio/TEST_MAPPING b/services/core/java/com/android/server/audio/TEST_MAPPING
index f3a73f0..d6c2fb2 100644
--- a/services/core/java/com/android/server/audio/TEST_MAPPING
+++ b/services/core/java/com/android/server/audio/TEST_MAPPING
@@ -13,6 +13,17 @@
                     "include-filter": "android.media.audio.cts.SpatializerTest"
                 }
             ]
+        },
+        {
+            "name": "audiopolicytest",
+            "options": [
+                {
+                    "include-filter": "com.android.audiopolicytest.AudioPolicyDeathTest"
+                },
+                {
+                    "include-annotation": "android.platform.test.annotations.Presubmit"
+                }
+            ]
         }
     ]
 }
diff --git a/services/core/java/com/android/server/companion/virtual/OWNERS b/services/core/java/com/android/server/companion/virtual/OWNERS
new file mode 100644
index 0000000..2e475a9
--- /dev/null
+++ b/services/core/java/com/android/server/companion/virtual/OWNERS
@@ -0,0 +1 @@
+include /services/companion/java/com/android/server/companion/virtual/OWNERS
\ No newline at end of file
diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index d2e572f..218be9d 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.companion.virtual.IVirtualDevice;
-import android.companion.virtual.VirtualDeviceParams;
 
 import java.util.Set;
 
@@ -94,12 +93,6 @@
     public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice);
 
     /**
-     * Returns true if the given {@code uid} is the owner of any virtual devices that are
-     * currently active.
-     */
-    public abstract boolean isAppOwnerOfAnyVirtualDevice(int uid);
-
-    /**
      * Returns true if the given {@code uid} is currently running on any virtual devices. This is
      * determined by whether the app has any activities in the task stack on a virtual-device-owned
      * display.
@@ -110,14 +103,4 @@
      * Returns true if the {@code displayId} is owned by any virtual device
      */
     public abstract boolean isDisplayOwnedByAnyVirtualDevice(int displayId);
-
-    /**
-     * Returns the device policy for the given virtual device and policy type.
-     *
-     * <p>In case the virtual device identifier is not valid, or there's no explicitly specified
-     * policy for that device and policy type, then
-     * {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT} is returned.
-     */
-    public abstract @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
-            int deviceId, @VirtualDeviceParams.PolicyType int policyType);
 }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index bc9bc03..4fcde97 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -3475,6 +3475,8 @@
                 return;
             } else {
                 mActiveNetwork = null;
+                mUnderlyingNetworkCapabilities = null;
+                mUnderlyingLinkProperties = null;
             }
 
             if (mScheduledHandleNetworkLostFuture != null) {
@@ -3664,9 +3666,6 @@
                 scheduleRetryNewIkeSession();
             }
 
-            mUnderlyingNetworkCapabilities = null;
-            mUnderlyingLinkProperties = null;
-
             // Close all obsolete state, but keep VPN alive incase a usable network comes up.
             // (Mirrors VpnService behavior)
             Log.d(TAG, "Resetting state for token: " + mCurrentToken);
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index ddb9243..fe1d1a6 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -245,6 +245,12 @@
     public int modeId;
 
     /**
+     * The render frame rate this display is scheduled at.
+     * @see android.view.DisplayInfo#renderFrameRate for more details.
+     */
+    public float renderFrameRate;
+
+    /**
      * The default mode of the display.
      */
     public int defaultModeId;
@@ -439,6 +445,7 @@
                 || width != other.width
                 || height != other.height
                 || modeId != other.modeId
+                || renderFrameRate != other.renderFrameRate
                 || defaultModeId != other.defaultModeId
                 || !Arrays.equals(supportedModes, other.supportedModes)
                 || !Arrays.equals(supportedColorModes, other.supportedColorModes)
@@ -483,6 +490,7 @@
         width = other.width;
         height = other.height;
         modeId = other.modeId;
+        renderFrameRate = other.renderFrameRate;
         defaultModeId = other.defaultModeId;
         supportedModes = other.supportedModes;
         colorMode = other.colorMode;
@@ -523,6 +531,7 @@
         sb.append(name).append("\": uniqueId=\"").append(uniqueId).append("\", ");
         sb.append(width).append(" x ").append(height);
         sb.append(", modeId ").append(modeId);
+        sb.append(", renderFrameRate ").append(renderFrameRate);
         sb.append(", defaultModeId ").append(defaultModeId);
         sb.append(", supportedModes ").append(Arrays.toString(supportedModes));
         sb.append(", colorMode ").append(colorMode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 0d1aca8..ae84e96 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -36,6 +36,7 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 
 import android.Manifest;
@@ -882,20 +883,27 @@
 
     private DisplayInfo getDisplayInfoForFrameRateOverride(DisplayEventReceiver.FrameRateOverride[]
             frameRateOverrides, DisplayInfo info, int callingUid) {
-        float frameRateHz = 0;
+        float frameRateHz = info.renderFrameRate;
         for (DisplayEventReceiver.FrameRateOverride frameRateOverride : frameRateOverrides) {
             if (frameRateOverride.uid == callingUid) {
                 frameRateHz = frameRateOverride.frameRateHz;
                 break;
             }
         }
+
         if (frameRateHz == 0) {
             return info;
         }
 
+        // For non-apps users we always return the physical refresh rate from display mode
+        boolean displayModeReturnsPhysicalRefreshRate =
+                callingUid < FIRST_APPLICATION_UID
+                        || CompatChanges.isChangeEnabled(
+                                DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE, callingUid);
+
         // Override the refresh rate only if it is a divisor of the current
         // refresh rate. This calculation needs to be in sync with the native code
-        // in RefreshRateConfigs::getFrameRateDivisor
+        // in RefreshRateSelector::getFrameRateDivisor
         Display.Mode currentMode = info.getMode();
         float numPeriods = currentMode.getRefreshRate() / frameRateHz;
         float numPeriodsRound = Math.round(numPeriods);
@@ -919,8 +927,7 @@
                 }
                 overriddenInfo.refreshRateOverride = mode.getRefreshRate();
 
-                if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                        callingUid)) {
+                if (!displayModeReturnsPhysicalRefreshRate) {
                     overriddenInfo.modeId = mode.getModeId();
                 }
                 return overriddenInfo;
@@ -928,8 +935,7 @@
         }
 
         overriddenInfo.refreshRateOverride = frameRateHz;
-        if (!CompatChanges.isChangeEnabled(DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE,
-                callingUid)) {
+        if (!displayModeReturnsPhysicalRefreshRate) {
             overriddenInfo.supportedModes = Arrays.copyOf(info.supportedModes,
                     info.supportedModes.length + 1);
             overriddenInfo.supportedModes[overriddenInfo.supportedModes.length - 1] =
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index dc5c80f2..be5980b 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -112,13 +112,13 @@
                 mSurfaceControlProxy.getPhysicalDisplayToken(physicalDisplayId);
         if (displayToken != null) {
             SurfaceControl.StaticDisplayInfo staticInfo =
-                    mSurfaceControlProxy.getStaticDisplayInfo(displayToken);
+                    mSurfaceControlProxy.getStaticDisplayInfo(physicalDisplayId);
             if (staticInfo == null) {
                 Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);
                 return;
             }
             SurfaceControl.DynamicDisplayInfo dynamicInfo =
-                    mSurfaceControlProxy.getDynamicDisplayInfo(displayToken);
+                    mSurfaceControlProxy.getDynamicDisplayInfo(physicalDisplayId);
             if (dynamicInfo == null) {
                 Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);
                 return;
@@ -226,6 +226,8 @@
         private SurfaceControl.DisplayMode[] mSfDisplayModes;
         // The active display mode in SurfaceFlinger
         private SurfaceControl.DisplayMode mActiveSfDisplayMode;
+        // The active display vsync period in SurfaceFlinger
+        private float mActiveRenderFrameRate;
 
         private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =
                 new DisplayEventReceiver.FrameRateOverride[0];
@@ -267,7 +269,7 @@
                 SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             boolean changed = updateDisplayModesLocked(
                     dynamicInfo.supportedDisplayModes, dynamicInfo.preferredBootDisplayMode,
-                    dynamicInfo.activeDisplayModeId, modeSpecs);
+                    dynamicInfo.activeDisplayModeId, dynamicInfo.renderFrameRate, modeSpecs);
             changed |= updateStaticInfo(staticInfo);
             changed |= updateColorModesLocked(dynamicInfo.supportedColorModes,
                     dynamicInfo.activeColorMode);
@@ -283,7 +285,8 @@
 
         public boolean updateDisplayModesLocked(
                 SurfaceControl.DisplayMode[] displayModes, int preferredSfDisplayModeId,
-                int activeSfDisplayModeId, SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
+                int activeSfDisplayModeId, float renderFrameRate,
+                SurfaceControl.DesiredDisplayModeSpecs modeSpecs) {
             mSfDisplayModes = Arrays.copyOf(displayModes, displayModes.length);
             mActiveSfDisplayMode = getModeById(displayModes, activeSfDisplayModeId);
             SurfaceControl.DisplayMode preferredSfDisplayMode =
@@ -379,6 +382,16 @@
                 sendTraversalRequestLocked();
             }
 
+            boolean renderFrameRateChanged = false;
+
+            if (mActiveRenderFrameRate > 0 &&  mActiveRenderFrameRate != renderFrameRate) {
+                Slog.d(TAG, "The render frame rate was changed from SurfaceFlinger or the display"
+                        + " device to " + renderFrameRate);
+                mActiveRenderFrameRate = renderFrameRate;
+                renderFrameRateChanged = true;
+                sendTraversalRequestLocked();
+            }
+
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
             if (mDisplayModeSpecs.baseModeId != INVALID_MODE_ID) {
@@ -398,7 +411,7 @@
             boolean recordsChanged = records.size() != mSupportedModes.size() || modesAdded;
             // If the records haven't changed then we're done here.
             if (!recordsChanged) {
-                return activeModeChanged || preferredModeChanged;
+                return activeModeChanged || preferredModeChanged || renderFrameRateChanged;
             }
 
             mSupportedModes.clear();
@@ -410,16 +423,19 @@
             if (mDefaultModeId == INVALID_MODE_ID) {
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (modesAdded && activeModeChanged) {
                 Slog.d(TAG, "New display modes are added and the active mode has changed, "
                         + "use active mode as default mode.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             } else if (findSfDisplayModeIdLocked(mDefaultModeId, mDefaultModeGroup) < 0) {
                 Slog.w(TAG, "Default display mode no longer available, using currently"
                         + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
                 mDefaultModeGroup = mActiveSfDisplayMode.group;
+                mActiveRenderFrameRate = renderFrameRate;
             }
 
             // Determine whether the display mode specs' base mode is still there.
@@ -620,6 +636,7 @@
                 mInfo.width = mActiveSfDisplayMode.width;
                 mInfo.height = mActiveSfDisplayMode.height;
                 mInfo.modeId = mActiveModeId;
+                mInfo.renderFrameRate = mActiveRenderFrameRate;
                 mInfo.defaultModeId = getPreferredModeId();
                 mInfo.supportedModes = getDisplayModes(mSupportedModes);
                 mInfo.colorMode = mActiveColorMode;
@@ -995,8 +1012,8 @@
             updateDeviceInfoLocked();
         }
 
-        public void onActiveDisplayModeChangedLocked(int sfModeId) {
-            if (updateActiveModeLocked(sfModeId)) {
+        public void onActiveDisplayModeChangedLocked(int sfModeId, float renderFrameRate) {
+            if (updateActiveModeLocked(sfModeId, renderFrameRate)) {
                 updateDeviceInfoLocked();
             }
         }
@@ -1008,8 +1025,9 @@
             }
         }
 
-        public boolean updateActiveModeLocked(int activeSfModeId) {
-            if (mActiveSfDisplayMode.id == activeSfModeId) {
+        public boolean updateActiveModeLocked(int activeSfModeId, float renderFrameRate) {
+            if (mActiveSfDisplayMode.id == activeSfModeId
+                    && mActiveRenderFrameRate == renderFrameRate) {
                 return false;
             }
             mActiveSfDisplayMode = getModeById(mSfDisplayModes, activeSfModeId);
@@ -1018,6 +1036,7 @@
                 Slog.w(TAG, "In unknown mode after setting allowed modes"
                         + ", activeModeId=" + activeSfModeId);
             }
+            mActiveRenderFrameRate = renderFrameRate;
             return true;
         }
 
@@ -1114,6 +1133,7 @@
                 pw.println("  " + sfDisplayMode);
             }
             pw.println("mActiveSfDisplayMode=" + mActiveSfDisplayMode);
+            pw.println("mActiveRenderFrameRate=" + mActiveRenderFrameRate);
             pw.println("mSupportedModes=");
             for (int i = 0; i < mSupportedModes.size(); i++) {
                 pw.println("  " + mSupportedModes.valueAt(i));
@@ -1288,7 +1308,8 @@
 
     public interface DisplayEventListener {
         void onHotplug(long timestampNanos, long physicalDisplayId, boolean connected);
-        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId);
+        void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod);
         void onFrameRateOverridesChanged(long timestampNanos, long physicalDisplayId,
                 DisplayEventReceiver.FrameRateOverride[] overrides);
 
@@ -1309,8 +1330,9 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
-            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId);
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
+            mListener.onModeChanged(timestampNanos, physicalDisplayId, modeId, renderPeriod);
         }
 
         @Override
@@ -1333,12 +1355,14 @@
         }
 
         @Override
-        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId) {
+        public void onModeChanged(long timestampNanos, long physicalDisplayId, int modeId,
+                long renderPeriod) {
             if (DEBUG) {
                 Slog.d(TAG, "onModeChanged("
                         + "timestampNanos=" + timestampNanos
                         + ", physicalDisplayId=" + physicalDisplayId
-                        + ", modeId=" + modeId + ")");
+                        + ", modeId=" + modeId
+                        + ", renderPeriod=" + renderPeriod + ")");
             }
             synchronized (getSyncRoot()) {
                 LocalDisplayDevice device = mDevices.get(physicalDisplayId);
@@ -1349,7 +1373,8 @@
                     }
                     return;
                 }
-                device.onActiveDisplayModeChangedLocked(modeId);
+                float renderFrameRate = 1e9f / renderPeriod;
+                device.onActiveDisplayModeChangedLocked(modeId, renderFrameRate);
             }
         }
 
@@ -1377,8 +1402,8 @@
 
     @VisibleForTesting
     public static class SurfaceControlProxy {
-        public SurfaceControl.DynamicDisplayInfo getDynamicDisplayInfo(IBinder token) {
-            return SurfaceControl.getDynamicDisplayInfo(token);
+        public SurfaceControl.DynamicDisplayInfo getDynamicDisplayInfo(long displayId) {
+            return SurfaceControl.getDynamicDisplayInfo(displayId);
         }
 
         public long[] getPhysicalDisplayIds() {
@@ -1389,8 +1414,8 @@
             return DisplayControl.getPhysicalDisplayToken(physicalDisplayId);
         }
 
-        public SurfaceControl.StaticDisplayInfo getStaticDisplayInfo(IBinder displayToken) {
-            return SurfaceControl.getStaticDisplayInfo(displayToken);
+        public SurfaceControl.StaticDisplayInfo getStaticDisplayInfo(long displayId) {
+            return SurfaceControl.getStaticDisplayInfo(displayId);
         }
 
         public SurfaceControl.DesiredDisplayModeSpecs getDesiredDisplayModeSpecs(
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 8dd169bf..c7b27de 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -377,6 +377,7 @@
             mBaseDisplayInfo.logicalHeight = maskedHeight;
             mBaseDisplayInfo.rotation = Surface.ROTATION_0;
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
+            mBaseDisplayInfo.renderFrameRate = deviceInfo.renderFrameRate;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
             mBaseDisplayInfo.supportedModes = Arrays.copyOf(
                     deviceInfo.supportedModes, deviceInfo.supportedModes.length);
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index 0e11b53..3e67f0a 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -340,6 +340,7 @@
                 mInfo.width = mode.getPhysicalWidth();
                 mInfo.height = mode.getPhysicalHeight();
                 mInfo.modeId = mode.getModeId();
+                mInfo.renderFrameRate = mode.getRefreshRate();
                 mInfo.defaultModeId = mModes[0].getModeId();
                 mInfo.supportedModes = mModes;
                 mInfo.densityDpi = rawMode.mDensityDpi;
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index f30a84f..e7601bc 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -619,7 +619,7 @@
 
     private static final class DisplayState {
         private int mColorMode;
-        private float mBrightness;
+        private float mBrightness = Float.NaN;
         private int mWidth;
         private int mHeight;
         private float mRefreshRate;
@@ -700,7 +700,11 @@
                         break;
                     case TAG_BRIGHTNESS_VALUE:
                         String brightness = parser.nextText();
-                        mBrightness = Float.parseFloat(brightness);
+                        try {
+                            mBrightness = Float.parseFloat(brightness);
+                        } catch (NumberFormatException e) {
+                            mBrightness = Float.NaN;
+                        }
                         break;
                     case TAG_BRIGHTNESS_CONFIGURATIONS:
                         mDisplayBrightnessConfigurations.loadFromXml(parser);
@@ -727,7 +731,9 @@
             serializer.endTag(null, TAG_COLOR_MODE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_VALUE);
-            serializer.text(Float.toString(mBrightness));
+            if (!Float.isNaN(mBrightness)) {
+                serializer.text(Float.toString(mBrightness));
+            }
             serializer.endTag(null, TAG_BRIGHTNESS_VALUE);
 
             serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index d24630d..a118b2f 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -448,6 +448,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.densityDpi = mDensityDpi;
diff --git a/services/core/java/com/android/server/display/WifiDisplayAdapter.java b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
index c759d98..e832701 100644
--- a/services/core/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/WifiDisplayAdapter.java
@@ -646,6 +646,7 @@
                 mInfo.width = mWidth;
                 mInfo.height = mHeight;
                 mInfo.modeId = mMode.getModeId();
+                mInfo.renderFrameRate = mMode.getRefreshRate();
                 mInfo.defaultModeId = mMode.getModeId();
                 mInfo.supportedModes = new Display.Mode[] { mMode };
                 mInfo.presentationDeadlineNanos = 1000000000L / (int) mRefreshRate; // 1 frame
diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
index 28dc318..3c5b067 100644
--- a/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
+++ b/services/core/java/com/android/server/graphics/fonts/FontManagerService.java
@@ -187,8 +187,8 @@
         }
 
         @Override
-        public void setUpFsverity(String filePath, byte[] pkcs7Signature) throws IOException {
-            VerityUtils.setUpFsverity(filePath, pkcs7Signature);
+        public void setUpFsverity(String filePath) throws IOException {
+            VerityUtils.setUpFsverity(filePath, /* signature */ (byte[]) null);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
index 457d5b7..6f93608 100644
--- a/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
+++ b/services/core/java/com/android/server/graphics/fonts/UpdatableFontDir.java
@@ -78,7 +78,7 @@
     interface FsverityUtil {
         boolean isFromTrustedProvider(String path, byte[] pkcs7Signature);
 
-        void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException;
+        void setUpFsverity(String path) throws IOException;
 
         boolean rename(File src, File dest);
     }
@@ -354,8 +354,7 @@
             try {
                 // Do not parse font file before setting up fs-verity.
                 // setUpFsverity throws IOException if failed.
-                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath(),
-                        pkcs7Signature);
+                mFsverityUtil.setUpFsverity(tempNewFontFile.getAbsolutePath());
             } catch (IOException e) {
                 throw new SystemFontException(
                         FontManager.RESULT_ERROR_VERIFICATION_FAILURE,
diff --git a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
index 4855be6..ccb2633 100644
--- a/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
+++ b/services/core/java/com/android/server/hdmi/ArcTerminationActionFromAvr.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 
 /**
@@ -33,6 +35,10 @@
         super(source);
     }
 
+    ArcTerminationActionFromAvr(HdmiCecLocalDevice source, IHdmiControlCallback callback) {
+        super(source, callback);
+    }
+
     @Override
     boolean start() {
         mState = STATE_WAITING_FOR_INITIATE_ARC_RESPONSE;
@@ -47,10 +53,19 @@
             return false;
         }
         switch (cmd.getOpcode()) {
+            case Constants.MESSAGE_FEATURE_ABORT:
+                int originalOpcode = cmd.getParams()[0] & 0xFF;
+                if (originalOpcode == Constants.MESSAGE_TERMINATE_ARC) {
+                    mState = STATE_ARC_TERMINATED;
+                    audioSystem().processArcTermination();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+                    return true;
+                }
+                return false;
             case Constants.MESSAGE_REPORT_ARC_TERMINATED:
                 mState = STATE_ARC_TERMINATED;
                 audioSystem().processArcTermination();
-                finish();
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
                 return true;
         }
         return false;
@@ -79,7 +94,7 @@
                         audioSystem().setArcStatus(false);
                     }
                     HdmiLogger.debug("Terminate ARC was not successfully sent.");
-                    finish();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
                 }
             });
     }
@@ -88,6 +103,6 @@
         // Disable ARC if TV didn't respond with <Report ARC Terminated> in time.
         audioSystem().setArcStatus(false);
         HdmiLogger.debug("handleTerminateArcTimeout");
-        finish();
+        finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index ccaa9255d..a026c4b 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -458,8 +458,16 @@
             HdmiLogger.debug("ARC is not established between TV and AVR device");
             return Constants.ABORT_NOT_IN_CORRECT_MODE;
         } else {
-            removeAction(ArcTerminationActionFromAvr.class);
-            addAndStartAction(new ArcTerminationActionFromAvr(this));
+            if (!getActions(ArcTerminationActionFromAvr.class).isEmpty()
+                    && !getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.isEmpty()) {
+                IHdmiControlCallback callback =
+                        getActions(ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0);
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this, callback));
+            } else {
+                removeAction(ArcTerminationActionFromAvr.class);
+                addAndStartAction(new ArcTerminationActionFromAvr(this));
+            }
             return Constants.HANDLED;
         }
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 43cd71a..2f15e57 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -878,16 +878,30 @@
             Slog.w(TAG, "Device type doesn't support ARC.");
             return;
         }
+        boolean isArcEnabled = false;
         if (settingValue == SOUNDBAR_MODE_DISABLED && audioSystem != null) {
-            if (audioSystem.isArcEnabled()) {
-                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem));
-            }
+            isArcEnabled = audioSystem.isArcEnabled();
             if (isSystemAudioActivated()) {
                 audioSystem.terminateSystemAudioMode();
             }
+            if (isArcEnabled) {
+                if (audioSystem.hasAction(ArcTerminationActionFromAvr.class)) {
+                    audioSystem.removeAction(ArcTerminationActionFromAvr.class);
+                }
+                audioSystem.addAndStartAction(new ArcTerminationActionFromAvr(audioSystem,
+                        new IHdmiControlCallback.Stub() {
+                            @Override
+                            public void onComplete(int result) {
+                                mAddressAllocated = false;
+                                initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+                            }
+                        }));
+            }
         }
-        mAddressAllocated = false;
-        initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+        if (!isArcEnabled) {
+            mAddressAllocated = false;
+            initializeCecLocalDevices(INITIATED_BY_SOUNDBAR_MODE);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 81d782e..0da04a2 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -294,6 +294,9 @@
     // Manages Keyboard backlight
     private final KeyboardBacklightController mKeyboardBacklightController;
 
+    // Manages Keyboard modifier keys remapping
+    private final KeyRemapper mKeyRemapper;
+
     // Maximum number of milliseconds to wait for input event injection.
     private static final int INJECTION_TIMEOUT_MILLIS = 30 * 1000;
 
@@ -408,6 +411,7 @@
         mBatteryController = new BatteryController(mContext, mNative, injector.getLooper());
         mKeyboardBacklightController = new KeyboardBacklightController(mContext, mNative,
                 mDataStore, injector.getLooper());
+        mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
                 mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
@@ -536,6 +540,7 @@
         mKeyboardLayoutManager.systemRunning();
         mBatteryController.systemRunning();
         mKeyboardBacklightController.systemRunning();
+        mKeyRemapper.systemRunning();
     }
 
     private void reloadDeviceAliases() {
@@ -2738,6 +2743,27 @@
         return mKeyboardLayoutManager.getKeyboardLayoutOverlay(identifier);
     }
 
+    @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    @Override // Binder call
+    public void remapModifierKey(int fromKey, int toKey) {
+        super.remapModifierKey_enforcePermission();
+        mKeyRemapper.remapKey(fromKey, toKey);
+    }
+
+    @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    @Override // Binder call
+    public void clearAllModifierKeyRemappings() {
+        super.clearAllModifierKeyRemappings_enforcePermission();
+        mKeyRemapper.clearAllKeyRemappings();
+    }
+
+    @EnforcePermission(Manifest.permission.REMAP_MODIFIER_KEYS)
+    @Override // Binder call
+    public Map<Integer, Integer> getModifierKeyRemapping() {
+        super.getModifierKeyRemapping_enforcePermission();
+        return mKeyRemapper.getKeyRemapping();
+    }
+
     // Native callback.
     @SuppressWarnings("unused")
     private String getDeviceAlias(String uniqueId) {
diff --git a/services/core/java/com/android/server/input/KeyRemapper.java b/services/core/java/com/android/server/input/KeyRemapper.java
new file mode 100644
index 0000000..950e094
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyRemapper.java
@@ -0,0 +1,161 @@
+/*
+ * 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.input;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.view.InputDevice;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A component of {@link InputManagerService} responsible for managing key remappings.
+ *
+ * @hide
+ */
+final class KeyRemapper implements InputManager.InputDeviceListener {
+
+    private static final int MSG_UPDATE_EXISTING_DEVICES = 1;
+    private static final int MSG_REMAP_KEY = 2;
+    private static final int MSG_CLEAR_ALL_REMAPPING = 3;
+
+    private final Context mContext;
+    private final NativeInputManagerService mNative;
+    // The PersistentDataStore should be locked before use.
+    @GuardedBy("mDataStore")
+    private final PersistentDataStore mDataStore;
+    private final Handler mHandler;
+
+    KeyRemapper(Context context, NativeInputManagerService nativeService,
+            PersistentDataStore dataStore, Looper looper) {
+        mContext = context;
+        mNative = nativeService;
+        mDataStore = dataStore;
+        mHandler = new Handler(looper, this::handleMessage);
+    }
+
+    public void systemRunning() {
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        inputManager.registerInputDeviceListener(this, mHandler);
+
+        Message msg = Message.obtain(mHandler, MSG_UPDATE_EXISTING_DEVICES,
+                inputManager.getInputDeviceIds());
+        mHandler.sendMessage(msg);
+    }
+
+    public void remapKey(int fromKey, int toKey) {
+        Message msg = Message.obtain(mHandler, MSG_REMAP_KEY, fromKey, toKey);
+        mHandler.sendMessage(msg);
+    }
+
+    public void clearAllKeyRemappings() {
+        Message msg = Message.obtain(mHandler, MSG_CLEAR_ALL_REMAPPING);
+        mHandler.sendMessage(msg);
+    }
+
+    public Map<Integer, Integer> getKeyRemapping() {
+        synchronized (mDataStore) {
+            return mDataStore.getKeyRemapping();
+        }
+    }
+
+    private void addKeyRemapping(int fromKey, int toKey) {
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        for (int deviceId : inputManager.getInputDeviceIds()) {
+            InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+            if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+                mNative.addKeyRemapping(deviceId, fromKey, toKey);
+            }
+        }
+    }
+
+    private void remapKeyInternal(int fromKey, int toKey) {
+        addKeyRemapping(fromKey, toKey);
+        synchronized (mDataStore) {
+            try {
+                if (fromKey == toKey) {
+                    mDataStore.clearMappedKey(fromKey);
+                } else {
+                    mDataStore.remapKey(fromKey, toKey);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    private void clearAllRemappingsInternal() {
+        synchronized (mDataStore) {
+            try {
+                Map<Integer, Integer> keyRemapping = mDataStore.getKeyRemapping();
+                for (int fromKey : keyRemapping.keySet()) {
+                    mDataStore.clearMappedKey(fromKey);
+
+                    // Remapping to itself will clear the remapping on native side
+                    addKeyRemapping(fromKey, fromKey);
+                }
+            } finally {
+                mDataStore.saveIfNeeded();
+            }
+        }
+    }
+
+    @Override
+    public void onInputDeviceAdded(int deviceId) {
+        InputManager inputManager = Objects.requireNonNull(
+                mContext.getSystemService(InputManager.class));
+        InputDevice inputDevice = inputManager.getInputDevice(deviceId);
+        if (inputDevice != null && !inputDevice.isVirtual() && inputDevice.isFullKeyboard()) {
+            Map<Integer, Integer> remapping = getKeyRemapping();
+            remapping.forEach(
+                    (fromKey, toKey) -> mNative.addKeyRemapping(deviceId, fromKey, toKey));
+        }
+    }
+
+    @Override
+    public void onInputDeviceRemoved(int deviceId) {
+    }
+
+    @Override
+    public void onInputDeviceChanged(int deviceId) {
+    }
+
+    private boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case MSG_UPDATE_EXISTING_DEVICES:
+                for (int deviceId : (int[]) msg.obj) {
+                    onInputDeviceAdded(deviceId);
+                }
+                return true;
+            case MSG_REMAP_KEY:
+                remapKeyInternal(msg.arg1, msg.arg2);
+                return true;
+            case MSG_CLEAR_ALL_REMAPPING:
+                clearAllRemappingsInternal();
+                return true;
+        }
+        return false;
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 85d6197..1bb14aa 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -145,7 +145,7 @@
     @Override
     public void onInputDeviceChanged(int deviceId) {
         final InputDevice inputDevice = getInputDevice(deviceId);
-        if (inputDevice == null) {
+        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
         }
         synchronized (mDataStore) {
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index cfa7fb1..8781c6e 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -47,6 +47,8 @@
 
     int getSwitchState(int deviceId, int sourceMask, int sw);
 
+    void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
     boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes, boolean[] keyExists);
 
     int getKeyCodeForKeyLocation(int deviceId, int locationKeyCode);
@@ -235,6 +237,9 @@
         public native int getSwitchState(int deviceId, int sourceMask, int sw);
 
         @Override
+        public native void addKeyRemapping(int deviceId, int fromKeyCode, int toKeyCode);
+
+        @Override
         public native boolean hasKeys(int deviceId, int sourceMask, int[] keyCodes,
                 boolean[] keyExists);
 
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index 1bb10c7..375377a7 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -27,14 +27,13 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 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.XmlPullParserException;
 
-import com.android.modules.utils.TypedXmlPullParser;
-import com.android.modules.utils.TypedXmlSerializer;
-
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
@@ -64,6 +63,8 @@
 final class PersistentDataStore {
     static final String TAG = "InputManager";
 
+    private static final int INVALID_VALUE = -1;
+
     // Input device state by descriptor.
     private final HashMap<String, InputDeviceState> mInputDevices =
             new HashMap<String, InputDeviceState>();
@@ -77,6 +78,9 @@
     // True if there are changes to be saved.
     private boolean mDirty;
 
+    // Storing key remapping
+    private Map<Integer, Integer> mKeyRemapping = new HashMap<>();
+
     public PersistentDataStore() {
         this(new Injector());
     }
@@ -187,6 +191,30 @@
         return state.getKeyboardBacklightBrightness(lightId);
     }
 
+    public boolean remapKey(int fromKey, int toKey) {
+        loadIfNeeded();
+        if (mKeyRemapping.getOrDefault(fromKey, INVALID_VALUE) == toKey) {
+            return false;
+        }
+        mKeyRemapping.put(fromKey, toKey);
+        setDirty();
+        return true;
+    }
+
+    public boolean clearMappedKey(int key) {
+        loadIfNeeded();
+        if (mKeyRemapping.containsKey(key)) {
+            mKeyRemapping.remove(key);
+            setDirty();
+        }
+        return true;
+    }
+
+    public Map<Integer, Integer> getKeyRemapping() {
+        loadIfNeeded();
+        return new HashMap<>(mKeyRemapping);
+    }
+
     public boolean removeUninstalledKeyboardLayouts(Set<String> availableKeyboardLayouts) {
         boolean changed = false;
         for (InputDeviceState state : mInputDevices.values()) {
@@ -229,6 +257,7 @@
     }
 
     private void clearState() {
+        mKeyRemapping.clear();
         mInputDevices.clear();
     }
 
@@ -280,7 +309,9 @@
         XmlUtils.beginDocument(parser, "input-manager-state");
         final int outerDepth = parser.getDepth();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
-            if (parser.getName().equals("input-devices")) {
+            if (parser.getName().equals("key-remapping")) {
+                loadKeyRemappingFromXml(parser);
+            } else if (parser.getName().equals("input-devices")) {
                 loadInputDevicesFromXml(parser);
             }
         }
@@ -307,10 +338,31 @@
         }
     }
 
+    private void loadKeyRemappingFromXml(TypedXmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
+            if (parser.getName().equals("remap")) {
+                int fromKey = parser.getAttributeInt(null, "from-key");
+                int toKey = parser.getAttributeInt(null, "to-key");
+                mKeyRemapping.put(fromKey, toKey);
+            }
+        }
+    }
+
     private void saveToXml(TypedXmlSerializer serializer) throws IOException {
         serializer.startDocument(null, true);
         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
         serializer.startTag(null, "input-manager-state");
+        serializer.startTag(null, "key-remapping");
+        for (int fromKey : mKeyRemapping.keySet()) {
+            int toKey = mKeyRemapping.get(fromKey);
+            serializer.startTag(null, "remap");
+            serializer.attributeInt(null, "from-key", fromKey);
+            serializer.attributeInt(null, "to-key", toKey);
+            serializer.endTag(null, "remap");
+        }
+        serializer.endTag(null, "key-remapping");
         serializer.startTag(null, "input-devices");
         for (Map.Entry<String, InputDeviceState> entry : mInputDevices.entrySet()) {
             final String descriptor = entry.getKey();
@@ -329,7 +381,6 @@
         private static final String[] CALIBRATION_NAME = { "x_scale",
                 "x_ymix", "x_offset", "y_xmix", "y_scale", "y_offset" };
 
-        private static final int INVALID_VALUE = -1;
         private final TouchCalibration[] mTouchCalibration = new TouchCalibration[4];
         @Nullable
         private String mCurrentKeyboardLayout;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index ebf9237d..be99bfb 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -199,9 +199,14 @@
     }
 
     /**
-     * Utility class for putting and getting settings for InputMethod
+     * Utility class for putting and getting settings for InputMethod.
+     *
+     * This is used in two ways:
+     * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to
+     * follow the current user.
+     * - On-demand instances when we need settings for non-current users.
+     *
      * TODO: Move all putters and getters of settings to this class.
-     * TODO(b/235661780): Make the setting supports multi-users.
      */
     @UserHandleAware
     public static class InputMethodSettings {
@@ -212,9 +217,9 @@
                 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
 
         @NonNull
-        private final Context mUserAwareContext;
-        private final Resources mRes;
-        private final ContentResolver mResolver;
+        private Context mUserAwareContext;
+        private Resources mRes;
+        private ContentResolver mResolver;
         private final ArrayMap<String, InputMethodInfo> mMethodMap;
 
         /**
@@ -272,15 +277,19 @@
             return imsList;
         }
 
-        InputMethodSettings(@NonNull Context context,
-                ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
-                boolean copyOnWrite) {
+        private void initContentWithUserContext(@NonNull Context context, @UserIdInt int userId) {
             mUserAwareContext = context.getUserId() == userId
                     ? context
                     : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
             mRes = mUserAwareContext.getResources();
             mResolver = mUserAwareContext.getContentResolver();
+        }
+
+        InputMethodSettings(@NonNull Context context,
+                ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId,
+                boolean copyOnWrite) {
             mMethodMap = methodMap;
+            initContentWithUserContext(context, userId);
             switchCurrentUser(userId, copyOnWrite);
         }
 
@@ -301,6 +310,9 @@
                 mEnabledInputMethodsStrCache = "";
                 // TODO: mCurrentProfileIds should be cleared here.
             }
+            if (mUserAwareContext.getUserId() != userId) {
+                initContentWithUserContext(mUserAwareContext, userId);
+            }
             mCurrentUserId = userId;
             mCopyOnWrite = copyOnWrite;
             // TODO: mCurrentProfileIds should be updated here.
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 0ae3a02..2dc1e1c 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -21,6 +21,8 @@
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS;
 import static android.Manifest.permission.SET_INITIAL_LOCK;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_DETAIL;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_MESSAGE;
 import static android.app.admin.DevicePolicyResources.Strings.Core.PROFILE_ENCRYPTED_TITLE;
@@ -69,7 +71,7 @@
 import android.content.pm.UserInfo;
 import android.database.ContentObserver;
 import android.database.sqlite.SQLiteDatabase;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.face.Face;
 import android.hardware.face.FaceManager;
@@ -91,6 +93,7 @@
 import android.os.UserManager;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.security.AndroidKeyStoreMaintenance;
@@ -265,7 +268,8 @@
     protected boolean mHasSecureLockScreen;
 
     protected IGateKeeperService mGateKeeperService;
-    protected IAuthSecret mAuthSecretService;
+    protected IAuthSecret mAuthSecretServiceAidl;
+    protected android.hardware.authsecret.V1_0.IAuthSecret mAuthSecretServiceHidl;
 
     private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
 
@@ -833,12 +837,19 @@
     }
 
     private void getAuthSecretHal() {
-        try {
-            mAuthSecretService = IAuthSecret.getService(/* retry */ true);
-        } catch (NoSuchElementException e) {
-            Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to get AuthSecret HAL", e);
+        mAuthSecretServiceAidl = IAuthSecret.Stub.asInterface(ServiceManager.
+                                 waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
+        if (mAuthSecretServiceAidl == null) {
+            Slog.i(TAG, "Device doesn't implement AuthSecret HAL(aidl), try to get hidl version");
+
+            try {
+                mAuthSecretServiceHidl =
+                    android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+            } catch (NoSuchElementException e) {
+                Slog.i(TAG, "Device doesn't implement AuthSecret HAL(hidl)");
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
+            }
         }
     }
 
@@ -2601,17 +2612,25 @@
         // If the given user is the primary user, pass the auth secret to the HAL.  Only the system
         // user can be primary.  Check for the system user ID before calling getUserInfo(), as other
         // users may still be under construction.
-        if (mAuthSecretService != null && userId == UserHandle.USER_SYSTEM &&
+        if (userId == UserHandle.USER_SYSTEM &&
                 mUserManager.getUserInfo(userId).isPrimary()) {
-            try {
-                final byte[] rawSecret = sp.deriveVendorAuthSecret();
-                final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
-                for (int i = 0; i < rawSecret.length; ++i) {
-                    secret.add(rawSecret[i]);
+            final byte[] rawSecret = sp.deriveVendorAuthSecret();
+            if (mAuthSecretServiceAidl != null) {
+                try {
+                    mAuthSecretServiceAidl.setPrimaryUserCredential(rawSecret);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(aidl)", e);
                 }
-                mAuthSecretService.primaryUserCredential(secret);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
+            } else if (mAuthSecretServiceHidl != null) {
+                try {
+                    final ArrayList<Byte> secret = new ArrayList<>(rawSecret.length);
+                    for (int i = 0; i < rawSecret.length; ++i) {
+                        secret.add(rawSecret[i]);
+                    }
+                    mAuthSecretServiceHidl.primaryUserCredential(secret);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL(hidl)", e);
+                }
             }
         }
     }
@@ -3167,18 +3186,34 @@
      * if we are running an automotive build.
      */
     private void disableEscrowTokenOnNonManagedDevicesIfNeeded(int userId) {
-        final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
+        // TODO(b/258213147): Remove
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
 
-        // Managed profile should have escrow enabled
-        if (userManagerInternal.isUserManaged(userId)) {
-            Slog.i(TAG, "Managed profile can have escrow token");
-            return;
-        }
+                if (mInjector.getDeviceStateCache().isUserOrganizationManaged(userId)) {
+                    Slog.i(TAG, "Organization managed users can have escrow token");
+                    return;
+                }
+            } else {
+                final UserManagerInternal userManagerInternal = mInjector.getUserManagerInternal();
 
-        // Devices with Device Owner should have escrow enabled on all users.
-        if (userManagerInternal.isDeviceManaged()) {
-            Slog.i(TAG, "Corp-owned device can have escrow token");
-            return;
+                // Managed profile should have escrow enabled
+                if (userManagerInternal.isUserManaged(userId)) {
+                    Slog.i(TAG, "Managed profile can have escrow token");
+                    return;
+                }
+
+                // Devices with Device Owner should have escrow enabled on all users.
+                if (userManagerInternal.isDeviceManaged()) {
+                    Slog.i(TAG, "Corp-owned device can have escrow token");
+                    return;
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
 
         // If the device is yet to be provisioned (still in SUW), there is still
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index d08150c..e51ed1b 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -2046,6 +2046,11 @@
                 int controllerUid) {
             final int uid = Binder.getCallingUid();
             final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+            if (LocalServices.getService(PackageManagerInternal.class)
+                    .filterAppAccess(controllerPackageName, uid, userId)) {
+                // The controllerPackageName is not visible to the caller.
+                return false;
+            }
             final long token = Binder.clearCallingIdentity();
             try {
                 // Don't perform check between controllerPackageName and controllerUid.
diff --git a/services/core/java/com/android/server/media/projection/OWNERS b/services/core/java/com/android/server/media/projection/OWNERS
index 9ca3910..832bcd9 100644
--- a/services/core/java/com/android/server/media/projection/OWNERS
+++ b/services/core/java/com/android/server/media/projection/OWNERS
@@ -1,2 +1 @@
-michaelwr@google.com
-santoscordon@google.com
+include /media/java/android/media/projection/OWNERS
diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
index ef1e11c..4031c83 100644
--- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java
+++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java
@@ -18,8 +18,8 @@
 
 import static android.service.notification.NotificationListenerService.REASON_ASSISTANT_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationListenerService.REASON_CLEAR_DATA;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
-import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -172,7 +172,12 @@
         NOTIFICATION_CANCEL_SNOOZED(181),
         @UiEvent(doc = "Notification was canceled due to timeout")
         NOTIFICATION_CANCEL_TIMEOUT(182),
-        // Values 183-189 reserved for future system dismissal reasons
+        @UiEvent(doc = "Notification was canceled due to the backing channel being deleted")
+        NOTIFICATION_CANCEL_CHANNEL_REMOVED(1261),
+        @UiEvent(doc = "Notification was canceled due to the app's storage being cleared")
+        NOTIFICATION_CANCEL_CLEAR_DATA(1262),
+        // Values above this line must remain in the same order as the corresponding
+        // NotificationCancelReason enum values.
         @UiEvent(doc = "Notification was canceled due to user dismissal of a peeking notification.")
         NOTIFICATION_CANCEL_USER_PEEK(190),
         @UiEvent(doc = "Notification was canceled due to user dismissal from the always-on display")
@@ -208,7 +213,7 @@
             // Most cancel reasons do not have a meaningful surface. Reason codes map directly
             // to NotificationCancelledEvent codes.
             if (surface == NotificationStats.DISMISSAL_OTHER) {
-                if ((REASON_CLICK <= reason) && (reason <= REASON_TIMEOUT)) {
+                if ((REASON_CLICK <= reason) && (reason <= REASON_CLEAR_DATA)) {
                     return NotificationCancelledEvent.values()[reason];
                 }
                 if (reason == REASON_ASSISTANT_CANCEL) {
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 4bbd40d..5f8572b 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -59,6 +59,9 @@
 
     static final int CONCURRENT_SNOOZE_LIMIT = 500;
 
+    // A safe size for strings to be put in persistent storage, to avoid breaking the XML write.
+    static final int MAX_STRING_LENGTH = 1000;
+
     protected static final String XML_TAG_NAME = "snoozed-notifications";
 
     private static final String XML_SNOOZED_NOTIFICATION = "notification";
@@ -200,7 +203,7 @@
         scheduleRepost(key, duration);
         Long activateAt = System.currentTimeMillis() + duration;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.put(key, activateAt);
+            mPersistedSnoozedNotifications.put(getTrimmedString(key), activateAt);
         }
     }
 
@@ -210,7 +213,10 @@
     protected void snooze(NotificationRecord record, String contextId) {
         if (contextId != null) {
             synchronized (mLock) {
-                mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
+                mPersistedSnoozedNotificationsWithContext.put(
+                        getTrimmedString(record.getKey()),
+                        getTrimmedString(contextId)
+                );
             }
         }
         snooze(record);
@@ -225,6 +231,13 @@
         }
     }
 
+    private String getTrimmedString(String key) {
+        if (key != null && key.length() > MAX_STRING_LENGTH) {
+            return key.substring(0, MAX_STRING_LENGTH);
+        }
+        return key;
+    }
+
     protected boolean cancel(int userId, String pkg, String tag, int id) {
         synchronized (mLock) {
             final Set<Map.Entry<String, NotificationRecord>> records =
@@ -293,10 +306,12 @@
     }
 
     protected void repost(String key, int userId, boolean muteOnReturn) {
+        final String trimmedKey = getTrimmedString(key);
+
         NotificationRecord record;
         synchronized (mLock) {
-            mPersistedSnoozedNotifications.remove(key);
-            mPersistedSnoozedNotificationsWithContext.remove(key);
+            mPersistedSnoozedNotifications.remove(trimmedKey);
+            mPersistedSnoozedNotificationsWithContext.remove(trimmedKey);
             record = mSnoozedNotifications.remove(key);
         }
 
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 9ea350f..e6e8212a 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -16,15 +16,22 @@
 
 package com.android.server.pm;
 
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
+
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.ActivityManagerInternal;
 import android.content.Context;
+import android.media.AudioManager;
 import android.media.IAudioService;
 import android.os.ServiceManager;
+import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.ArraySet;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.server.LocalServices;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -72,6 +79,43 @@
     }
 
     /**
+     * True if any app is using voice communication.
+     */
+    private boolean hasVoiceCall() {
+        var am = mContext.getSystemService(AudioManager.class);
+        try {
+            for (var apc : am.getActivePlaybackConfigurations()) {
+                if (!apc.isActive()) {
+                    continue;
+                }
+                var usage = apc.getAudioAttributes().getUsage();
+                if (usage == USAGE_VOICE_COMMUNICATION
+                        || usage == USAGE_VOICE_COMMUNICATION_SIGNALLING) {
+                    return true;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
+     * True if the app is recording audio.
+     */
+    private boolean isRecordingAudio(String packageName) {
+        var am = mContext.getSystemService(AudioManager.class);
+        try {
+            for (var arc : am.getActiveRecordingConfigurations()) {
+                if (TextUtils.equals(arc.getClientPackageName(), packageName)) {
+                    return true;
+                }
+            }
+        } catch (Exception ignore) {
+        }
+        return false;
+    }
+
+    /**
      * True if the app is in the foreground.
      */
     private boolean isAppForeground(String packageName) {
@@ -89,8 +133,7 @@
      * True if the app is playing/recording audio.
      */
     private boolean hasActiveAudio(String packageName) {
-        // TODO(b/235306967): also check recording
-        return hasAudioFocus(packageName);
+        return hasAudioFocus(packageName) || isRecordingAudio(packageName);
     }
 
     /**
@@ -143,16 +186,16 @@
      * True if there is an ongoing phone call.
      */
     public boolean isInCall() {
-        // To be implemented
-        return false;
+        // TelecomManager doesn't handle the case where some apps don't implement ConnectionService.
+        // We check apps using voice communication to detect if the device is in call.
+        var tm = mContext.getSystemService(TelecomManager.class);
+        return tm.isInCall() || hasVoiceCall();
     }
 
     /**
      * Returns a list of packages which depend on {@code packageNames}. These are the packages
      * that will be affected when updating {@code packageNames} and should participate in
      * the evaluation of install constraints.
-     *
-     * TODO(b/235306967): Also include bounded services as dependency.
      */
     public List<String> getDependencyPackages(List<String> packageNames) {
         var results = new ArraySet<String>();
@@ -167,6 +210,10 @@
                 }
             }
         }
+        var amInternal = LocalServices.getService(ActivityManagerInternal.class);
+        for (var packageName : packageNames) {
+            results.addAll(amInternal.getClientPackages(packageName));
+        }
         return new ArrayList<>(results);
     }
 }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 6998db7..a6def7d 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -261,7 +261,7 @@
             final boolean killApp = (deleteFlags & PackageManager.DELETE_DONT_KILL_APP) == 0;
             info.sendPackageRemovedBroadcasts(killApp, removedBySystem);
             info.sendSystemPackageUpdatedBroadcasts();
-            PackageMetrics.onUninstallSucceeded(info, deleteFlags, mUserManagerInternal);
+            PackageMetrics.onUninstallSucceeded(info, deleteFlags, userId);
         }
 
         // Force a gc to clear up things.
@@ -550,6 +550,7 @@
             outInfo.mRemovedUsers = userIds;
             outInfo.mBroadcastUsers = userIds;
             outInfo.mIsExternal = ps.isExternalStorage();
+            outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7553370..33fe4c5 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1608,6 +1608,7 @@
                             ps.getUninstallReason(userId));
                 }
                 removedInfo.mIsExternal = oldPackage.isExternalStorage();
+                removedInfo.mRemovedPackageVersionCode = oldPackage.getLongVersionCode();
                 request.setRemovedInfo(removedInfo);
 
                 sysPkg = oldPackage.isSystem();
@@ -2241,6 +2242,7 @@
     @GuardedBy("mPm.mInstallLock")
     private void executePostCommitStepsLIF(List<ReconciledPackage> reconciledPackages) {
         final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
+        final ArrayList<String> apkPaths = new ArrayList<>();
         for (ReconciledPackage reconciledPkg : reconciledPackages) {
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
@@ -2260,25 +2262,11 @@
             }
 
             // Enabling fs-verity is a blocking operation. To reduce the impact to the install time,
-            // run in a background thread.
-            final ArrayList<String> apkPaths = new ArrayList<>();
+            // collect the files to later enable in a background thread.
             apkPaths.add(pkg.getBaseApkPath());
             if (pkg.getSplitCodePaths() != null) {
                 Collections.addAll(apkPaths, pkg.getSplitCodePaths());
             }
-            mInjector.getBackgroundHandler().post(() -> {
-                try {
-                    for (String path : apkPaths) {
-                        if (!VerityUtils.hasFsverity(path)) {
-                            VerityUtils.setUpFsverity(path, (byte[]) null);
-                        }
-                    }
-                } catch (IOException e) {
-                    // There's nothing we can do if the setup failed. Since fs-verity is
-                    // optional, just ignore the error for now.
-                    Slog.e(TAG, "Failed to fully enable fs-verity to " + packageName);
-                }
-            });
 
             // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
             mAppDataHelper.prepareAppDataPostCommitLIF(pkg, 0);
@@ -2392,6 +2380,20 @@
         }
         PackageManagerServiceUtils.waitForNativeBinariesExtractionForIncremental(
                 incrementalStorages);
+
+        mInjector.getBackgroundHandler().post(() -> {
+            for (String path : apkPaths) {
+                if (!VerityUtils.hasFsverity(path)) {
+                    try {
+                        VerityUtils.setUpFsverity(path, (byte[]) null);
+                    } catch (IOException e) {
+                        // There's nothing we can do if the setup failed. Since fs-verity is
+                        // optional, just ignore the error for now.
+                        Slog.e(TAG, "Failed to fully enable fs-verity to " + path);
+                    }
+                }
+            }
+        });
     }
 
     Pair<Integer, String> verifyReplacingVersionCode(PackageInfoLite pkgLite,
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 5974a9c..c6cdc4c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -773,10 +773,10 @@
         }
     }
 
-    public void onInstallCompleted() {
+    public void onInstallCompleted(int userId) {
         if (getReturnCode() == INSTALL_SUCCEEDED) {
             if (mPackageMetrics != null) {
-                mPackageMetrics.onInstallSucceed();
+                mPackageMetrics.onInstallSucceed(userId);
             }
         }
     }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 2b6398a..b600aa8 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -535,7 +535,7 @@
             mInstallPackageHelper.installPackagesTraced(installRequests);
 
             for (InstallRequest request : installRequests) {
-                request.onInstallCompleted();
+                request.onInstallCompleted(mUser.getIdentifier());
                 doPostInstall(request);
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 409d352..4803c5e 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -154,6 +154,11 @@
     /** Destroy sessions older than this on storage free request */
     private static final long MAX_SESSION_AGE_ON_LOW_STORAGE_MILLIS = 8 * DateUtils.HOUR_IN_MILLIS;
 
+    /** Threshold of historical sessions size */
+    private static final int HISTORICAL_SESSIONS_THRESHOLD = 500;
+    /** Size of historical sessions to be cleared when reaching threshold */
+    private static final int HISTORICAL_CLEAR_SIZE = 400;
+
     /**
      * Allow verification-skipping if it's a development app installed through ADB with
      * disable verification flag specified.
@@ -549,6 +554,10 @@
         CharArrayWriter writer = new CharArrayWriter();
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "    ");
         session.dump(pw);
+        if (mHistoricalSessions.size() > HISTORICAL_SESSIONS_THRESHOLD) {
+            Slog.d(TAG, "Historical sessions size reaches threshold, clear the oldest");
+            mHistoricalSessions.subList(0, HISTORICAL_CLEAR_SIZE).clear();
+        }
         mHistoricalSessions.add(writer.toString());
 
         int installerUid = session.getInstallerUid();
diff --git a/services/core/java/com/android/server/pm/PackageManagerLocal.java b/services/core/java/com/android/server/pm/PackageManagerLocal.java
index 21c2f2c..d163d3d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerLocal.java
+++ b/services/core/java/com/android/server/pm/PackageManagerLocal.java
@@ -30,7 +30,6 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /**
  * In-process API for server side PackageManager related infrastructure.
@@ -167,15 +166,15 @@
         PackageState getPackageState(@NonNull String packageName);
 
         /**
-         * Iterates on all states. This should only be used when either the target package name
-         * is not known or the large majority of the states are expected to be used.
-         *
+         * Returns a map of all {@link PackageState PackageStates} on the device.
+         * <p>
          * This will cause app visibility filtering to be invoked on each state on the device,
-         * which can be expensive.
+         * which can be expensive. Prefer {@link #getPackageState(String)} if possible.
          *
-         * @param consumer Block to accept each state as it becomes available post-filtering.
+         * @return Mapping of package name to {@link PackageState}.
          */
-        void forAllPackageStates(@NonNull Consumer<PackageState> consumer);
+        @NonNull
+        Map<String, PackageState> getPackageStates();
 
         @Override
         void close();
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index cf59a1e..91f7011 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3078,6 +3078,7 @@
         info.mRemovedUsers = new int[] {userId};
         info.mBroadcastUsers = new int[] {userId};
         info.mUid = UserHandle.getUid(userId, packageState.getAppId());
+        info.mRemovedPackageVersionCode = packageState.getVersionCode();
         info.sendPackageRemovedBroadcasts(true /*killApp*/, false /*removedBySystem*/);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 77334e5..a72ae56 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -142,6 +142,11 @@
     public static final Predicate<PackageStateInternal> REMOVE_IF_NULL_PKG =
             pkgSetting -> pkgSetting.getPkg() == null;
 
+    // This is a horrible hack to workaround b/240373119, specifically for fixing the T branch.
+    // A proper fix should be implemented in master instead.
+    public static final ThreadLocal<Boolean> DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS =
+            ThreadLocal.withInitial(() -> false);
+
     /**
      * Components of apps targeting Android T and above will stop receiving intents from
      * external callers that do not match its declared intent filters.
@@ -1093,6 +1098,8 @@
             PlatformCompat compat, ComponentResolverApi resolver,
             List<ResolveInfo> resolveInfos, boolean isReceiver,
             Intent intent, String resolvedType, int filterCallingUid) {
+        if (DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.get()) return;
+
         final Printer logPrinter = DEBUG_INTENT_MATCHING
                 ? new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM)
                 : null;
diff --git a/services/core/java/com/android/server/pm/PackageMetrics.java b/services/core/java/com/android/server/pm/PackageMetrics.java
index 81f1a98..8252a9fa 100644
--- a/services/core/java/com/android/server/pm/PackageMetrics.java
+++ b/services/core/java/com/android/server/pm/PackageMetrics.java
@@ -19,6 +19,7 @@
 import static android.os.Process.INVALID_UID;
 
 import android.annotation.IntDef;
+import android.app.admin.SecurityLog;
 import android.content.pm.PackageManager;
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.util.Pair;
@@ -67,8 +68,8 @@
         mInstallRequest = installRequest;
     }
 
-    public void onInstallSucceed() {
-        // TODO(b/239722919): report to SecurityLog if on work profile or managed device
+    public void onInstallSucceed(int userId) {
+        reportInstallationToSecurityLog(userId);
         reportInstallationStats(true /* success */);
     }
 
@@ -77,8 +78,13 @@
     }
 
     private void reportInstallationStats(boolean success) {
-        UserManagerInternal userManagerInternal =
+        final UserManagerInternal userManagerInternal =
                 LocalServices.getService(UserManagerInternal.class);
+        if (userManagerInternal == null) {
+            // UserManagerService is not available. Skip metrics reporting.
+            return;
+        }
+
         final long installDurationMillis =
                 System.currentTimeMillis() - mInstallStartTimestampMillis;
         // write to stats
@@ -196,12 +202,17 @@
         }
     }
 
-    public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags,
-            UserManagerInternal userManagerInternal) {
+    public static void onUninstallSucceeded(PackageRemovedInfo info, int deleteFlags, int userId) {
         if (info.mIsUpdate) {
             // Not logging uninstalls caused by app updates
             return;
         }
+        final UserManagerInternal userManagerInternal =
+                LocalServices.getService(UserManagerInternal.class);
+        if (userManagerInternal == null) {
+            // UserManagerService is not available. Skip metrics reporting.
+            return;
+        }
         final int[] removedUsers = info.mRemovedUsers;
         final int[] removedUserTypes = userManagerInternal.getUserTypesForStatsd(removedUsers);
         final int[] originalUsers = info.mOrigUsers;
@@ -210,6 +221,9 @@
                 info.mUid, removedUsers, removedUserTypes, originalUsers, originalUserTypes,
                 deleteFlags, PackageManager.DELETE_SUCCEEDED, info.mIsRemovedPackageSystemUpdate,
                 !info.mRemovedForAllUsers);
+        final String packageName = info.mRemovedPackage;
+        final long versionCode = info.mRemovedPackageVersionCode;
+        reportUninstallationToSecurityLog(packageName, versionCode, userId);
     }
 
     public static void onVerificationFailed(VerifyingSession verifyingSession) {
@@ -242,4 +256,32 @@
                 verifyingSession.isStaged() /* is_staged */
         );
     }
+
+    private void reportInstallationToSecurityLog(int userId) {
+        if (!SecurityLog.isLoggingEnabled()) {
+            return;
+        }
+        final PackageSetting ps = mInstallRequest.getScannedPackageSetting();
+        if (ps == null) {
+            return;
+        }
+        final String packageName = ps.getPackageName();
+        final long versionCode = ps.getVersionCode();
+        if (!mInstallRequest.isInstallReplace()) {
+            SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_INSTALLED, packageName, versionCode,
+                    userId);
+        } else {
+            SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UPDATED, packageName, versionCode,
+                    userId);
+        }
+    }
+
+    private static void reportUninstallationToSecurityLog(String packageName, long versionCode,
+            int userId) {
+        if (!SecurityLog.isLoggingEnabled()) {
+            return;
+        }
+        SecurityLog.writeEvent(SecurityLog.TAG_PACKAGE_UNINSTALLED, packageName, versionCode,
+                userId);
+    }
 }
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index dd580a5..c762fd3 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -51,6 +51,7 @@
     boolean mRemovedForAllUsers;
     boolean mIsStaticSharedLib;
     boolean mIsExternal;
+    long mRemovedPackageVersionCode;
     // a two dimensional array mapping userId to the set of appIds that can receive notice
     // of package changes
     SparseArray<int[]> mBroadcastAllowList;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 6dcbb52..b18179e 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1380,7 +1380,11 @@
         return mSecondaryCpuAbi;
     }
 
-
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    @Override
+    public int getHiddenApiEnforcementPolicy() {
+        return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
+    }
 
     // Code below generated by codegen v1.0.23.
     //
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 8c58397..41985e3 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -275,6 +275,7 @@
             outInfo.populateUsers(deletedPs.queryInstalledUsers(
                     mUserManagerInternal.getUserIds(), true), deletedPs);
             outInfo.mIsExternal = deletedPs.isExternalStorage();
+            outInfo.mRemovedPackageVersionCode = deletedPs.getVersionCode();
         }
 
         removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index fada577..622c6ee 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -102,7 +103,8 @@
     }
 
     private static void filterNonExportedComponents(Intent intent, int filterCallingUid,
-            List<ResolveInfo> query, PlatformCompat platformCompat, Computer computer) {
+            List<ResolveInfo> query, PlatformCompat platformCompat, String resolvedType,
+            Computer computer) {
         if (query == null
                 || intent.getPackage() != null
                 || intent.getComponent() != null
@@ -113,21 +115,24 @@
         String callerPackage = caller == null ? "Not specified" : caller.getPackageName();
         for (int i = query.size() - 1; i >= 0; i--) {
             if (!query.get(i).getComponentInfo().exported) {
-                if (!platformCompat.isChangeEnabledByUid(
+                boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
                         ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
-                        filterCallingUid)) {
-                    Slog.w(TAG, "Non-exported component not filtered out "
-                            + "(will be filtered out once the app targets U+)- intent: "
-                            + intent.getAction() + ", component: "
-                            + query.get(i).getComponentInfo()
-                            .getComponentName().flattenToShortString()
-                            + ", starter: " + callerPackage);
+                        filterCallingUid);
+                String[] categories = intent.getCategories() == null ? new String[0]
+                        : intent.getCategories().toArray(String[]::new);
+                FrameworkStatsLog.write(FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED,
+                        FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH,
+                        filterCallingUid,
+                        query.get(i).getComponentInfo().getComponentName().flattenToShortString(),
+                        callerPackage,
+                        intent.getAction(),
+                        categories,
+                        resolvedType,
+                        intent.getScheme(),
+                        hasToBeExportedToMatch);
+                if (!hasToBeExportedToMatch) {
                     return;
                 }
-                Slog.w(TAG, "Non-exported component filtered out - intent: "
-                        + intent.getAction() + ", component: "
-                        + query.get(i).getComponentInfo().getComponentName().flattenToShortString()
-                        + ", starter: " + callerPackage);
                 query.remove(i);
             }
         }
@@ -173,7 +178,7 @@
                     resolveForStart, true /*allowDynamicSplits*/);
             if (exportedComponentsOnly) {
                 filterNonExportedComponents(intent, filterCallingUid, query,
-                        mPlatformCompat, computer);
+                        mPlatformCompat, resolvedType, computer);
             }
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index bc9f7b2..89b74f4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -369,6 +369,8 @@
 
     // Current settings file.
     private final File mSettingsFilename;
+    // Compressed current settings file.
+    private final File mCompressedSettingsFilename;
     // Previous settings file.
     // Removed when the current settings file successfully stored.
     private final File mPreviousSettingsFilename;
@@ -639,6 +641,7 @@
         mRuntimePermissionsPersistence = null;
         mPermissionDataProvider = null;
         mSettingsFilename = null;
+        mCompressedSettingsFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -710,6 +713,7 @@
                 |FileUtils.S_IROTH|FileUtils.S_IXOTH,
                 -1, -1);
         mSettingsFilename = new File(mSystemDir, "packages.xml");
+        mCompressedSettingsFilename = new File(mSystemDir, "packages.compressed");
         mPreviousSettingsFilename = new File(mSystemDir, "packages-backup.xml");
         mPackageListFilename = new File(mSystemDir, "packages.list");
         FileUtils.setPermissions(mPackageListFilename, 0640, SYSTEM_UID, PACKAGE_INFO_GID);
@@ -751,6 +755,7 @@
         mLock = null;
         mRuntimePermissionsPersistence = r.mRuntimePermissionsPersistence;
         mSettingsFilename = null;
+        mCompressedSettingsFilename = null;
         mPreviousSettingsFilename = null;
         mPackageListFilename = null;
         mStoppedPackagesFilename = null;
@@ -2465,7 +2470,7 @@
                 mReadMessages.append("Reading from backup stopped packages file\n");
                 PackageManagerService.reportSettingsProblem(Log.INFO,
                         "Need to read from backup stopped packages file");
-                if (mSettingsFilename.exists()) {
+                if (mStoppedPackagesFilename.exists()) {
                     // If both the backup and normal file exist, we
                     // ignore the normal one since it might have been
                     // corrupted.
@@ -2588,6 +2593,8 @@
                 Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
             }
         }
+        // Compressed settings are not valid anymore.
+        mCompressedSettingsFilename.delete();
 
         mPastSignatures.clear();
 
@@ -2677,10 +2684,30 @@
             mPreviousSettingsFilename.delete();
 
             FileUtils.setPermissions(mSettingsFilename.toString(),
-                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
-                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
+                    FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
                     -1, -1);
 
+            final FileInputStream fis = new FileInputStream(mSettingsFilename);
+            final AtomicFile compressed = new AtomicFile(mCompressedSettingsFilename);
+            final FileOutputStream fos = compressed.startWrite();
+
+            BackgroundThread.getHandler().post(() -> {
+                try {
+                    if (!nativeCompressLz4(fis.getFD().getInt$(), fos.getFD().getInt$())) {
+                        throw new IOException("Failed to compress");
+                    }
+                    compressed.finishWrite(fos);
+                    FileUtils.setPermissions(mCompressedSettingsFilename.toString(),
+                            FileUtils.S_IRUSR | FileUtils.S_IWUSR | FileUtils.S_IRGRP
+                                    | FileUtils.S_IWGRP, -1, -1);
+                } catch (IOException e) {
+                    Slog.e(PackageManagerService.TAG, "Failed to write compressed settings file: "
+                            + mCompressedSettingsFilename, e);
+                    compressed.delete();
+                }
+                IoUtils.closeQuietly(fis);
+            });
+
             writeKernelMappingLPr();
             writePackageListLPr();
             writeAllUsersPackageRestrictionsLPr(sync);
@@ -2703,6 +2730,8 @@
         //Debug.stopMethodTracing();
     }
 
+    private native boolean nativeCompressLz4(int inputFd, int outputFd);
+
     private void writeKernelRemoveUserLPr(int userId) {
         if (mKernelMappingFilename == null) return;
 
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 0362ddd..4fddc9c 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,9 +1470,15 @@
         }
 
         // Then make sure none of the activities have more than the max number of shortcuts.
+        int total = 0;
         for (int i = counts.size() - 1; i >= 0; i--) {
-            service.enforceMaxActivityShortcuts(counts.valueAt(i));
+            int count = counts.valueAt(i);
+            service.enforceMaxActivityShortcuts(count);
+            total += count;
         }
+
+        // Finally make sure that the app doesn't have more than the max number of shortcuts.
+        service.enforceMaxAppShortcuts(total);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 83720f1..12a33ee 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,6 +181,9 @@
     static final int DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY = 15;
 
     @VisibleForTesting
+    static final int DEFAULT_MAX_SHORTCUTS_PER_APP = 60;
+
+    @VisibleForTesting
     static final int DEFAULT_MAX_ICON_DIMENSION_DP = 96;
 
     @VisibleForTesting
@@ -257,6 +260,11 @@
         String KEY_MAX_SHORTCUTS = "max_shortcuts";
 
         /**
+         * Key name for the max dynamic shortcuts per app. (int)
+         */
+        String KEY_MAX_SHORTCUTS_PER_APP = "max_shortcuts_per_app";
+
+        /**
          * Key name for icon compression quality, 0-100.
          */
         String KEY_ICON_QUALITY = "icon_quality";
@@ -329,9 +337,14 @@
             new SparseArray<>();
 
     /**
+     * Max number of dynamic + manifest shortcuts that each activity can have at a time.
+     */
+    private int mMaxShortcutsPerActivity;
+
+    /**
      * Max number of dynamic + manifest shortcuts that each application can have at a time.
      */
-    private int mMaxShortcuts;
+    private int mMaxShortcutsPerApp;
 
     /**
      * Max number of updating API calls that each application can make during the interval.
@@ -804,9 +817,12 @@
         mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
 
-        mMaxShortcuts = Math.max(0, (int) parser.getLong(
+        mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_SHORTCUTS, DEFAULT_MAX_SHORTCUTS_PER_ACTIVITY));
 
+        mMaxShortcutsPerApp = Math.max(0, (int) parser.getLong(
+                ConfigConstants.KEY_MAX_SHORTCUTS_PER_APP, DEFAULT_MAX_SHORTCUTS_PER_APP));
+
         final int iconDimensionDp = Math.max(1, injectIsLowRamDevice()
                 ? (int) parser.getLong(
                 ConfigConstants.KEY_MAX_ICON_DIMENSION_DP_LOWRAM,
@@ -1746,16 +1762,33 @@
      *                                  {@link #getMaxActivityShortcuts()}.
      */
     void enforceMaxActivityShortcuts(int numShortcuts) {
-        if (numShortcuts > mMaxShortcuts) {
+        if (numShortcuts > mMaxShortcutsPerActivity) {
             throw new IllegalArgumentException("Max number of dynamic shortcuts exceeded");
         }
     }
 
     /**
+     * @throws IllegalArgumentException if {@code numShortcuts} is bigger than
+     *                                  {@link #getMaxAppShortcuts()}.
+     */
+    void enforceMaxAppShortcuts(int numShortcuts) {
+        if (numShortcuts > mMaxShortcutsPerApp) {
+            throw new IllegalArgumentException("Max number of dynamic shortcuts per app exceeded");
+        }
+    }
+
+    /**
      * Return the max number of dynamic + manifest shortcuts for each launcher icon.
      */
     int getMaxActivityShortcuts() {
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
+    }
+
+    /**
+     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
+     */
+    int getMaxAppShortcuts() {
+        return mMaxShortcutsPerApp;
     }
 
     /**
@@ -2188,6 +2221,8 @@
             ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
             fillInDefaultActivity(Arrays.asList(shortcut));
 
+            enforceMaxAppShortcuts(ps.getShortcutCount());
+
             if (!shortcut.hasRank()) {
                 shortcut.setRank(0);
             }
@@ -2575,7 +2610,7 @@
             throws RemoteException {
         verifyCaller(packageName, userId);
 
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
     }
 
     @Override
@@ -4724,7 +4759,7 @@
                 pw.print("    maxUpdatesPerInterval: ");
                 pw.println(mMaxUpdatesPerInterval);
                 pw.print("    maxShortcutsPerActivity: ");
-                pw.println(mMaxShortcuts);
+                pw.println(mMaxShortcutsPerActivity);
                 pw.println();
 
                 mStatLogger.dump(pw, "  ");
@@ -5211,7 +5246,7 @@
 
     @VisibleForTesting
     int getMaxShortcutsForTest() {
-        return mMaxShortcuts;
+        return mMaxShortcutsPerActivity;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/UserManagerInternal.java b/services/core/java/com/android/server/pm/UserManagerInternal.java
index 0f920c6..2ae8b52 100644
--- a/services/core/java/com/android/server/pm/UserManagerInternal.java
+++ b/services/core/java/com/android/server/pm/UserManagerInternal.java
@@ -59,6 +59,18 @@
     })
     public @interface UserAssignmentResult {}
 
+    public static final int USER_START_MODE_FOREGROUND = 1;
+    public static final int USER_START_MODE_BACKGROUND = 2;
+    public static final int USER_START_MODE_BACKGROUND_VISIBLE = 3;
+
+    private static final String PREFIX_USER_START_MODE = "USER_START_MODE_";
+    @IntDef(flag = false, prefix = {PREFIX_USER_START_MODE}, value = {
+            USER_START_MODE_FOREGROUND,
+            USER_START_MODE_BACKGROUND,
+            USER_START_MODE_BACKGROUND_VISIBLE
+    })
+    public @interface UserStartMode {}
+
     public interface UserRestrictionsListener {
         /**
          * Called when a user restriction changes.
@@ -141,23 +153,39 @@
     /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
      * whether the device is managed by device owner.
+     *
+     * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
      */
+    @Deprecated
+    // TODO(b/258213147): Remove
     public abstract void setDeviceManaged(boolean isManaged);
 
     /**
      * Returns whether the device is managed by device owner.
+     *
+     * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
      */
+    @Deprecated
+    // TODO(b/258213147): Remove
     public abstract boolean isDeviceManaged();
 
     /**
      * Called by {@link com.android.server.devicepolicy.DevicePolicyManagerService} to update
      * whether the user is managed by profile owner.
+     *
+     * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
      */
+    // TODO(b/258213147): Remove
+    @Deprecated
     public abstract void setUserManaged(int userId, boolean isManaged);
 
     /**
-     * whether a profile owner manages this user.
+     * Whether a profile owner manages this user.
+     *
+     * @deprecated Use methods in {@link android.app.admin.DevicePolicyManagerInternal}.
      */
+    // TODO(b/258213147): Remove
+    @Deprecated
     public abstract boolean isUserManaged(int userId);
 
     /**
@@ -367,8 +395,7 @@
      * pass a valid display id.
      */
     public abstract @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
-            @UserIdInt int profileGroupId,
-            boolean foreground, int displayId);
+            @UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId);
 
     /**
      * Unassigns a user from its current display when it's stopping.
@@ -429,6 +456,13 @@
                 result);
     }
 
+    /**
+     * Gets the user-friendly representation of a user start {@code mode}.
+     */
+    public static String userStartModeToString(@UserStartMode int mode) {
+        return DebugUtils.constantToString(UserManagerInternal.class, PREFIX_USER_START_MODE, mode);
+    }
+
     /** Adds a {@link UserVisibilityListener}. */
     public abstract void addUserVisibilityListener(UserVisibilityListener listener);
 
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 3234e87..0a650c9 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -93,6 +93,7 @@
 import android.provider.Settings;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.stats.devicepolicy.DevicePolicyEnums;
+import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -126,8 +127,10 @@
 import com.android.server.LockGuard;
 import com.android.server.SystemService;
 import com.android.server.am.UserState;
+import com.android.server.pm.UserManagerInternal.UserAssignmentResult;
 import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
+import com.android.server.pm.UserManagerInternal.UserStartMode;
 import com.android.server.pm.UserManagerInternal.UserVisibilityListener;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 import com.android.server.utils.Slogf;
@@ -1970,6 +1973,63 @@
         }
     }
 
+    /**
+     * Returns whether switching users is currently allowed for the provided user.
+     * <p>
+     * Switching users is not allowed in the following cases:
+     * <li>the user is in a phone call</li>
+     * <li>{@link UserManager#DISALLOW_USER_SWITCH} is set</li>
+     * <li>system user hasn't been unlocked yet</li>
+     *
+     * @return A {@link UserManager.UserSwitchabilityResult} flag indicating if the user is
+     * switchable.
+     */
+    public @UserManager.UserSwitchabilityResult int getUserSwitchability(int userId) {
+        checkManageOrInteractPermissionIfCallerInOtherProfileGroup(userId, "getUserSwitchability");
+
+        final TimingsTraceAndSlog t = new TimingsTraceAndSlog();
+        t.traceBegin("getUserSwitchability-" + userId);
+
+        int flags = UserManager.SWITCHABILITY_STATUS_OK;
+
+        t.traceBegin("TM.isInCall");
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            final TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+            if (telecomManager != null && telecomManager.isInCall()) {
+                flags |= UserManager.SWITCHABILITY_STATUS_USER_IN_CALL;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+        t.traceEnd();
+
+        t.traceBegin("hasUserRestriction-DISALLOW_USER_SWITCH");
+        if (mLocalService.hasUserRestriction(DISALLOW_USER_SWITCH, userId)) {
+            flags |= UserManager.SWITCHABILITY_STATUS_USER_SWITCH_DISALLOWED;
+        }
+        t.traceEnd();
+
+        // System User is always unlocked in Headless System User Mode, so ignore this flag
+        if (!isHeadlessSystemUserMode()) {
+            t.traceBegin("getInt-ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED");
+            final boolean allowUserSwitchingWhenSystemUserLocked = Settings.Global.getInt(
+                    mContext.getContentResolver(),
+                    Settings.Global.ALLOW_USER_SWITCHING_WHEN_SYSTEM_USER_LOCKED, 0) != 0;
+            t.traceEnd();
+            t.traceBegin("isUserUnlocked-USER_SYSTEM");
+            final boolean systemUserUnlocked = mLocalService.isUserUnlocked(UserHandle.USER_SYSTEM);
+            t.traceEnd();
+
+            if (!allowUserSwitchingWhenSystemUserLocked && !systemUserUnlocked) {
+                flags |= UserManager.SWITCHABILITY_STATUS_SYSTEM_USER_LOCKED;
+            }
+        }
+        t.traceEnd();
+
+        return flags;
+    }
+
     @VisibleForTesting
     boolean isUserSwitcherEnabled(@UserIdInt int mUserId) {
         boolean multiUserSettingOn = Settings.Global.getInt(mContext.getContentResolver(),
@@ -6602,6 +6662,7 @@
             }
         }
 
+        // TODO(b/258213147): Remove
         @Override
         public void setDeviceManaged(boolean isManaged) {
             synchronized (mUsersLock) {
@@ -6609,6 +6670,7 @@
             }
         }
 
+        // TODO(b/258213147): Remove
         @Override
         public boolean isDeviceManaged() {
             synchronized (mUsersLock) {
@@ -6616,6 +6678,7 @@
             }
         }
 
+        // TODO(b/258213147): Remove
         @Override
         public void setUserManaged(@UserIdInt int userId, boolean isManaged) {
             synchronized (mUsersLock) {
@@ -6623,6 +6686,7 @@
             }
         }
 
+        // TODO(b/258213147): Remove
         @Override
         public boolean isUserManaged(@UserIdInt int userId) {
             synchronized (mUsersLock) {
@@ -6919,8 +6983,11 @@
         }
 
         @Override
-        public int assignUserToDisplayOnStart(@UserIdInt int userId, @UserIdInt int profileGroupId,
-                boolean foreground, int displayId) {
+        @UserAssignmentResult
+        public int assignUserToDisplayOnStart(@UserIdInt int userId,
+                @UserIdInt int profileGroupId, @UserStartMode int userStartMode, int displayId) {
+            // TODO(245939659): change UserVisibilityMediator to take @UserStartMode
+            boolean foreground = userStartMode == UserManagerInternal.USER_START_MODE_FOREGROUND;
             return mUserVisibilityMediator.assignUserToDisplayOnStart(userId, profileGroupId,
                     foreground, displayId);
         }
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 9b9ca10..92e8f55 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -139,7 +139,7 @@
     }
 
     /**
-     * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, boolean, int)}.
+     * See {@link UserManagerInternal#assignUserToDisplayOnStart(int, int, int, int)}.
      */
     public @UserAssignmentResult int assignUserToDisplayOnStart(@UserIdInt int userId,
             @UserIdInt int unResolvedProfileGroupId, boolean foreground, int displayId) {
diff --git a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
index 9f21097..f0bf1ea8 100644
--- a/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtStatsLogUtils.java
@@ -299,7 +299,8 @@
                     apkType,
                     ISA_MAP.getOrDefault(isa,
                             ArtStatsLog.ART_DATUM_REPORTED__ISA__ART_ISA_UNKNOWN),
-                    ArtStatsLog.ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN);
+                    ArtStatsLog.ART_DATUM_REPORTED__GC__ART_GC_COLLECTOR_TYPE_UNKNOWN,
+                    ArtStatsLog.ART_DATUM_REPORTED__UFFD_SUPPORT__ART_UFFD_SUPPORT_UNKNOWN);
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
index 4ff0d59..f8e1547 100644
--- a/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
+++ b/services/core/java/com/android/server/pm/local/PackageManagerLocalImpl.java
@@ -22,6 +22,7 @@
 import android.annotation.UserIdInt;
 import android.os.Binder;
 import android.os.UserHandle;
+import android.util.ArrayMap;
 
 import com.android.server.pm.Computer;
 import com.android.server.pm.PackageManagerLocal;
@@ -30,11 +31,9 @@
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.function.Consumer;
 
 /** @hide */
 public class PackageManagerLocalImpl implements PackageManagerLocal {
@@ -143,7 +142,7 @@
         private final int mUserId;
 
         @Nullable
-        private ArrayList<PackageState> mFilteredPackageStates;
+        private Map<String, PackageState> mFilteredPackageStates;
 
         @Nullable
         private final UnfilteredSnapshotImpl mParentSnapshot;
@@ -179,26 +178,24 @@
             return mSnapshot.getPackageStateFiltered(packageName, mCallingUid, mUserId);
         }
 
+        @NonNull
         @Override
-        public void forAllPackageStates(@NonNull Consumer<PackageState> consumer) {
+        public Map<String, PackageState> getPackageStates() {
             checkClosed();
 
             if (mFilteredPackageStates == null) {
                 var packageStates = mSnapshot.getPackageStates();
-                var filteredPackageStates = new ArrayList<PackageState>();
+                var filteredPackageStates = new ArrayMap<String, PackageState>();
                 for (int index = 0, size = packageStates.size(); index < size; index++) {
                     var packageState = packageStates.valueAt(index);
                     if (!mSnapshot.shouldFilterApplication(packageState, mCallingUid, mUserId)) {
-                        filteredPackageStates.add(packageState);
+                        filteredPackageStates.put(packageStates.keyAt(index), packageState);
                     }
                 }
-                mFilteredPackageStates = filteredPackageStates;
+                mFilteredPackageStates = Collections.unmodifiableMap(filteredPackageStates);
             }
 
-            for (int index = 0, size = mFilteredPackageStates.size(); index < size; index++) {
-                var packageState = mFilteredPackageStates.get(index);
-                consumer.accept(packageState);
-            }
+            return mFilteredPackageStates;
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index c76b129..82b5fa2 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -234,10 +234,12 @@
                 || !pkg.getLibraryNames().isEmpty();
     }
 
-    public static int getHiddenApiEnforcementPolicy(AndroidPackage pkg,
+    public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
             @NonNull PackageStateInternal pkgSetting) {
         boolean isAllowedToUseHiddenApis;
-        if (pkg.isSignedWithPlatformKey()) {
+        if (pkg == null) {
+            isAllowedToUseHiddenApis = false;
+        } else if (pkg.isSignedWithPlatformKey()) {
             isAllowedToUseHiddenApis = true;
         } else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
             isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
diff --git a/services/core/java/com/android/server/pm/permission/Permission.java b/services/core/java/com/android/server/pm/permission/Permission.java
index 69e7bf1..165c52d 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -322,6 +322,10 @@
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_COMPANION) != 0;
     }
 
+    public boolean isModule() {
+        return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_MODULE) != 0;
+    }
+
     public boolean isRetailDemo() {
         return (mPermissionInfo.protectionLevel & PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO) != 0;
     }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index e56edeb..2a2bcab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -238,6 +238,8 @@
         NOTIFICATION_PERMISSIONS.add(Manifest.permission.POST_NOTIFICATIONS);
     }
 
+    @NonNull private final ApexManager mApexManager;
+
     /** Set of source package names for Privileged Permission Allowlist */
     private final ArraySet<String> mPrivilegedPermissionAllowlistSourcePackageNames =
             new ArraySet<>();
@@ -421,6 +423,7 @@
         mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
         mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
         mIsLeanback = availableFeatures.containsKey(PackageManager.FEATURE_LEANBACK);
+        mApexManager = ApexManager.getInstance();
 
         mPrivilegedPermissionAllowlistSourcePackageNames.add(PLATFORM_PACKAGE_NAME);
         // PackageManager.hasSystemFeature() is not used here because PackageManagerService
@@ -3309,9 +3312,8 @@
             return true;
         }
         final String permissionName = permission.getName();
-        final ApexManager apexManager = ApexManager.getInstance();
         final String containingApexPackageName =
-                apexManager.getActiveApexPackageNameContainingPackage(packageName);
+                mApexManager.getActiveApexPackageNameContainingPackage(packageName);
         if (isInSystemConfigPrivAppPermissions(pkg, permissionName,
                 containingApexPackageName)) {
             return true;
@@ -3365,8 +3367,7 @@
         } else if (pkg.isSystemExt()) {
             permissions = systemConfig.getSystemExtPrivAppPermissions(pkg.getPackageName());
         } else if (containingApexPackageName != null) {
-            final ApexManager apexManager = ApexManager.getInstance();
-            final String apexName = apexManager.getApexModuleNameForPackageName(
+            final String apexName = mApexManager.getApexModuleNameForPackageName(
                     containingApexPackageName);
             final Set<String> privAppPermissions = systemConfig.getPrivAppPermissions(
                     pkg.getPackageName());
@@ -3582,6 +3583,11 @@
             // Special permission for the recents app.
             allowed = true;
         }
+        if (!allowed && bp.isModule() && mApexManager.getActiveApexPackageNameContainingPackage(
+                pkg.getPackageName()) != null) {
+            // Special permission granted for APKs inside APEX modules.
+            allowed = true;
+        }
         return allowed;
     }
 
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index e8d0640..67b7647 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -112,6 +112,19 @@
     int getAppId();
 
     /**
+     * Retrieves effective hidden API policy for this app. The state can be dependent on
+     * {@link #getAndroidPackage()} availability and whether the app is a system app.
+     *
+     * Note that during process start, this policy may be mutated by device specific process
+     * configuration, so this value isn't truly final.
+     *
+     * @return The (mostly) final {@link ApplicationInfo.HiddenApiEnforcementPolicy} that should be
+     * applied to this package.
+     */
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    int getHiddenApiEnforcementPolicy();
+
+    /**
      * @see PackageInfo#packageName
      * @see AndroidPackage#getPackageName()
      */
@@ -139,6 +152,18 @@
     String getSeInfo();
 
     /**
+     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
+     */
+    @NonNull
+    PackageUserState getStateForUser(@NonNull UserHandle user);
+
+    /**
+     * @see R.styleable#AndroidManifestUsesLibrary
+     */
+    @NonNull
+    List<SharedLibrary> getUsesLibraries();
+
+    /**
      * @see AndroidPackage#isPrivileged()
      */
     boolean isPrivileged();
@@ -154,18 +179,6 @@
      */
     boolean isUpdatedSystemApp();
 
-    /**
-     * @return State for a user or {@link PackageUserState#DEFAULT} if the state doesn't exist.
-     */
-    @NonNull
-    PackageUserState getStateForUser(@NonNull UserHandle user);
-
-    /**
-     * @see R.styleable#AndroidManifestUsesLibrary
-     */
-    @NonNull
-    List<SharedLibrary> getUsesLibraries();
-
     // Methods below this comment are not yet exposed as API
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
index e552a34..43d019a 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.SigningInfo;
@@ -118,6 +119,8 @@
     private final int mCategoryOverride;
     @Nullable
     private final String mCpuAbiOverride;
+    @ApplicationInfo.HiddenApiEnforcementPolicy
+    private final int mHiddenApiEnforcementPolicy;
     private final long mLastModifiedTime;
     private final long mLastUpdateTime;
     private final long mLongVersionCode;
@@ -170,6 +173,7 @@
         mAppId = pkgState.getAppId();
         mCategoryOverride = pkgState.getCategoryOverride();
         mCpuAbiOverride = pkgState.getCpuAbiOverride();
+        mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
         mLastModifiedTime = pkgState.getLastModifiedTime();
         mLastUpdateTime = pkgState.getLastUpdateTime();
         mLongVersionCode = pkgState.getVersionCode();
@@ -545,7 +549,7 @@
         }
 
         @DataClass.Generated(
-                time = 1665778832625L,
+                time = 1666719622708L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
                 inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final  long mFirstInstallTime\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
@@ -609,6 +613,11 @@
     }
 
     @DataClass.Generated.Member
+    public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
+        return mHiddenApiEnforcementPolicy;
+    }
+
+    @DataClass.Generated.Member
     public long getLastModifiedTime() {
         return mLastModifiedTime;
     }
@@ -705,10 +714,10 @@
     }
 
     @DataClass.Generated(
-            time = 1665778832668L,
+            time = 1666719622749L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
+            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override int getSharedUserAppId()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
index b2080b2..90a0c7c 100644
--- a/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
+++ b/services/core/java/com/android/server/pm/snapshot/PackageDataSnapshot.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm.snapshot;
 
-import android.annotation.SystemApi;
 import android.content.pm.PackageManagerInternal;
 
 import com.android.server.pm.Computer;
@@ -32,6 +31,5 @@
  *
  * @hide
  */
-@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
 public interface PackageDataSnapshot {
 }
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index d2e0502..91bb677 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -378,13 +378,14 @@
                 try {
                     conditionSatisfied = mStateConditions.get(state).getAsBoolean();
                 } catch (IllegalStateException e) {
-                    // Failed to compute the current state based on current available data. Return
+                    // Failed to compute the current state based on current available data. Continue
                     // with the expectation that notifyDeviceStateChangedIfNeeded() will be called
-                    // when a callback with the missing data is triggered.
+                    // when a callback with the missing data is triggered. May trigger another state
+                    // change if another state is satisfied currently.
                     if (DEBUG) {
                         Slog.d(TAG, "Unable to check current state", e);
                     }
-                    return;
+                    continue;
                 }
 
                 if (conditionSatisfied) {
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9d58a73..399b94c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1714,6 +1714,11 @@
         }
     }
 
+    private void showSystemSettings() {
+        startActivityAsUser(new Intent(android.provider.Settings.ACTION_SETTINGS),
+                UserHandle.CURRENT_OR_SELF);
+    }
+
     private void showPictureInPictureMenu(KeyEvent event) {
         if (DEBUG_INPUT) Log.d(TAG, "showPictureInPictureMenu event=" + event);
         mHandler.removeMessages(MSG_SHOW_PICTURE_IN_PICTURE_MENU);
@@ -2868,16 +2873,7 @@
 
         switch(keyCode) {
             case KeyEvent.KEYCODE_HOME:
-                // First we always handle the home key here, so applications
-                // can never break it, although if keyguard is on, we do let
-                // it handle it, because that gives us the correct 5 second
-                // timeout.
-                DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId);
-                if (handler == null) {
-                    handler = new DisplayHomeButtonHandler(displayId);
-                    mDisplayHomeButtonHandlers.put(displayId, handler);
-                }
-                return handler.handleHomeButton(focusedToken, event);
+                return handleHomeShortcuts(displayId, focusedToken, event);
             case KeyEvent.KEYCODE_MENU:
                 // Hijack modified menu keys for debugging features
                 final int chordBug = KeyEvent.META_SHIFT_ON;
@@ -2900,6 +2896,25 @@
                     }
                 }
                 return key_consumed;
+            case KeyEvent.KEYCODE_A:
+                if (down && event.isMetaPressed()) {
+                    launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
+                            event.getDeviceId(),
+                            event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_H:
+                if (down && event.isMetaPressed()) {
+                    return handleHomeShortcuts(displayId, focusedToken, event);
+                }
+                break;
+            case KeyEvent.KEYCODE_I:
+                if (down && event.isMetaPressed()) {
+                    showSystemSettings();
+                    return key_consumed;
+                }
+                break;
             case KeyEvent.KEYCODE_N:
                 if (down && event.isMetaPressed()) {
                     IStatusBarService service = getStatusBarService();
@@ -2920,6 +2935,40 @@
                     return key_consumed;
                 }
                 break;
+            case KeyEvent.KEYCODE_DPAD_UP:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+                    if (statusbar != null) {
+                        statusbar.goToFullscreenFromSplit();
+                    }
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(true /* leftOrTop */);
+                    return key_consumed;
+                } else if (!down && event.isMetaPressed()) {
+                    boolean backKeyHandled = backKeyPress();
+                    if (backKeyHandled) {
+                        return key_consumed;
+                    }
+                }
+                break;
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                if (down && event.isMetaPressed() && event.isCtrlPressed() && repeatCount == 0) {
+                    enterStageSplitFromRunningApp(false /* leftOrTop */);
+                    return key_consumed;
+                }
+                break;
+            case KeyEvent.KEYCODE_GRAVE:
+                if (!down && event.isMetaPressed()) {
+                    boolean backKeyHandled = backKeyPress();
+                    if (backKeyHandled) {
+                        return key_consumed;
+                    }
+                }
+                break;
             case KeyEvent.KEYCODE_SLASH:
                 if (down && repeatCount == 0 && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
@@ -3019,12 +3068,13 @@
                 }
                 break;
             case KeyEvent.KEYCODE_TAB:
-                if (event.isMetaPressed()) {
-                    // Pass through keyboard navigation keys.
-                    return key_not_consumed;
-                }
-                // Display task switcher for ALT-TAB.
-                if (down && repeatCount == 0) {
+                if (down && event.isMetaPressed()) {
+                    if (!keyguardOn && isUserSetupComplete()) {
+                        showRecentApps(false);
+                        return key_consumed;
+                    }
+                } else if (down && repeatCount == 0) {
+                    // Display task switcher for ALT-TAB.
                     if (mRecentAppsHeldModifiers == 0 && !keyguardOn && isUserSetupComplete()) {
                         final int shiftlessModifiers =
                                 event.getModifiers() & ~KeyEvent.META_SHIFT_MASK;
@@ -3079,9 +3129,7 @@
                         mPendingCapsLockToggle = false;
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
-                            launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
-                                    event.getDeviceId(),
-                                    event.getEventTime(), AssistUtils.INVOCATION_TYPE_UNKNOWN);
+                            // TODO: launch all apps here.
                         }
                         mPendingMetaAction = false;
                     }
@@ -3136,6 +3184,19 @@
         return key_not_consumed;
     }
 
+    private int handleHomeShortcuts(int displayId, IBinder focusedToken, KeyEvent event) {
+        // First we always handle the home key here, so applications
+        // can never break it, although if keyguard is on, we do let
+        // it handle it, because that gives us the correct 5 second
+        // timeout.
+        DisplayHomeButtonHandler handler = mDisplayHomeButtonHandlers.get(displayId);
+        if (handler == null) {
+            handler = new DisplayHomeButtonHandler(displayId);
+            mDisplayHomeButtonHandlers.put(displayId, handler);
+        }
+        return handler.handleHomeButton(focusedToken, event);
+    }
+
     private void toggleMicrophoneMuteFromKey() {
         if (mSensorPrivacyManager.supportsSensorToggle(
                 SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE,
@@ -3574,6 +3635,13 @@
         }
     }
 
+    private void enterStageSplitFromRunningApp(boolean leftOrTop) {
+        StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
+        if (statusbar != null) {
+            statusbar.enterStageSplitFromRunningApp(leftOrTop);
+        }
+    }
+
     void launchHomeFromHotKey(int displayId) {
         launchHomeFromHotKey(displayId, true /* awakenFromDreams */, true /*respectKeyguard*/);
     }
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index f378588..6b2c6e3 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -19,16 +19,18 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.hardware.thermal.IThermal;
+import android.hardware.thermal.IThermalChangedCallback;
+import android.hardware.thermal.TemperatureThreshold;
+import android.hardware.thermal.ThrottlingSeverity;
 import android.hardware.thermal.V1_0.ThermalStatus;
 import android.hardware.thermal.V1_0.ThermalStatusCode;
 import android.hardware.thermal.V1_1.IThermalCallback;
-import android.hardware.thermal.V2_0.IThermalChangedCallback;
-import android.hardware.thermal.V2_0.TemperatureThreshold;
-import android.hardware.thermal.V2_0.ThrottlingSeverity;
 import android.os.Binder;
 import android.os.CoolingDevice;
 import android.os.Handler;
 import android.os.HwBinder;
+import android.os.IBinder;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
 import android.os.IThermalStatusListener;
@@ -37,6 +39,7 @@
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
 import android.os.SystemClock;
@@ -56,12 +59,14 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
 
 /**
  * This is a system service that listens to HAL thermal events and dispatch those to listeners.
@@ -98,7 +103,7 @@
     @GuardedBy("mLock")
     private int mStatus;
 
-    /** If override status takes effect*/
+    /** If override status takes effect */
     @GuardedBy("mLock")
     private boolean mIsStatusOverride;
 
@@ -144,6 +149,10 @@
             // Connect to HAL and post to listeners.
             boolean halConnected = (mHalWrapper != null);
             if (!halConnected) {
+                mHalWrapper = new ThermalHalAidlWrapper();
+                halConnected = mHalWrapper.connectToHal();
+            }
+            if (!halConnected) {
                 mHalWrapper = new ThermalHal20Wrapper();
                 halConnected = mHalWrapper.connectToHal();
             }
@@ -684,6 +693,162 @@
         }
     }
 
+    @VisibleForTesting
+    static class ThermalHalAidlWrapper extends ThermalHalWrapper implements IBinder.DeathRecipient {
+        /* Proxy object for the Thermal HAL AIDL service. */
+        private IThermal mInstance = null;
+
+        /** Callback for Thermal HAL AIDL. */
+        private final IThermalChangedCallback mThermalChangedCallback =
+                new IThermalChangedCallback.Stub() {
+                    @Override public void notifyThrottling(
+                            android.hardware.thermal.Temperature temperature)
+                            throws RemoteException {
+                        Temperature svcTemperature = new Temperature(temperature.value,
+                                temperature.type, temperature.name, temperature.throttlingStatus);
+                        final long token = Binder.clearCallingIdentity();
+                        try {
+                            mCallback.onValues(svcTemperature);
+                        } finally {
+                            Binder.restoreCallingIdentity(token);
+                        }
+                    }
+
+            @Override public int getInterfaceVersion() throws RemoteException {
+                return this.VERSION;
+            }
+
+            @Override public String getInterfaceHash() throws RemoteException {
+                return this.HASH;
+            }
+        };
+
+        @Override
+        protected List<Temperature> getCurrentTemperatures(boolean shouldFilter,
+                int type) {
+            synchronized (mHalLock) {
+                final List<Temperature> ret = new ArrayList<>();
+                if (mInstance == null) {
+                    return ret;
+                }
+                try {
+                    final android.hardware.thermal.Temperature[] halRet =
+                            shouldFilter ? mInstance.getTemperaturesWithType(type)
+                                    : mInstance.getTemperatures();
+                    for (android.hardware.thermal.Temperature t : halRet) {
+                        if (!Temperature.isValidStatus(t.throttlingStatus)) {
+                            Slog.e(TAG, "Invalid temperature status " + t.throttlingStatus
+                                    + " received from AIDL HAL");
+                            t.throttlingStatus = Temperature.THROTTLING_NONE;
+                        }
+                        if (shouldFilter && t.type != type) {
+                            continue;
+                        }
+                        ret.add(new Temperature(t.value, t.type, t.name, t.throttlingStatus));
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting", e);
+                    connectToHal();
+                }
+                return ret;
+            }
+        }
+
+        @Override
+        protected List<CoolingDevice> getCurrentCoolingDevices(boolean shouldFilter,
+                int type) {
+            synchronized (mHalLock) {
+                final List<CoolingDevice> ret = new ArrayList<>();
+                if (mInstance == null) {
+                    return ret;
+                }
+                try {
+                    final android.hardware.thermal.CoolingDevice[] halRet = shouldFilter
+                            ? mInstance.getCoolingDevicesWithType(type)
+                            : mInstance.getCoolingDevices();
+                    for (android.hardware.thermal.CoolingDevice t : halRet) {
+                        if (!CoolingDevice.isValidType(t.type)) {
+                            Slog.e(TAG, "Invalid cooling device type " + t.type + " from AIDL HAL");
+                            continue;
+                        }
+                        if (shouldFilter && t.type != type) {
+                            continue;
+                        }
+                        ret.add(new CoolingDevice(t.value, t.type, t.name));
+                    }
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting", e);
+                    connectToHal();
+                }
+                return ret;
+            }
+        }
+
+        @Override
+        @NonNull protected List<TemperatureThreshold> getTemperatureThresholds(
+                boolean shouldFilter, int type) {
+            synchronized (mHalLock) {
+                final List<TemperatureThreshold> ret = new ArrayList<>();
+                if (mInstance == null) {
+                    return ret;
+                }
+                try {
+                    final TemperatureThreshold[] halRet =
+                            shouldFilter ? mInstance.getTemperatureThresholdsWithType(type)
+                                    : mInstance.getTemperatureThresholds();
+
+                    return Arrays.stream(halRet).filter(t -> t.type == type).collect(
+                            Collectors.toList());
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
+                    connectToHal();
+                }
+                return ret;
+            }
+        }
+
+        @Override
+        protected boolean connectToHal() {
+            synchronized (mHalLock) {
+                IBinder binder = Binder.allowBlocking(ServiceManager.waitForDeclaredService(
+                        IThermal.DESCRIPTOR + "/default"));
+                initProxyAndRegisterCallback(binder);
+            }
+            return mInstance != null;
+        }
+
+        @VisibleForTesting
+        void initProxyAndRegisterCallback(IBinder binder) {
+            synchronized (mHalLock) {
+                if (binder != null) {
+                    mInstance = IThermal.Stub.asInterface(binder);
+                    try {
+                        binder.linkToDeath(this, 0);
+                        mInstance.registerThermalChangedCallback(mThermalChangedCallback);
+                    } catch (RemoteException e) {
+                        Slog.e(TAG, "Unable to connect IThermal AIDL instance", e);
+                        mInstance = null;
+                    }
+                }
+            }
+        }
+
+        @Override
+        protected void dump(PrintWriter pw, String prefix) {
+            synchronized (mHalLock) {
+                pw.print(prefix);
+                pw.println(
+                        "ThermalHAL AIDL " + IThermal.VERSION + "  connected: " + (mInstance != null
+                                ? "yes" : "no"));
+            }
+        }
+
+        @Override
+        public synchronized void binderDied() {
+            Slog.w(TAG, "IThermal HAL instance died");
+            mInstance = null;
+        }
+    }
 
     static class ThermalHal10Wrapper extends ThermalHalWrapper {
         /** Proxy object for the Thermal HAL 1.0 service. */
@@ -814,8 +979,8 @@
                             android.hardware.thermal.V1_0.Temperature temperature) {
                         Temperature thermalSvcTemp = new Temperature(
                                 temperature.currentValue, temperature.type, temperature.name,
-                                isThrottling ? ThrottlingSeverity.SEVERE
-                                        : ThrottlingSeverity.NONE);
+                                isThrottling ? Temperature.THROTTLING_SEVERE
+                                        : Temperature.THROTTLING_NONE);
                         final long token = Binder.clearCallingIdentity();
                         try {
                             mCallback.onValues(thermalSvcTemp);
@@ -941,8 +1106,9 @@
         private android.hardware.thermal.V2_0.IThermal mThermalHal20 = null;
 
         /** HWbinder callback for Thermal HAL 2.0. */
-        private final IThermalChangedCallback.Stub mThermalCallback20 =
-                new IThermalChangedCallback.Stub() {
+        private final android.hardware.thermal.V2_0.IThermalChangedCallback.Stub
+                mThermalCallback20 =
+                new android.hardware.thermal.V2_0.IThermalChangedCallback.Stub() {
                     @Override
                     public void notifyThrottling(
                             android.hardware.thermal.V2_0.Temperature temperature) {
@@ -976,7 +1142,7 @@
                                                 temperature.throttlingStatus)) {
                                             Slog.e(TAG, "Invalid status data from HAL");
                                             temperature.throttlingStatus =
-                                                Temperature.THROTTLING_NONE;
+                                                    Temperature.THROTTLING_NONE;
                                         }
                                         ret.add(new Temperature(
                                                 temperature.value, temperature.type,
@@ -1043,7 +1209,9 @@
                     mThermalHal20.getTemperatureThresholds(shouldFilter, type,
                             (status, thresholds) -> {
                                 if (ThermalStatusCode.SUCCESS == status.code) {
-                                    ret.addAll(thresholds);
+                                    ret.addAll(thresholds.stream().map(
+                                            this::convertToAidlTemperatureThreshold).collect(
+                                            Collectors.toList()));
                                 } else {
                                     Slog.e(TAG,
                                             "Couldn't get temperature thresholds because of HAL "
@@ -1057,6 +1225,16 @@
             }
         }
 
+        private TemperatureThreshold convertToAidlTemperatureThreshold(
+                android.hardware.thermal.V2_0.TemperatureThreshold threshold) {
+            final TemperatureThreshold ret = new TemperatureThreshold();
+            ret.name = threshold.name;
+            ret.type = threshold.type;
+            ret.coldThrottlingThresholds = threshold.coldThrottlingThresholds;
+            ret.hotThrottlingThresholds = threshold.hotThrottlingThresholds;
+            return ret;
+        }
+
         @Override
         protected boolean connectToHal() {
             synchronized (mHalLock) {
diff --git a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index 0c5e451..2a88473 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -4658,10 +4658,10 @@
         final int mappedUid = mapUid(uid);
         if (type == WAKE_TYPE_PARTIAL) {
             mWakeLockNesting--;
+            if (historyName == null) {
+                historyName = name;
+            }
             if (mRecordAllHistory) {
-                if (historyName == null) {
-                    historyName = name;
-                }
                 if (mActiveEvents.updateState(HistoryItem.EVENT_WAKE_LOCK_FINISH, historyName,
                         mappedUid, 0)) {
                     mHistory.recordEvent(elapsedRealtimeMs, uptimeMs,
@@ -4669,8 +4669,8 @@
                 }
             }
             if (mWakeLockNesting == 0) {
-                mHistory.recordStateStopEvent(elapsedRealtimeMs, uptimeMs,
-                        HistoryItem.STATE_WAKE_LOCK_FLAG);
+                mHistory.recordWakelockStopEvent(elapsedRealtimeMs, uptimeMs, historyName,
+                        mappedUid);
             }
         }
         if (mappedUid >= 0) {
@@ -12229,6 +12229,7 @@
     @GuardedBy("this")
     private void incrementPerRatDataLocked(ModemActivityInfo deltaInfo, long elapsedRealtimeMs) {
         final int infoSize = deltaInfo.getSpecificInfoLength();
+
         if (infoSize == 1 && deltaInfo.getSpecificInfoRat(0)
                 == AccessNetworkConstants.AccessNetworkType.UNKNOWN
                 && deltaInfo.getSpecificInfoFrequencyRange(0)
diff --git a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
index c36d950..7da9197 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -286,16 +286,15 @@
                 BatteryStats.Uid.PROCESS_STATE_FOREGROUND, realtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
 
-        totalForegroundDurationUs += uid.getProcessStateTime(
-                BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE, realtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED);
-
         return totalForegroundDurationUs / 1000;
     }
 
     private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, long realtimeUs) {
-        return uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND, realtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        return (uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
+                realtimeUs, BatteryStats.STATS_SINCE_CHARGED)
+                + uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
+                realtimeUs, BatteryStats.STATS_SINCE_CHARGED))
+                / 1000;
     }
 
     private BatteryUsageStats getAggregatedBatteryUsageStats(BatteryUsageStatsQuery query) {
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
index 806ed64..2c7aea9 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -15,45 +15,79 @@
  */
 package com.android.server.power.stats;
 
+import android.annotation.Nullable;
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.BatteryUsageStats;
 import android.os.BatteryUsageStatsQuery;
 import android.os.UidBatteryConsumer;
 import android.telephony.CellSignalStrength;
+import android.telephony.ServiceState;
 import android.util.Log;
+import android.util.LongArrayQueue;
 import android.util.SparseArray;
 
 import com.android.internal.os.PowerProfile;
+import com.android.internal.power.ModemPowerProfile;
+
+import java.util.ArrayList;
 
 public class MobileRadioPowerCalculator extends PowerCalculator {
     private static final String TAG = "MobRadioPowerCalculator";
     private static final boolean DEBUG = PowerCalculator.DEBUG;
 
+    private static final double MILLIS_IN_HOUR = 1000.0 * 60 * 60;
+
     private static final int NUM_SIGNAL_STRENGTH_LEVELS =
             CellSignalStrength.getNumSignalStrengthLevels();
 
     private static final BatteryConsumer.Key[] UNINITIALIZED_KEYS = new BatteryConsumer.Key[0];
+    private static final int IGNORE = -1;
 
-    private final UsageBasedPowerEstimator mActivePowerEstimator;
+    private final UsageBasedPowerEstimator mActivePowerEstimator; // deprecated
     private final UsageBasedPowerEstimator[] mIdlePowerEstimators =
-            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS];
-    private final UsageBasedPowerEstimator mScanPowerEstimator;
+            new UsageBasedPowerEstimator[NUM_SIGNAL_STRENGTH_LEVELS]; // deprecated
+    private final UsageBasedPowerEstimator mScanPowerEstimator; // deprecated
+
+    @Nullable
+    private final UsageBasedPowerEstimator mSleepPowerEstimator;
+    @Nullable
+    private final UsageBasedPowerEstimator mIdlePowerEstimator;
+
+    private final PowerProfile mPowerProfile;
 
     private static class PowerAndDuration {
-        public long durationMs;
+        public long remainingDurationMs;
         public double remainingPowerMah;
         public long totalAppDurationMs;
         public double totalAppPowerMah;
-        public long signalDurationMs;
-        public long noCoverageDurationMs;
     }
 
     public MobileRadioPowerCalculator(PowerProfile profile) {
-        // Power consumption when radio is active
+        mPowerProfile = profile;
+
+        final double sleepDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_SLEEP,
+                Double.NaN);
+        if (Double.isNaN(sleepDrainRateMa)) {
+            mSleepPowerEstimator = null;
+        } else {
+            mSleepPowerEstimator = new UsageBasedPowerEstimator(sleepDrainRateMa);
+        }
+
+        final double idleDrainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(
+                PowerProfile.SUBSYSTEM_MODEM | ModemPowerProfile.MODEM_DRAIN_TYPE_IDLE,
+                Double.NaN);
+        if (Double.isNaN(idleDrainRateMa)) {
+            mIdlePowerEstimator = null;
+        } else {
+            mIdlePowerEstimator = new UsageBasedPowerEstimator(idleDrainRateMa);
+        }
+
+        // Instantiate legacy power estimators
         double powerRadioActiveMa =
-                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, -1);
-        if (powerRadioActiveMa == -1) {
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ACTIVE, Double.NaN);
+        if (Double.isNaN(powerRadioActiveMa)) {
             double sum = 0;
             sum += profile.getAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX);
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
@@ -61,11 +95,10 @@
             }
             powerRadioActiveMa = sum / (NUM_SIGNAL_STRENGTH_LEVELS + 1);
         }
-
         mActivePowerEstimator = new UsageBasedPowerEstimator(powerRadioActiveMa);
 
-        // Power consumption when radio is on, but idle
-        if (profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, -1) != -1) {
+        if (!Double.isNaN(
+                profile.getAveragePowerOrDefault(PowerProfile.POWER_RADIO_ON, Double.NaN))) {
             for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
                 mIdlePowerEstimators[i] = new UsageBasedPowerEstimator(
                         profile.getAveragePower(PowerProfile.POWER_RADIO_ON, i));
@@ -95,6 +128,23 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
+        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+        final int powerModel = getPowerModel(totalConsumptionUC, query);
+
+        final double totalActivePowerMah;
+        final ArrayList<UidBatteryConsumer.Builder> apps;
+        final LongArrayQueue appDurationsMs;
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Measured energy is available, don't bother calculating power.
+            totalActivePowerMah = Double.NaN;
+            apps = null;
+            appDurationsMs = null;
+        } else {
+            totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
+            apps = new ArrayList();
+            appDurationsMs = new LongArrayQueue();
+        }
+
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         BatteryConsumer.Key[] keys = UNINITIALIZED_KEYS;
@@ -110,132 +160,352 @@
                 }
             }
 
-            calculateApp(app, uid, total, query, keys);
+            // Sum and populate each app's active radio duration.
+            final long radioActiveDurationMs = calculateDuration(uid,
+                    BatteryStats.STATS_SINCE_CHARGED);
+            if (!app.isVirtualUid()) {
+                total.totalAppDurationMs += radioActiveDurationMs;
+            }
+            app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                    radioActiveDurationMs);
+
+            if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+                // Measured energy is available, populate the consumed power now.
+                final long appConsumptionUC = uid.getMobileRadioMeasuredBatteryConsumptionUC();
+                if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
+                    final double appConsumptionMah = uCtoMah(appConsumptionUC);
+                    if (!app.isVirtualUid()) {
+                        total.totalAppPowerMah += appConsumptionMah;
+                    }
+                    app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                            appConsumptionMah, powerModel);
+
+                    if (query.isProcessStateDataNeeded() && keys != null) {
+                        for (BatteryConsumer.Key key : keys) {
+                            final int processState = key.processState;
+                            if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                                // Already populated with the total across all process states
+                                continue;
+                            }
+                            final long consumptionInStateUc =
+                                    uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+                            final double powerInStateMah = uCtoMah(consumptionInStateUc);
+                            app.setConsumedPower(key, powerInStateMah, powerModel);
+                        }
+                    }
+                }
+            } else {
+                // Cache the app and its active duration for later calculations.
+                apps.add(app);
+                appDurationsMs.addLast(radioActiveDurationMs);
+            }
         }
 
-        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(totalConsumptionUC, query);
-        calculateRemaining(total, powerModel, batteryStats, rawRealtimeUs, totalConsumptionUC);
+        long totalActiveDurationMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
+                BatteryStats.STATS_SINCE_CHARGED) / 1000;
+        if (totalActiveDurationMs < total.totalAppDurationMs) {
+            totalActiveDurationMs = total.totalAppDurationMs;
+        }
+
+        if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            // Need to smear the calculated total active power across the apps based on app
+            // active durations.
+            final int appSize = apps.size();
+            for (int i = 0; i < appSize; i++) {
+                final UidBatteryConsumer.Builder app = apps.get(i);
+                final long activeDurationMs = appDurationsMs.get(i);
+
+                // Proportionally attribute radio power consumption based on active duration.
+                final double appConsumptionMah;
+                if (totalActiveDurationMs == 0.0) {
+                    appConsumptionMah = 0.0;
+                } else {
+                    appConsumptionMah =
+                            (totalActivePowerMah * activeDurationMs) / totalActiveDurationMs;
+                }
+
+                if (!app.isVirtualUid()) {
+                    total.totalAppPowerMah += appConsumptionMah;
+                }
+                app.setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+                        appConsumptionMah, powerModel);
+
+                if (query.isProcessStateDataNeeded() && keys != null) {
+                    final BatteryStats.Uid uid = app.getBatteryStatsUid();
+                    for (BatteryConsumer.Key key : keys) {
+                        final int processState = key.processState;
+                        if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
+                            // Already populated with the total across all process states
+                            continue;
+                        }
+
+                        final long durationInStateMs =
+                                uid.getMobileRadioActiveTimeInProcessState(processState) / 1000;
+                        // Proportionally attribute per process state radio power consumption
+                        // based on time state duration.
+                        final double powerInStateMah;
+                        if (activeDurationMs == 0.0) {
+                            powerInStateMah = 0.0;
+                        } else {
+                            powerInStateMah =
+                                    (appConsumptionMah * durationInStateMs) / activeDurationMs;
+                        }
+                        app.setConsumedPower(key, powerInStateMah, powerModel);
+                    }
+                }
+            }
+        }
+
+        total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
+
+        // Calculate remaining power consumption.
+        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+            total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
+            if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
+        } else {
+            // Smear unattributed active time and add it to the remaining power consumption.
+            total.remainingPowerMah +=
+                    (totalActivePowerMah * total.remainingDurationMs) / totalActiveDurationMs;
+
+            // Calculate the inactive modem power consumption.
+            final BatteryStats.ControllerActivityCounter modemActivity =
+                    batteryStats.getModemControllerActivity();
+            if (modemActivity != null && (mSleepPowerEstimator != null
+                    || mIdlePowerEstimator != null)) {
+                final long sleepDurationMs = modemActivity.getSleepTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mSleepPowerEstimator.calculatePower(sleepDurationMs);
+                final long idleDurationMs = modemActivity.getIdleTimeCounter().getCountLocked(
+                        BatteryStats.STATS_SINCE_CHARGED);
+                total.remainingPowerMah += mIdlePowerEstimator.calculatePower(idleDurationMs);
+            } else {
+                // Modem activity counters unavailable. Use legacy calculations for inactive usage.
+                final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
+                        BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                total.remainingPowerMah += calcScanTimePowerMah(scanningTimeMs);
+                for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
+                    long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
+                            BatteryStats.STATS_SINCE_CHARGED) / 1000;
+                    total.remainingPowerMah += calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
+                }
+            }
+
+        }
 
         if (total.remainingPowerMah != 0 || total.totalAppPowerMah != 0) {
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.remainingDurationMs + total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.remainingPowerMah + total.totalAppPowerMah, powerModel);
 
             builder.getAggregateBatteryConsumerBuilder(
-                    BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
+                            BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                     .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                            total.durationMs)
+                            total.totalAppDurationMs)
                     .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                             total.totalAppPowerMah, powerModel);
         }
     }
 
-    private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
-            PowerAndDuration total,
-            BatteryUsageStatsQuery query, BatteryConsumer.Key[] keys) {
-        final long radioActiveDurationMs = calculateDuration(u, BatteryStats.STATS_SINCE_CHARGED);
-        final long consumptionUC = u.getMobileRadioMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(consumptionUC, query);
-        final double powerMah = calculatePower(u, powerModel, radioActiveDurationMs, consumptionUC);
-
-        if (!app.isVirtualUid()) {
-            total.totalAppDurationMs += radioActiveDurationMs;
-            total.totalAppPowerMah += powerMah;
-        }
-
-        app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
-                        radioActiveDurationMs)
-                .setConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO, powerMah,
-                        powerModel);
-
-        if (query.isProcessStateDataNeeded() && keys != null) {
-            for (BatteryConsumer.Key key: keys) {
-                final int processState = key.processState;
-                if (processState == BatteryConsumer.PROCESS_STATE_UNSPECIFIED) {
-                    // Already populated with the total across all process states
-                    continue;
-                }
-
-                final long durationInStateMs =
-                        u.getMobileRadioActiveTimeInProcessState(processState) / 1000;
-                final long consumptionInStateUc =
-                        u.getMobileRadioMeasuredBatteryConsumptionUC(processState);
-                final double powerInStateMah = calculatePower(u, powerModel, durationInStateMs,
-                        consumptionInStateUc);
-                app.setConsumedPower(key, powerInStateMah, powerModel);
-            }
-        }
-    }
-
     private long calculateDuration(BatteryStats.Uid u, int statsType) {
         return u.getMobileRadioActiveTime(statsType) / 1000;
     }
 
-    private double calculatePower(BatteryStats.Uid u, @BatteryConsumer.PowerModel int powerModel,
-            long radioActiveDurationMs, long measuredChargeUC) {
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            return uCtoMah(measuredChargeUC);
+    private double calculateActiveModemPowerMah(BatteryStats bs, long elapsedRealtimeUs) {
+        final long elapsedRealtimeMs = elapsedRealtimeUs / 1000;
+        final int txLvlCount = CellSignalStrength.getNumSignalStrengthLevels();
+        double consumptionMah = 0.0;
+
+        if (DEBUG) {
+            Log.d(TAG, "Calculating radio power consumption at elapased real timestamp : "
+                    + elapsedRealtimeMs + " ms");
         }
 
-        if (radioActiveDurationMs > 0) {
-            return calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+        boolean hasConstants = false;
+
+        for (int rat = 0; rat < BatteryStats.RADIO_ACCESS_TECHNOLOGY_COUNT; rat++) {
+            final int freqCount = rat == BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR
+                    ? ServiceState.FREQUENCY_RANGE_COUNT : 1;
+            for (int freq = 0; freq < freqCount; freq++) {
+                for (int txLvl = 0; txLvl < txLvlCount; txLvl++) {
+                    final long txDurationMs = bs.getActiveTxRadioDurationMs(rat, freq, txLvl,
+                            elapsedRealtimeMs);
+                    if (txDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                        continue;
+                    }
+                    final double txConsumptionMah = calcTxStatePowerMah(rat, freq, txLvl,
+                            txDurationMs);
+                    if (Double.isNaN(txConsumptionMah)) {
+                        continue;
+                    }
+                    hasConstants = true;
+                    consumptionMah += txConsumptionMah;
+                }
+
+                final long rxDurationMs = bs.getActiveRxRadioDurationMs(rat, freq,
+                        elapsedRealtimeMs);
+                if (rxDurationMs == BatteryStats.DURATION_UNAVAILABLE) {
+                    continue;
+                }
+                final double rxConsumptionMah = calcRxStatePowerMah(rat, freq, rxDurationMs);
+                if (Double.isNaN(rxConsumptionMah)) {
+                    continue;
+                }
+                hasConstants = true;
+                consumptionMah += rxConsumptionMah;
+            }
         }
-        return 0;
+
+        if (!hasConstants) {
+            final long radioActiveDurationMs = bs.getMobileRadioActiveTime(elapsedRealtimeUs,
+                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
+            if (DEBUG) {
+                Log.d(TAG,
+                        "Failed to calculate radio power consumption. Reattempted with legacy "
+                                + "method. Radio active duration : "
+                                + radioActiveDurationMs + " ms");
+            }
+            if (radioActiveDurationMs > 0) {
+                consumptionMah = calcPowerFromRadioActiveDurationMah(radioActiveDurationMs);
+            } else {
+                consumptionMah = 0.0;
+            }
+        }
+
+        if (DEBUG) {
+            Log.d(TAG, "Total active radio power consumption calculated to be " + consumptionMah
+                    + " mAH.");
+        }
+
+        return consumptionMah;
     }
 
-    private void calculateRemaining(PowerAndDuration total,
-            @BatteryConsumer.PowerModel int powerModel, BatteryStats batteryStats,
-            long rawRealtimeUs, long totalConsumptionUC) {
-        long signalTimeMs = 0;
-        double powerMah = 0;
+    private static long buildModemPowerProfileKey(@ModemPowerProfile.ModemDrainType int drainType,
+            @BatteryStats.RadioAccessTechnology int rat, @ServiceState.FrequencyRange int freqRange,
+            int txLevel) {
+        long key = PowerProfile.SUBSYSTEM_MODEM;
 
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            powerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
-            if (powerMah < 0) powerMah = 0;
+        // Attach Modem drain type to the key if specified.
+        if (drainType != IGNORE) {
+            key |= drainType;
         }
 
-        for (int i = 0; i < NUM_SIGNAL_STRENGTH_LEVELS; i++) {
-            long strengthTimeMs = batteryStats.getPhoneSignalStrengthTime(i, rawRealtimeUs,
-                    BatteryStats.STATS_SINCE_CHARGED) / 1000;
-            if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-                final double p = calcIdlePowerAtSignalStrengthMah(strengthTimeMs, i);
-                if (DEBUG && p != 0) {
-                    Log.d(TAG, "Cell strength #" + i + ": time=" + strengthTimeMs + " power="
-                            + BatteryStats.formatCharge(p));
-                }
-                powerMah += p;
-            }
-            signalTimeMs += strengthTimeMs;
-            if (i == 0) {
-                total.noCoverageDurationMs = strengthTimeMs;
-            }
+        // Attach RadioAccessTechnology to the key if specified.
+        switch (rat) {
+            case IGNORE:
+                // do nothing
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_OTHER:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_DEFAULT;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_LTE:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_LTE;
+                break;
+            case BatteryStats.RADIO_ACCESS_TECHNOLOGY_NR:
+                key |= ModemPowerProfile.MODEM_RAT_TYPE_NR;
+                break;
+            default:
+                Log.w(TAG, "Unexpected RadioAccessTechnology : " + rat);
         }
 
-        final long scanningTimeMs = batteryStats.getPhoneSignalScanningTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long radioActiveTimeMs = batteryStats.getMobileRadioActiveTime(rawRealtimeUs,
-                BatteryStats.STATS_SINCE_CHARGED) / 1000;
-        long remainingActiveTimeMs = radioActiveTimeMs - total.totalAppDurationMs;
-
-        if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE) {
-            final double p = calcScanTimePowerMah(scanningTimeMs);
-            if (DEBUG && p != 0) {
-                Log.d(TAG, "Cell radio scanning: time=" + scanningTimeMs
-                        + " power=" + BatteryStats.formatCharge(p));
-            }
-            powerMah += p;
-
-            if (remainingActiveTimeMs > 0) {
-                powerMah += calcPowerFromRadioActiveDurationMah(remainingActiveTimeMs);
-            }
+        // Attach NR Frequency Range to the key if specified.
+        switch (freqRange) {
+            case IGNORE:
+                // do nothing
+                break;
+            case ServiceState.FREQUENCY_RANGE_UNKNOWN:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_DEFAULT;
+                break;
+            case ServiceState.FREQUENCY_RANGE_LOW:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_LOW;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MID:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MID;
+                break;
+            case ServiceState.FREQUENCY_RANGE_HIGH:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_HIGH;
+                break;
+            case ServiceState.FREQUENCY_RANGE_MMWAVE:
+                key |= ModemPowerProfile.MODEM_NR_FREQUENCY_RANGE_MMWAVE;
+                break;
+            default:
+                Log.w(TAG, "Unexpected NR frequency range : " + freqRange);
         }
-        total.durationMs = radioActiveTimeMs;
-        total.remainingPowerMah = powerMah;
-        total.signalDurationMs = signalTimeMs;
+
+        // Attach transmission level to the key if specified.
+        switch (txLevel) {
+            case IGNORE:
+                // do nothing
+                break;
+            case 0:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_0;
+                break;
+            case 1:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_1;
+                break;
+            case 2:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_2;
+                break;
+            case 3:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_3;
+                break;
+            case 4:
+                key |= ModemPowerProfile.MODEM_TX_LEVEL_4;
+                break;
+            default:
+                Log.w(TAG, "Unexpected transmission level : " + txLevel);
+        }
+        return key;
+    }
+
+    /**
+     * Calculates active receive radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcRxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, long rxDurationMs) {
+        final long rxKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_RX, rat,
+                freqRange, IGNORE);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(rxKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(rxKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * rxDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated RX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + rxDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) rxKey));
+        }
+        return consumptionMah;
+    }
+
+    /**
+     * Calculates active transmit radio power consumption (in milliamp-hours) from the given state's
+     * duration.
+     */
+    public double calcTxStatePowerMah(@BatteryStats.RadioAccessTechnology int rat,
+            @ServiceState.FrequencyRange int freqRange, int txLevel, long txDurationMs) {
+        final long txKey = buildModemPowerProfileKey(ModemPowerProfile.MODEM_DRAIN_TYPE_TX, rat,
+                freqRange, txLevel);
+        final double drainRateMa = mPowerProfile.getAverageBatteryDrainOrDefaultMa(txKey,
+                Double.NaN);
+        if (Double.isNaN(drainRateMa)) {
+            Log.w(TAG, "Unavailable Power Profile constant for key 0x" + Long.toHexString(txKey));
+            return Double.NaN;
+        }
+
+        final double consumptionMah = drainRateMa * txDurationMs / MILLIS_IN_HOUR;
+        if (DEBUG) {
+            Log.d(TAG, "Calculated TX consumption " + consumptionMah + " mAH from a drain rate of "
+                    + drainRateMa + " mA and a duration of " + txDurationMs + " ms for "
+                    + ModemPowerProfile.keyToString((int) txKey));
+        }
+        return consumptionMah;
     }
 
     /**
diff --git a/services/core/java/com/android/server/security/rkp/OWNERS b/services/core/java/com/android/server/security/rkp/OWNERS
new file mode 100644
index 0000000..348f940
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/OWNERS
@@ -0,0 +1 @@
+file:platform/frameworks/base:master:/core/java/android/security/rkp/OWNERS
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.java
new file mode 100644
index 0000000..65a4b38
--- /dev/null
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningService.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.
+ */
+
+package com.android.server.security.rkp;
+
+import android.content.Context;
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.security.rkp.IGetKeyCallback;
+import android.security.rkp.IGetRegistrationCallback;
+import android.security.rkp.IRegistration;
+import android.security.rkp.IRemoteProvisioning;
+import android.security.rkp.service.RegistrationProxy;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import java.time.Duration;
+
+/**
+ * Implements the remote provisioning system service. This service is backed by a mainline
+ * module, allowing the underlying implementation to be updated. The code here is a thin
+ * proxy for the code in android.security.rkp.service.
+ *
+ * @hide
+ */
+public class RemoteProvisioningService extends SystemService {
+    public static final String TAG = "RemoteProvisionSysSvc";
+    private static final Duration CREATE_REGISTRATION_TIMEOUT = Duration.ofSeconds(10);
+    private final RemoteProvisioningImpl mBinderImpl = new RemoteProvisioningImpl();
+
+    /** @hide */
+    public RemoteProvisioningService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.REMOTE_PROVISIONING_SERVICE, mBinderImpl);
+    }
+
+    private final class RemoteProvisioningImpl extends IRemoteProvisioning.Stub {
+
+        final class RegistrationBinder extends IRegistration.Stub {
+            static final String TAG = RemoteProvisioningService.TAG;
+            private final RegistrationProxy mRegistration;
+
+            RegistrationBinder(RegistrationProxy registration) {
+                mRegistration = registration;
+            }
+
+            @Override
+            public void getKey(int keyId, IGetKeyCallback callback) {
+                Log.e(TAG, "RegistrationBinder.getKey NOT YET IMPLEMENTED");
+            }
+
+            @Override
+            public void cancelGetKey(IGetKeyCallback callback) {
+                Log.e(TAG, "RegistrationBinder.cancelGetKey NOT YET IMPLEMENTED");
+            }
+
+            @Override
+            public void storeUpgradedKey(byte[] oldKeyBlob, byte[] newKeyBlob) {
+                Log.e(TAG, "RegistrationBinder.storeUpgradedKey NOT YET IMPLEMENTED");
+            }
+        }
+
+        @Override
+        public void getRegistration(String irpcName, IGetRegistrationCallback callback)
+                throws RemoteException {
+            final int callerUid = Binder.getCallingUidOrThrow();
+            final long callingIdentity = Binder.clearCallingIdentity();
+            try {
+                Log.i(TAG, "getRegistration(" + irpcName + ")");
+                RegistrationProxy.createAsync(
+                        getContext(),
+                        callerUid,
+                        irpcName,
+                        CREATE_REGISTRATION_TIMEOUT,
+                        getContext().getMainExecutor(),
+                        new OutcomeReceiver<>() {
+                            @Override
+                            public void onResult(RegistrationProxy registration) {
+                                try {
+                                    callback.onSuccess(new RegistrationBinder(registration));
+                                } catch (RemoteException e) {
+                                    Log.e(TAG, "Error calling success callback", e);
+                                }
+                            }
+
+                            @Override
+                            public void onError(Exception error) {
+                                try {
+                                    callback.onError(error.toString());
+                                } catch (RemoteException e) {
+                                    Log.e(TAG, "Error calling error callback", e);
+                                }
+                            }
+                        });
+            } finally {
+                Binder.restoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        @Override
+        public void cancelGetRegistration(IGetRegistrationCallback callback)
+                throws RemoteException {
+            Log.i(TAG, "cancelGetRegistration()");
+            callback.onError("cancelGetRegistration not yet implemented");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 434cd78..392fda9 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -205,4 +205,16 @@
      * @see com.android.internal.statusbar.IStatusBar#showRearDisplayDialog
      */
     void showRearDisplayDialog(int currentBaseState);
+
+    /**
+     * Called when requested to go to fullscreen from the active split app.
+     */
+    void goToFullscreenFromSplit();
+
+    /**
+     * Enters stage split from a current running app.
+     *
+     * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp
+     */
+    void enterStageSplitFromRunningApp(boolean leftOrTop);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 006d888..8d71d9c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -726,6 +726,24 @@
                 } catch (RemoteException ex) { }
             }
         }
+
+        @Override
+        public void goToFullscreenFromSplit() {
+            if (mBar != null) {
+                try {
+                    mBar.goToFullscreenFromSplit();
+                } catch (RemoteException ex) { }
+            }
+        }
+
+        @Override
+        public void enterStageSplitFromRunningApp(boolean leftOrTop) {
+            if (mBar != null) {
+                try {
+                    mBar.enterStageSplitFromRunningApp(leftOrTop);
+                } catch (RemoteException ex) { }
+            }
+        }
     };
 
     private final GlobalActionsProvider mGlobalActionsProvider = new GlobalActionsProvider() {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 5d08461..4480d52 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -34,6 +34,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
+import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.ILocalWallpaperColorConsumer;
@@ -3166,6 +3167,15 @@
                 }
             }
 
+            final ActivityOptions clientOptions = ActivityOptions.makeBasic();
+            clientOptions.setIgnorePendingIntentCreatorForegroundState(true);
+            PendingIntent clientIntent = PendingIntent.getActivityAsUser(
+                    mContext, 0, Intent.createChooser(
+                            new Intent(Intent.ACTION_SET_WALLPAPER),
+                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
+                    PendingIntent.FLAG_IMMUTABLE, clientOptions.toBundle(),
+                    UserHandle.of(serviceUserId));
+
             // Bind the service!
             if (DEBUG) Slog.v(TAG, "Binding to:" + componentName);
             final int componentUid = mIPackageManager.getPackageUid(componentName.getPackageName(),
@@ -3174,11 +3184,7 @@
             intent.setComponent(componentName);
             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                     com.android.internal.R.string.wallpaper_binding_label);
-            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
-                    mContext, 0,
-                    Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER),
-                            mContext.getText(com.android.internal.R.string.chooser_wallpaper)),
-                    PendingIntent.FLAG_IMMUTABLE, null, new UserHandle(serviceUserId)));
+            intent.putExtra(Intent.EXTRA_CLIENT_INTENT, clientIntent);
             if (!mContext.bindServiceAsUser(intent, newConn,
                     Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI
                             | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE
diff --git a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
index e155a06..e145898 100644
--- a/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
+++ b/services/core/java/com/android/server/wearable/WearableSensingManagerService.java
@@ -38,6 +38,7 @@
 import android.util.Slog;
 
 import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.infra.AbstractMasterSystemService;
@@ -161,6 +162,32 @@
         return null;
     }
 
+    @VisibleForTesting
+    void provideDataStream(@UserIdInt int userId, ParcelFileDescriptor parcelFileDescriptor,
+            RemoteCallback callback) {
+        synchronized (mLock) {
+            final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+            if (mService != null) {
+                mService.onProvideDataStream(parcelFileDescriptor, callback);
+            } else {
+                Slog.w(TAG, "Service not available.");
+            }
+        }
+    }
+
+    @VisibleForTesting
+    void provideData(@UserIdInt int userId, PersistableBundle data, SharedMemory sharedMemory,
+            RemoteCallback callback) {
+        synchronized (mLock) {
+            final WearableSensingManagerPerUserService mService = getServiceForUserLocked(userId);
+            if (mService != null) {
+                mService.onProvidedData(data, sharedMemory, callback);
+            } else {
+                Slog.w(TAG, "Service not available.");
+            }
+        }
+    }
+
     private final class WearableSensingManagerInternal extends IWearableSensingManager.Stub {
         final WearableSensingManagerPerUserService mService = getServiceForUserLocked(
                 UserHandle.getCallingUserId());
@@ -205,6 +232,8 @@
         @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver) {
+            new WearableSensingShellCommand(WearableSensingManagerService.this).exec(
+                    this, in, out, err, args, callback, resultReceiver);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
new file mode 100644
index 0000000..842bccb
--- /dev/null
+++ b/services/core/java/com/android/server/wearable/WearableSensingShellCommand.java
@@ -0,0 +1,212 @@
+/*
+ * 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.wearable;
+
+import android.annotation.NonNull;
+import android.app.wearable.WearableSensingManager;
+import android.content.ComponentName;
+import android.os.Binder;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteCallback;
+import android.os.ShellCommand;
+import android.util.Slog;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+
+final class WearableSensingShellCommand extends ShellCommand {
+    private static final String TAG = WearableSensingShellCommand.class.getSimpleName();
+
+    static final TestableCallbackInternal sTestableCallbackInternal =
+            new TestableCallbackInternal();
+
+    @NonNull
+    private final WearableSensingManagerService mService;
+
+    private static ParcelFileDescriptor[] sPipe;
+
+    WearableSensingShellCommand(@NonNull WearableSensingManagerService service) {
+        mService = service;
+    }
+
+    /** Callbacks for WearableSensingService results used internally for testing. */
+    static class TestableCallbackInternal {
+        private int mLastStatus;
+
+        public int getLastStatus() {
+            return mLastStatus;
+        }
+
+        @NonNull
+        private RemoteCallback createRemoteStatusCallback() {
+            return new RemoteCallback(result -> {
+                int status = result.getInt(WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    mLastStatus = status;
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            });
+        }
+    }
+
+    @Override
+    public int onCommand(String cmd) {
+        if (cmd == null) {
+            return handleDefaultCommands(cmd);
+        }
+
+        switch (cmd) {
+            case "create-data-stream":
+                return createDataStream();
+            case "destroy-data-stream":
+                return destroyDataStream();
+            case "provide-data-stream":
+                return provideDataStream();
+            case "write-to-data-stream":
+                return writeToDataStream();
+            case "provide-data":
+                return provideData();
+            case "get-last-status-code":
+                return getLastStatusCode();
+            case "get-bound-package":
+                return getBoundPackageName();
+            case "set-temporary-service":
+                return setTemporaryService();
+            default:
+                return handleDefaultCommands(cmd);
+        }
+    }
+
+    @Override
+    public void onHelp() {
+        PrintWriter pw = getOutPrintWriter();
+        pw.println("WearableSensingCommands commands: ");
+        pw.println("  help");
+        pw.println("    Print this help text.");
+        pw.println();
+        pw.println("  create-data-stream: Creates a data stream to be provided.");
+        pw.println("  destroy-data-stream: Destroys a data stream if one was previously created.");
+        pw.println("  provide-data-stream USER_ID: "
+                + "Provides data stream to WearableSensingService.");
+        pw.println("  write-to-data-stream STRING: writes string to data stream.");
+        pw.println("  provide-data USER_ID KEY INTEGER: provide integer as data with key.");
+        pw.println("  get-last-status-code: Prints the latest request status code.");
+        pw.println("  get-bound-package USER_ID:"
+                + "     Print the bound package that implements the service.");
+        pw.println("  set-temporary-service USER_ID [PACKAGE_NAME] [COMPONENT_NAME DURATION]");
+        pw.println("    Temporarily (for DURATION ms) changes the service implementation.");
+        pw.println("    To reset, call with just the USER_ID argument.");
+    }
+
+    private int createDataStream() {
+        Slog.d(TAG, "createDataStream");
+        try {
+            sPipe = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            Slog.d(TAG, "Failed to createDataStream.", e);
+        }
+        return 0;
+    }
+
+    private int destroyDataStream() {
+        Slog.d(TAG, "destroyDataStream");
+        try {
+            if (sPipe != null) {
+                sPipe[0].close();
+                sPipe[1].close();
+            }
+        } catch (IOException e) {
+            Slog.d(TAG, "Failed to destroyDataStream.", e);
+        }
+        return 0;
+    }
+
+    private int provideDataStream() {
+        Slog.d(TAG, "provideDataStream");
+        if (sPipe != null) {
+            final int userId = Integer.parseInt(getNextArgRequired());
+            mService.provideDataStream(userId, sPipe[0],
+                    sTestableCallbackInternal.createRemoteStatusCallback());
+        }
+        return 0;
+    }
+
+    private int writeToDataStream() {
+        Slog.d(TAG, "writeToDataStream");
+        if (sPipe != null) {
+            final String value = getNextArgRequired();
+            try {
+                ParcelFileDescriptor writePipe = sPipe[1].dup();
+                OutputStream os = new ParcelFileDescriptor.AutoCloseOutputStream(writePipe);
+                os.write(value.getBytes());
+            } catch (IOException e) {
+                Slog.d(TAG, "Failed to writeToDataStream.", e);
+            }
+        }
+        return 0;
+    }
+
+    private int provideData() {
+        Slog.d(TAG, "provideData");
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String key = getNextArgRequired();
+        final int value = Integer.parseInt(getNextArgRequired());
+        PersistableBundle data = new PersistableBundle();
+        data.putInt(key, value);
+
+        mService.provideData(userId, data, null,
+                sTestableCallbackInternal.createRemoteStatusCallback());
+        return 0;
+    }
+
+    private int getLastStatusCode() {
+        Slog.d(TAG, "getLastStatusCode");
+        final PrintWriter resultPrinter = getOutPrintWriter();
+        int lastStatus = sTestableCallbackInternal.getLastStatus();
+        resultPrinter.println(lastStatus);
+        return 0;
+    }
+
+    private int setTemporaryService() {
+        final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String serviceName = getNextArg();
+        if (serviceName == null) {
+            mService.resetTemporaryService(userId);
+            out.println("WearableSensingManagerService temporary reset. ");
+            return 0;
+        }
+
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryService(userId, serviceName, duration);
+        out.println("WearableSensingService temporarily set to " + serviceName
+                + " for " + duration + "ms");
+        return 0;
+    }
+
+    private int getBoundPackageName() {
+        final PrintWriter resultPrinter = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final ComponentName componentName = mService.getComponentName(userId);
+        resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
+        return 0;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index e1ab291..13111fb 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -740,7 +740,7 @@
         // visible such as after the top task is finished.
         for (int i = mTransitionInfoList.size() - 2; i >= 0; i--) {
             final TransitionInfo prevInfo = mTransitionInfoList.get(i);
-            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.mVisibleRequested) {
+            if (prevInfo.mIsDrawn || !prevInfo.mLastLaunchedActivity.isVisibleRequested()) {
                 scheduleCheckActivityToBeDrawn(prevInfo.mLastLaunchedActivity, 0 /* delay */);
             }
         }
@@ -867,7 +867,7 @@
             return;
         }
         if (DEBUG_METRICS) {
-            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.mVisibleRequested
+            Slog.i(TAG, "notifyVisibilityChanged " + r + " visible=" + r.isVisibleRequested()
                     + " state=" + r.getState() + " finishing=" + r.finishing);
         }
         if (r.isState(ActivityRecord.State.RESUMED) && r.mDisplayContent.isSleeping()) {
@@ -876,7 +876,7 @@
             // the tracking of launch event.
             return;
         }
-        if (!r.mVisibleRequested || r.finishing) {
+        if (!r.isVisibleRequested() || r.finishing) {
             // Check if the tracker can be cancelled because the last launched activity may be
             // no longer visible.
             scheduleCheckActivityToBeDrawn(r, 0 /* delay */);
@@ -909,7 +909,7 @@
             // activities in this task may be finished, invisible or drawn, so the transition event
             // should be cancelled.
             if (t != null && t.forAllActivities(
-                    a -> a.mVisibleRequested && !a.isReportedDrawn() && !a.finishing)) {
+                    a -> a.isVisibleRequested() && !a.isReportedDrawn() && !a.finishing)) {
                 return;
             }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e099aac..9245d7f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -796,12 +796,6 @@
     // TODO: Make this final
     int mTargetSdk;
 
-    // Is this window's surface needed?  This is almost like visible, except
-    // it will sometimes be true a little earlier: when the activity record has
-    // been shown, but is still waiting for its app transition to execute
-    // before making its windows shown.
-    boolean mVisibleRequested;
-
     // Last visibility state we reported to the app token.
     boolean reportedVisible;
 
@@ -1574,15 +1568,9 @@
 
         if (oldParent != null) {
             oldParent.cleanUpActivityReferences(this);
-            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
-            // client side if needed.
-            oldParent.onActivityVisibleRequestedChanged();
         }
 
         if (newParent != null) {
-            // Update isVisibleRequested value of parent TaskFragment and send the callback to the
-            // client side if needed.
-            newParent.onActivityVisibleRequestedChanged();
             if (isState(RESUMED)) {
                 newParent.setResumedActivity(this, "onParentChanged");
                 mImeInsetsFrozenUntilStartInput = false;
@@ -3622,7 +3610,7 @@
         // implied that the current finishing activity should be added into stopping list rather
         // than destroy immediately.
         final boolean isNextNotYetVisible = next != null
-                && (!next.nowVisible || !next.mVisibleRequested);
+                && (!next.nowVisible || !next.isVisibleRequested());
 
         // Clear last paused activity to ensure top activity can be resumed during sleeping.
         if (isNextNotYetVisible && mDisplayContent.isSleeping()
@@ -4440,7 +4428,7 @@
     void transferStartingWindowFromHiddenAboveTokenIfNeeded() {
         task.forAllActivities(fromActivity -> {
             if (fromActivity == this) return true;
-            return !fromActivity.mVisibleRequested && transferStartingWindow(fromActivity);
+            return !fromActivity.isVisibleRequested() && transferStartingWindow(fromActivity);
         });
     }
 
@@ -5086,11 +5074,6 @@
         return mVisible;
     }
 
-    @Override
-    boolean isVisibleRequested() {
-        return mVisibleRequested;
-    }
-
     void setVisible(boolean visible) {
         if (visible != mVisible) {
             mVisible = visible;
@@ -5105,15 +5088,9 @@
      * This is the only place that writes {@link #mVisibleRequested} (except unit test). The caller
      * outside of this class should use {@link #setVisibility}.
      */
-    private void setVisibleRequested(boolean visible) {
-        if (visible == mVisibleRequested) {
-            return;
-        }
-        mVisibleRequested = visible;
-        final TaskFragment taskFragment = getTaskFragment();
-        if (taskFragment != null) {
-            taskFragment.onActivityVisibleRequestedChanged();
-        }
+    @Override
+    boolean setVisibleRequested(boolean visible) {
+        if (!super.setVisibleRequested(visible)) return false;
         setInsetsFrozen(!visible);
         if (app != null) {
             mTaskSupervisor.onProcessActivityStateChanged(app, false /* forceBatch */);
@@ -5122,6 +5099,13 @@
         if (!visible) {
             finishOrAbortReplacingWindow();
         }
+        return true;
+    }
+
+    @Override
+    protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
+        // Activity manages visibleRequested directly (it's not determined by children)
+        return false;
     }
 
     /**
@@ -5433,25 +5417,20 @@
      */
     private void postApplyAnimation(boolean visible, boolean fromTransition) {
         final boolean usingShellTransitions = mTransitionController.isShellTransitionsEnabled();
-        final boolean delayed = isAnimating(PARENTS | CHILDREN,
+        final boolean delayed = !usingShellTransitions && isAnimating(PARENTS | CHILDREN,
                 ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
                         | ANIMATION_TYPE_RECENTS);
-        if (!delayed) {
+        if (!delayed && !usingShellTransitions) {
             // We aren't delayed anything, but exiting windows rely on the animation finished
             // callback being called in case the ActivityRecord was pretending to be delayed,
             // which we might have done because we were in closing/opening apps list.
-            if (!usingShellTransitions) {
-                onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
-                if (visible) {
-                    // The token was made immediately visible, there will be no entrance animation.
-                    // We need to inform the client the enter animation was finished.
-                    mEnteringAnimation = true;
-                    mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
-                            token);
-                }
-            } else {
-                // update wallpaper target
-                setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
+            onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
+            if (visible) {
+                // The token was made immediately visible, there will be no entrance animation.
+                // We need to inform the client the enter animation was finished.
+                mEnteringAnimation = true;
+                mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+                        token);
             }
         }
 
@@ -5460,8 +5439,8 @@
         // updated.
         // If we're becoming invisible, update the client visibility if we are not running an
         // animation. Otherwise, we'll update client visibility in onAnimationFinished.
-        if (visible || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
-                || usingShellTransitions) {
+        if (visible || usingShellTransitions
+                || !isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)) {
             setClientVisible(visible);
         }
 
@@ -6549,7 +6528,7 @@
         if (associatedTask == null) {
             removeStartingWindow();
         } else if (associatedTask.getActivity(
-                r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
+                r -> r.isVisibleRequested() && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
@@ -7442,8 +7421,10 @@
             } else if (!show && mLastSurfaceShowing) {
                 getSyncTransaction().hide(mSurfaceControl);
             }
-            if (show) {
-                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getSyncTransaction());
+            // Input sink surface is not a part of animation, so just apply in a steady state
+            // (non-sync) with pending transaction.
+            if (show && mSyncState == SYNC_STATE_NONE) {
+                mActivityRecordInputSink.applyChangesToSurfaceIfChanged(getPendingTransaction());
             }
         }
         if (mThumbnail != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
index 30c7b23..0859d40 100644
--- a/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
+++ b/services/core/java/com/android/server/wm/ActivityServiceConnectionsHolder.java
@@ -92,7 +92,7 @@
 
     public boolean isActivityVisible() {
         synchronized (mService.mGlobalLock) {
-            return mActivity.mVisibleRequested || mActivity.isState(RESUMED, PAUSING);
+            return mActivity.isVisibleRequested() || mActivity.isState(RESUMED, PAUSING);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 56aae2d6..05ec3b5 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -561,7 +561,7 @@
         if (rootTask == null) return false;
         final RemoteTransition remote = options.getRemoteTransition();
         final ActivityRecord r = rootTask.topRunningActivity();
-        if (r == null || r.mVisibleRequested || !r.attachedToProcess() || remote == null
+        if (r == null || r.isVisibleRequested() || !r.attachedToProcess() || remote == null
                 || !r.mActivityComponent.equals(intent.getComponent())
                 // Recents keeps invisible while device is locked.
                 || r.mDisplayContent.isKeyguardLocked()) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 5c83c4f..2f4d154 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2285,6 +2285,15 @@
             mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
         }
 
+        if (r.info.requiredDisplayCategory != null && mSourceRecord != null
+                && !r.info.requiredDisplayCategory.equals(
+                        mSourceRecord.info.requiredDisplayCategory)) {
+            // Adding NEW_TASK flag for activity with display category attribute if the display
+            // category of the source record is different, so that the activity won't be launched
+            // in source record's task.
+            mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
+        }
+
         sendNewTaskResultRequestIfNeeded();
 
         if ((mLaunchFlags & FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
@@ -2372,6 +2381,12 @@
             Slog.w(TAG, "Starting activity in task not in recents: " + inTask);
             mInTask = null;
         }
+        // Prevent to start activity in Task with different display category
+        if (mInTask != null && !mInTask.isSameRequiredDisplayCategory(r.info)) {
+            Slog.w(TAG, "Starting activity in task with different display category: "
+                    + mInTask);
+            mInTask = null;
+        }
         mInTaskFragment = inTaskFragment;
 
         mStartFlags = startFlags;
@@ -2637,7 +2652,7 @@
                     // If the activity is visible in multi-windowing mode, it may already be on
                     // the top (visible to user but not the global top), then the result code
                     // should be START_DELIVERED_TO_TOP instead of START_TASK_TO_FRONT.
-                    final boolean wasTopOfVisibleRootTask = intentActivity.mVisibleRequested
+                    final boolean wasTopOfVisibleRootTask = intentActivity.isVisibleRequested()
                             && intentActivity.inMultiWindowMode()
                             && intentActivity == mTargetRootTask.topRunningActivity();
                     // We only want to move to the front, if we aren't going to launch on a
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 3145ab3..8a247cf 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -145,6 +145,7 @@
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.HostingRecord;
 import com.android.server.am.UserState;
+import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.utils.Slogf;
 import com.android.server.wm.ActivityMetricsLogger.LaunchingState;
 
@@ -2619,12 +2620,17 @@
         // ActivityStarter will acquire the lock where the places need, so execute the request
         // outside of the lock.
         try {
+            // We need to temporarily disable the explicit intent filter matching enforcement
+            // because Task does not store the resolved type of the intent data, causing filter
+            // mismatch in certain cases. (b/240373119)
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(true);
             return mService.getActivityStartController().startActivityInPackage(taskCallingUid,
                     callingPid, callingUid, callingPackage, callingFeatureId, intent, null, null,
                     null, 0, 0, options, userId, task, "startActivityFromRecents",
                     false /* validateIncomingUser */, null /* originatingPendingIntent */,
                     false /* allowBackgroundActivityStart */);
         } finally {
+            PackageManagerServiceUtils.DISABLE_ENFORCE_INTENTS_TO_MATCH_INTENT_FILTERS.set(false);
             synchronized (mService.mGlobalLock) {
                 mService.continueWindowLayout();
             }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 8680f1b..14131e6 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -235,6 +235,7 @@
             // We don't have an application callback, let's find the destination of the back gesture
             // The search logic should align with ActivityClientController#finishActivity
             prevActivity = currentTask.topRunningActivity(currentActivity.token, INVALID_TASK_ID);
+            final boolean isOccluded = isKeyguardOccluded(window);
             // TODO Dialog window does not need to attach on activity, check
             // window.mAttrs.type != TYPE_BASE_APPLICATION
             if ((window.getParent().getChildCount() > 1
@@ -244,16 +245,24 @@
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
                 removedWindowContainer = window;
             } else if (prevActivity != null) {
-                // We have another Activity in the same currentTask to go to
-                backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
-                removedWindowContainer = currentActivity;
-                prevTask = prevActivity.getTask();
+                if (!isOccluded || prevActivity.canShowWhenLocked()) {
+                    // We have another Activity in the same currentTask to go to
+                    backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+                    removedWindowContainer = currentActivity;
+                    prevTask = prevActivity.getTask();
+                } else {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
+                }
             } else if (currentTask.returnsToHomeRootTask()) {
-                // Our Task should bring back to home
-                removedWindowContainer = currentTask;
-                prevTask = currentTask.getDisplayArea().getRootHomeTask();
-                backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
-                mShowWallpaper = true;
+                if (isOccluded) {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
+                } else {
+                    // Our Task should bring back to home
+                    removedWindowContainer = currentTask;
+                    prevTask = currentTask.getDisplayArea().getRootHomeTask();
+                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                    mShowWallpaper = true;
+                }
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
@@ -267,7 +276,9 @@
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 } else {
                     prevActivity = prevTask.getTopNonFinishingActivity();
-                    if (prevTask.isActivityTypeHome()) {
+                    if (prevActivity == null || (isOccluded && !prevActivity.canShowWhenLocked())) {
+                        backType = BackNavigationInfo.TYPE_CALLBACK;
+                    } else if (prevTask.isActivityTypeHome()) {
                         backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
                         mShowWallpaper = true;
                     } else {
@@ -323,6 +334,12 @@
         return mAnimationTargets.mComposed && mAnimationTargets.mWaitTransition;
     }
 
+    boolean isKeyguardOccluded(WindowState focusWindow) {
+        final KeyguardController kc = mWindowManagerService.mAtmService.mKeyguardController;
+        final int displayId = focusWindow.getDisplayId();
+        return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
+    }
+
     // For legacy transition.
     /**
      *  Once we find the transition targets match back animation targets, remove the target from
@@ -810,7 +827,7 @@
         if (activity == null) {
             return;
         }
-        if (!activity.mVisibleRequested) {
+        if (!activity.isVisibleRequested()) {
             activity.setVisibility(true);
         }
         activity.mLaunchTaskBehind = true;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6140508..d5802cf 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -876,11 +876,11 @@
             final ActivityRecord activity = w.mActivityRecord;
             if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
             else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
                     + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
-                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
+                    + " visibleRequested=" + (activity != null && activity.isVisibleRequested())
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
@@ -1705,7 +1705,7 @@
                         .notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
             }
             // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
-            final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+            final ActivityRecord topCandidate = !r.isVisibleRequested() ? topRunningActivity() : r;
             if (handleTopActivityLaunchingInDifferentOrientation(
                     topCandidate, r, true /* checkOpening */)) {
                 // Display orientation should be deferred until the top fixed rotation is finished.
@@ -2101,13 +2101,13 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    boolean canShowTasksInRecents() {
+    boolean canShowTasksInHostDeviceRecents() {
         if (mDwpcHelper == null) {
             return true;
         }
-        return mDwpcHelper.canShowTasksInRecents();
+        return mDwpcHelper.canShowTasksInHostDeviceRecents();
     }
 
     /**
@@ -2712,7 +2712,7 @@
         mWmService.mWindowsChanged = true;
         // If the transition finished callback cannot match the token for some reason, make sure the
         // rotated state is cleared if it is already invisible.
-        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.mVisibleRequested
+        if (mFixedRotationLaunchingApp != null && !mFixedRotationLaunchingApp.isVisibleRequested()
                 && !mFixedRotationLaunchingApp.isVisible()
                 && !mDisplayRotation.isRotatingSeamlessly()) {
             clearFixedRotationLaunchingApp();
@@ -6814,10 +6814,9 @@
 
         @Override
         public boolean isRequestedVisible(@InsetsType int types) {
-            if (types == ime()) {
-                return getInsetsStateController().getImeSourceProvider().isImeShowing();
-            }
-            return (mRequestedVisibleTypes & types) != 0;
+            return ((types & ime()) != 0
+                            && getInsetsStateController().getImeSourceProvider().isImeShowing())
+                    || (mRequestedVisibleTypes & types) != 0;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 9ac1762..300deca 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -57,7 +57,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManager.TRANSIT_WAKE;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
 import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -789,13 +788,7 @@
             if (!mDisplayContent.isDefaultDisplay) {
                 return;
             }
-            if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
-                    && !mDisplayContent.mTransitionController.isCollecting()) {
-                // Start a transition for waking. This is needed for showWhenLocked activities.
-                mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
-                        0 /* flags */, null /* trigger */, mDisplayContent);
-            }
-            mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+            mService.mAtmService.mKeyguardController.updateDeferTransitionForAod(
                     mAwake /* waiting */);
         }
     }
@@ -2135,15 +2128,10 @@
     }
 
     void updateSystemBarAttributes() {
-        WindowState winCandidate = mFocusedWindow;
-        if (winCandidate == null && mTopFullscreenOpaqueWindowState != null
-                && (mTopFullscreenOpaqueWindowState.mAttrs.flags
-                & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) == 0) {
-            // Only focusable window can take system bar control.
-            winCandidate = mTopFullscreenOpaqueWindowState;
-        }
         // If there is no window focused, there will be nobody to handle the events
         // anyway, so just hang on in whatever state we're in until things settle down.
+        WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
+                : mTopFullscreenOpaqueWindowState;
         if (winCandidate == null) {
             return;
         }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index eaa08fd..185e06e 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1537,6 +1537,7 @@
         private int mHalfFoldSavedRotation = -1; // No saved rotation
         private DeviceStateController.FoldState mFoldState =
                 DeviceStateController.FoldState.UNKNOWN;
+        private boolean mInHalfFoldTransition = false;
 
         boolean overrideFrozenRotation() {
             return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1544,6 +1545,7 @@
 
         boolean shouldRevertOverriddenRotation() {
             return mFoldState == DeviceStateController.FoldState.OPEN // When transitioning to open.
+                    && mInHalfFoldTransition
                     && mHalfFoldSavedRotation != -1 // Ignore if we've already reverted.
                     && mUserRotationMode
                     == WindowManagerPolicy.USER_ROTATION_LOCKED; // Ignore if we're unlocked.
@@ -1552,6 +1554,7 @@
         int revertOverriddenRotation() {
             int savedRotation = mHalfFoldSavedRotation;
             mHalfFoldSavedRotation = -1;
+            mInHalfFoldTransition = false;
             return savedRotation;
         }
 
@@ -1577,16 +1580,11 @@
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             } else {
-                // Revert the rotation to our saved value if we transition from HALF_FOLDED.
-                if (mHalfFoldSavedRotation != -1) {
-                    mRotation = mHalfFoldSavedRotation;
-                }
-                // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
-                // so we will override USER_ROTATION_LOCKED and allow a rotation).
+                mInHalfFoldTransition = true;
+                mFoldState = newState;
+                // Tell the device to update its orientation.
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
-                // Once we are rotated, set mFoldstate, effectively removing the lock override.
-                mFoldState = newState;
             }
         }
     }
@@ -1683,6 +1681,7 @@
 
     private static class RotationHistory {
         private static final int MAX_SIZE = 8;
+        private static final int NO_FOLD_CONTROLLER = -2;
         private static class Record {
             final @Surface.Rotation int mFromRotation;
             final @Surface.Rotation int mToRotation;
@@ -1694,6 +1693,9 @@
             final String mLastOrientationSource;
             final @ActivityInfo.ScreenOrientation int mSourceOrientation;
             final long mTimestamp = System.currentTimeMillis();
+            final int mHalfFoldSavedRotation;
+            final boolean mInHalfFoldTransition;
+            final DeviceStateController.FoldState mFoldState;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
                 mFromRotation = fromRotation;
@@ -1719,6 +1721,15 @@
                     mLastOrientationSource = null;
                     mSourceOrientation = SCREEN_ORIENTATION_UNSET;
                 }
+                if (dr.mFoldController != null) {
+                    mHalfFoldSavedRotation = dr.mFoldController.mHalfFoldSavedRotation;
+                    mInHalfFoldTransition = dr.mFoldController.mInHalfFoldTransition;
+                    mFoldState = dr.mFoldController.mFoldState;
+                } else {
+                    mHalfFoldSavedRotation = NO_FOLD_CONTROLLER;
+                    mInHalfFoldTransition = false;
+                    mFoldState = DeviceStateController.FoldState.UNKNOWN;
+                }
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -1735,6 +1746,12 @@
                 if (mNonDefaultRequestingTaskDisplayArea != null) {
                     pw.println(prefix + "  requestingTda=" + mNonDefaultRequestingTaskDisplayArea);
                 }
+                if (mHalfFoldSavedRotation != NO_FOLD_CONTROLLER) {
+                    pw.println(prefix + " halfFoldSavedRotation="
+                            + mHalfFoldSavedRotation
+                            + " mInHalfFoldTransition=" + mInHalfFoldTransition
+                            + " mFoldState=" + mFoldState);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
index 6f821b5..69fd00c 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowPolicyControllerHelper.java
@@ -153,13 +153,13 @@
     }
 
     /**
-     * @see DisplayWindowPolicyController#canShowTasksInRecents()
+     * @see DisplayWindowPolicyController#canShowTasksInHostDeviceRecents()
      */
-    public final boolean canShowTasksInRecents() {
+    public final boolean canShowTasksInHostDeviceRecents() {
         if (mDisplayWindowPolicyController == null) {
             return true;
         }
-        return mDisplayWindowPolicyController.canShowTasksInRecents();
+        return mDisplayWindowPolicyController.canShowTasksInHostDeviceRecents();
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 7bb036d..bd83794 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -191,7 +191,7 @@
             if (!r.attachedToProcess()) {
                 makeVisibleAndRestartIfNeeded(mStarting, mConfigChanges, isTop,
                         resumeTopActivity && isTop, r);
-            } else if (r.mVisibleRequested) {
+            } else if (r.isVisibleRequested()) {
                 // If this activity is already visible, then there is nothing to do here.
                 if (DEBUG_VISIBILITY) {
                     Slog.v(TAG_VISIBILITY, "Skipping: already visible at " + r);
@@ -244,7 +244,7 @@
         // invisible. If the app is already visible, it must have died while it was visible. In this
         // case, we'll show the dead window but will not restart the app. Otherwise we could end up
         // thrashing.
-        if (!isTop && r.mVisibleRequested && !r.isState(INITIALIZING)) {
+        if (!isTop && r.isVisibleRequested() && !r.isState(INITIALIZING)) {
             return;
         }
 
@@ -256,7 +256,7 @@
         if (r != starting) {
             r.startFreezingScreenLocked(configChanges);
         }
-        if (!r.mVisibleRequested || r.mLaunchTaskBehind) {
+        if (!r.isVisibleRequested() || r.mLaunchTaskBehind) {
             if (DEBUG_VISIBILITY) {
                 Slog.v(TAG_VISIBILITY, "Starting and making visible: " + r);
             }
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 1567fa7..e9badef 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -37,6 +37,7 @@
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
@@ -176,7 +177,7 @@
         final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
                 || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
         if (aodRemoved) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
         if (!keyguardChanged && !aodChanged) {
             setWakeTransitionReady();
@@ -533,24 +534,25 @@
 
     private final Runnable mResetWaitTransition = () -> {
         synchronized (mWindowManager.mGlobalLock) {
-            updateDeferWakeTransition(false /* waiting */);
+            updateDeferTransitionForAod(false /* waiting */);
         }
     };
 
-    void updateDeferWakeTransition(boolean waiting) {
+    // Defer transition until AOD dismissed.
+    void updateDeferTransitionForAod(boolean waiting) {
         if (waiting == mWaitingForWakeTransition) {
             return;
         }
-        if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+        if (!mService.getTransitionController().isCollecting()) {
             return;
         }
-        // if aod is showing, defer the wake transition until aod state changed.
+        // if AOD is showing, defer the wake transition until AOD state changed.
         if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
             mWaitingForWakeTransition = true;
             mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
             mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
         } else if (!waiting) {
-            // dismiss the deferring if the aod state change or cancel awake.
+            // dismiss the deferring if the AOD state change or cancel awake.
             mWaitingForWakeTransition = false;
             mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
             mWindowManager.mH.removeCallbacks(mResetWaitTransition);
@@ -648,6 +650,12 @@
             mRequestDismissKeyguard = lastDismissKeyguardActivity != mDismissingKeyguardActivity
                     && !mOccluded && !mKeyguardGoingAway
                     && mDismissingKeyguardActivity != null;
+            if (mOccluded && mKeyguardShowing && !display.isSleeping() && !top.fillsParent()
+                    && display.mWallpaperController.getWallpaperTarget() == null) {
+                // The occluding activity may be translucent or not fill screen. Then let wallpaper
+                // to check whether it should set itself as target to avoid blank background.
+                display.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+            }
 
             if (mTopTurnScreenOnActivity != lastTurnScreenOnActivity
                     && mTopTurnScreenOnActivity != null
@@ -657,10 +665,18 @@
                 mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false);
             }
 
+            boolean hasChange = false;
             if (lastOccluded != mOccluded) {
                 controller.handleOccludedChanged(mDisplayId, mTopOccludesActivity);
+                hasChange = true;
             } else if (!lastKeyguardGoingAway && mKeyguardGoingAway) {
                 controller.handleKeyguardGoingAwayChanged(display);
+                hasChange = true;
+            }
+            // Collect the participates for shell transition, so that transition won't happen too
+            // early since the transition was set ready.
+            if (hasChange && top != null && (mOccluded || mKeyguardGoingAway)) {
+                display.mTransitionController.collect(top);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bb4c482..bcea6f4 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -673,6 +673,12 @@
                 + getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
         pw.println(prefix + "  letterboxVerticalPositionMultiplier="
                 + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+        pw.println(prefix + "  letterboxPositionForHorizontalReachability="
+                + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+        pw.println(prefix + "  letterboxPositionForVerticalReachability="
+                + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                    mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
         pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
                 + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
         pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
diff --git a/services/core/java/com/android/server/wm/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index 1fc061b..c827062 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -1393,7 +1393,7 @@
         // Ignore the task if it is started on a display which is not allow to show its tasks on
         // Recents.
         if (task.getDisplayContent() != null
-                && !task.getDisplayContent().canShowTasksInRecents()) {
+                && !task.getDisplayContent().canShowTasksInHostDeviceRecents()) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index ffe3374..be90588 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -112,7 +112,7 @@
                 mTargetActivityType);
         ActivityRecord targetActivity = getTargetActivity(targetRootTask);
         if (targetActivity != null) {
-            if (targetActivity.mVisibleRequested || targetActivity.isTopRunningActivity()) {
+            if (targetActivity.isVisibleRequested() || targetActivity.isTopRunningActivity()) {
                 // The activity is ready.
                 return;
             }
@@ -195,7 +195,7 @@
 
         // Send launch hint if we are actually launching the target. If it's already visible
         // (shouldn't happen in general) we don't need to send it.
-        if (targetActivity == null || !targetActivity.mVisibleRequested) {
+        if (targetActivity == null || !targetActivity.isVisibleRequested()) {
             mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(
                     true /* forceSend */, targetActivity);
         }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 64cca87..749fa1f 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -418,7 +418,8 @@
             } else if (!isDocument && !taskIsDocument
                     && mIdealRecord == null && mCandidateRecord == null
                     && task.rootAffinity != null) {
-                if (task.rootAffinity.equals(mTaskAffinity)) {
+                if (task.rootAffinity.equals(mTaskAffinity)
+                        && task.isSameRequiredDisplayCategory(mInfo)) {
                     ProtoLog.d(WM_DEBUG_TASKS, "Found matching affinity candidate!");
                     // It is possible for multiple tasks to have the same root affinity especially
                     // if they are in separate root tasks. We save off this candidate, but keep
@@ -2099,6 +2100,7 @@
                     // from the client organizer, so the PIP activity can get the correct config
                     // from the Task, and prevent conflict with the PipTaskOrganizer.
                     tf.updateRequestedOverrideConfiguration(EMPTY);
+                    tf.updateRelativeEmbeddedBounds();
                 }
             });
             rootTask.setWindowingMode(WINDOWING_MODE_PINNED);
@@ -2619,7 +2621,7 @@
         final ArrayList<Task> addedTasks = new ArrayList<>();
         forAllActivities((r) -> {
             final Task task = r.getTask();
-            if (r.mVisibleRequested && r.mStartingData == null && !addedTasks.contains(task)) {
+            if (r.isVisibleRequested() && r.mStartingData == null && !addedTasks.contains(task)) {
                 r.showStartingWindow(true /*taskSwitch*/);
                 addedTasks.add(task);
             }
@@ -2644,7 +2646,7 @@
         forAllLeafTasks(task -> {
             final int oldRank = task.mLayerRank;
             final ActivityRecord r = task.topRunningActivityLocked();
-            if (r != null && r.mVisibleRequested) {
+            if (r != null && r.isVisibleRequested()) {
                 task.mLayerRank = ++mTmpTaskLayerRank;
             } else {
                 task.mLayerRank = Task.LAYER_RANK_INVISIBLE;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index a93767e..0be4191 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -491,6 +491,9 @@
     private int mForceHiddenFlags = 0;
     private boolean mForceTranslucent = false;
 
+    // The display category name for this task.
+    String mRequiredDisplayCategory;
+
     // TODO(b/160201781): Revisit double invocation issue in Task#removeChild.
     /**
      * Skip {@link ActivityTaskSupervisor#removeTask(Task, boolean, boolean, String)} execution if
@@ -1011,6 +1014,7 @@
             // affinity -- we don't want it changing after initially set, but the initially
             // set value may be null.
             rootAffinity = affinity;
+            mRequiredDisplayCategory = info.requiredDisplayCategory;
         }
         effectiveUid = info.applicationInfo.uid;
         mIsEffectivelySystemApp = info.applicationInfo.isSystemApp();
@@ -1591,15 +1595,22 @@
                 removeChild(r, reason);
             });
         } else {
+            final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>();
+            forAllActivities(r -> {
+                if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
+                    return;
+                }
+                finishingActivities.add(r);
+            });
+
             // Finish or destroy apps from the bottom to ensure that all the other activity have
             // been finished and the top task in another task gets resumed when a top activity is
             // removed. Otherwise, the next top activity could be started while the top activity
             // is removed, which is not necessary since the next top activity is on the same Task
             // and should also be removed.
-            forAllActivities((r) -> {
-                if (r.finishing || (excludingTaskOverlay && r.isTaskOverlay())) {
-                    return;
-                }
+            for (int i = finishingActivities.size() - 1; i >= 0; i--) {
+                final ActivityRecord r = finishingActivities.get(i);
+
                 // Prevent the transition from being executed too early if the top activity is
                 // resumed but the mVisibleRequested of any other activity is true, the transition
                 // should wait until next activity resumed.
@@ -1609,7 +1620,7 @@
                 } else {
                     r.destroyIfPossible(reason);
                 }
-            }, false /* traverseTopToBottom */);
+            }
         }
     }
 
@@ -1914,7 +1925,6 @@
             mTaskSupervisor.scheduleUpdateMultiWindowMode(this);
         }
 
-        final int newWinMode = getWindowingMode();
         if (shouldStartChangeTransition(prevWinMode, mTmpPrevBounds)) {
             initializeChangeTransition(mTmpPrevBounds);
         }
@@ -1928,16 +1938,15 @@
             }
         }
 
-        if (pipChanging && wasInPictureInPicture) {
+        if (pipChanging && wasInPictureInPicture
+                && !mTransitionController.isShellTransitionsEnabled()) {
             // If the top activity is changing from PiP to fullscreen with fixed rotation,
             // clear the crop and rotation matrix of task because fixed rotation will handle
             // the transformation on activity level. This also avoids flickering caused by the
             // latency of fullscreen task organizer configuring the surface.
             final ActivityRecord r = topRunningActivity();
             if (r != null && mDisplayContent.isFixedRotationLaunchingApp(r)) {
-                getSyncTransaction().setWindowCrop(mSurfaceControl, null)
-                        .setCornerRadius(mSurfaceControl, 0f)
-                        .setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9]);
+                resetSurfaceControlTransforms();
             }
         }
 
@@ -2167,7 +2176,7 @@
     }
 
     private boolean shouldStartChangeTransition(int prevWinMode, @NonNull Rect prevBounds) {
-        if (!isLeafTask() || !canStartChangeTransition()) {
+        if (!(isLeafTask() || mCreatedByOrganizer) || !canStartChangeTransition()) {
             return false;
         }
         final int newWinMode = getWindowingMode();
@@ -2455,7 +2464,7 @@
 
         final String myReason = reason + " adjustFocusToNextFocusableTask";
         final ActivityRecord top = focusableTask.topRunningActivity();
-        if (focusableTask.isActivityTypeHome() && (top == null || !top.mVisibleRequested)) {
+        if (focusableTask.isActivityTypeHome() && (top == null || !top.isVisibleRequested())) {
             // If we will be focusing on the root home task next and its current top activity isn't
             // visible, then use the move the root home task to top to make the activity visible.
             focusableTask.getDisplayArea().moveHomeActivityToTop(myReason);
@@ -2768,7 +2777,7 @@
      */
     private static void getMaxVisibleBounds(ActivityRecord token, Rect out, boolean[] foundTop) {
         // skip hidden (or about to hide) apps
-        if (token.mIsExiting || !token.isClientVisible() || !token.mVisibleRequested) {
+        if (token.mIsExiting || !token.isClientVisible() || !token.isVisibleRequested()) {
             return;
         }
         final WindowState win = token.findMainWindow();
@@ -3077,7 +3086,7 @@
      * this activity.
      */
     ActivityRecord getTopVisibleActivity() {
-        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.mVisibleRequested);
+        return getActivity((r) -> !r.mIsExiting && r.isClientVisible() && r.isVisibleRequested());
     }
 
     /**
@@ -3529,13 +3538,10 @@
     }
 
     @Override
-    void onActivityVisibleRequestedChanged() {
-        final boolean prevVisibleRequested = mVisibleRequested;
-        // mVisibleRequested is updated in super method.
-        super.onActivityVisibleRequestedChanged();
-        if (prevVisibleRequested != mVisibleRequested) {
-            sendTaskFragmentParentInfoChangedIfNeeded();
-        }
+    protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
+        if (!super.onChildVisibleRequestedChanged(child)) return false;
+        sendTaskFragmentParentInfoChangedIfNeeded();
+        return true;
     }
 
     void sendTaskFragmentParentInfoChangedIfNeeded() {
@@ -5726,7 +5732,7 @@
         forAllActivities(r -> {
             if (!r.info.packageName.equals(packageName)) return;
             r.forceNewConfig = true;
-            if (starting != null && r == starting && r.mVisibleRequested) {
+            if (starting != null && r == starting && r.isVisibleRequested()) {
                 r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
             }
         });
@@ -6076,6 +6082,15 @@
         }
     }
 
+    /**
+     * Return true if the activityInfo has the same requiredDisplayCategory as this task.
+     */
+    boolean isSameRequiredDisplayCategory(@NonNull ActivityInfo info) {
+        return mRequiredDisplayCategory != null && mRequiredDisplayCategory.equals(
+                info.requiredDisplayCategory)
+                || (mRequiredDisplayCategory == null && info.requiredDisplayCategory == null);
+    }
+
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
             @WindowTraceLogLevel int logLevel) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d69d949..3f2f02b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -302,6 +302,14 @@
     private final IBinder mFragmentToken;
 
     /**
+     * The bounds of the embedded TaskFragment relative to the parent Task.
+     * {@code null} if it is not {@link #mIsEmbedded}
+     * TODO(b/261785978) cleanup with legacy app transition
+     */
+    @Nullable
+    private final Rect mRelativeEmbeddedBounds;
+
+    /**
      * Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
      * configuration change.
      */
@@ -316,9 +324,6 @@
 
     final Point mLastSurfaceSize = new Point();
 
-    /** The latest updated value when there's a child {@link #onActivityVisibleRequestedChanged} */
-    boolean mVisibleRequested;
-
     private final Rect mTmpBounds = new Rect();
     private final Rect mTmpFullBounds = new Rect();
     /** For calculating screenWidthDp and screenWidthDp, i.e. the area without the system bars. */
@@ -346,7 +351,7 @@
         }
 
         void process(ActivityRecord start, boolean preserveWindow) {
-            if (start == null || !start.mVisibleRequested) {
+            if (start == null || !start.isVisibleRequested()) {
                 return;
             }
             reset(preserveWindow);
@@ -383,6 +388,7 @@
         mRootWindowContainer = mAtmService.mRootWindowContainer;
         mCreatedByOrganizer = createdByOrganizer;
         mIsEmbedded = isEmbedded;
+        mRelativeEmbeddedBounds = isEmbedded ? new Rect() : null;
         mTaskFragmentOrganizerController =
                 mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController;
         mFragmentToken = fragmentToken;
@@ -1356,7 +1362,7 @@
         if (next.attachedToProcess()) {
             if (DEBUG_SWITCH) {
                 Slog.v(TAG_SWITCH, "Resume running: " + next + " stopped=" + next.stopped
-                        + " visibleRequested=" + next.mVisibleRequested);
+                        + " visibleRequested=" + next.isVisibleRequested());
             }
 
             // If the previous activity is translucent, force a visibility update of
@@ -1370,7 +1376,7 @@
                     || mLastPausedActivity != null && !mLastPausedActivity.occludesParent();
 
             // This activity is now becoming visible.
-            if (!next.mVisibleRequested || next.stopped || lastActivityTranslucent) {
+            if (!next.isVisibleRequested() || next.stopped || lastActivityTranslucent) {
                 next.app.addToPendingTop();
                 next.setVisibility(true);
             }
@@ -1421,7 +1427,7 @@
                     // Do over!
                     mTaskSupervisor.scheduleResumeTopActivities();
                 }
-                if (!next.mVisibleRequested || next.stopped) {
+                if (!next.isVisibleRequested() || next.stopped) {
                     next.setVisibility(true);
                 }
                 next.completeResumeLocked();
@@ -1732,7 +1738,7 @@
             } else if (prev.attachedToProcess()) {
                 ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
                                 + "wasStopping=%b visibleRequested=%b",  prev,  wasStopping,
-                        prev.mVisibleRequested);
+                        prev.isVisibleRequested());
                 if (prev.deferRelaunchUntilPaused) {
                     // Complete the deferred relaunch that was waiting for pause to complete.
                     ProtoLog.v(WM_DEBUG_STATES, "Re-launching after pause: %s", prev);
@@ -1742,7 +1748,7 @@
                     // We can't clobber it, because the stop confirmation will not be handled.
                     // We don't need to schedule another stop, we only need to let it happen.
                     prev.setState(STOPPING, "completePausedLocked");
-                } else if (!prev.mVisibleRequested || shouldSleepOrShutDownActivities()) {
+                } else if (!prev.isVisibleRequested() || shouldSleepOrShutDownActivities()) {
                     // Clear out any deferred client hide we might currently have.
                     prev.setDeferHidingClient(false);
                     // If we were visible then resumeTopActivities will release resources before
@@ -2410,16 +2416,53 @@
         }
     }
 
-    /** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
-    boolean shouldStartChangeTransition(Rect startBounds) {
+    /**
+     * Gets the relative bounds of this embedded TaskFragment. This should only be called on
+     * embedded TaskFragment.
+     */
+    @NonNull
+    Rect getRelativeEmbeddedBounds() {
+        if (mRelativeEmbeddedBounds == null) {
+            throw new IllegalStateException("The TaskFragment is not embedded");
+        }
+        return mRelativeEmbeddedBounds;
+    }
+
+    /**
+     * Updates the record of the relative bounds of this embedded TaskFragment. This should only be
+     * called when the embedded TaskFragment's override bounds are changed.
+     * Returns {@code true} if the bounds is changed.
+     */
+    void updateRelativeEmbeddedBounds() {
+        // We only record the override bounds, which means it will not be changed when it is filling
+        // Task, and resize with the parent.
+        getRequestedOverrideBounds(mTmpBounds);
+        getRelativePosition(mTmpPoint);
+        mTmpBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
+        mRelativeEmbeddedBounds.set(mTmpBounds);
+    }
+
+    /**
+     * Updates the record of relative bounds of this embedded TaskFragment, and checks whether we
+     * should prepare a transition for the bounds change.
+     */
+    boolean shouldStartChangeTransition(@NonNull Rect absStartBounds,
+            @NonNull Rect relStartBounds) {
         if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
             return false;
         }
 
-        // Only take snapshot if the bounds are resized.
-        final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
-        return endBounds.width() != startBounds.width()
-                || endBounds.height() != startBounds.height();
+        if (mTransitionController.isShellTransitionsEnabled()) {
+            // For Shell transition, the change will be collected anyway, so only take snapshot when
+            // the bounds are resized.
+            final Rect endBounds = getConfiguration().windowConfiguration.getBounds();
+            return endBounds.width() != absStartBounds.width()
+                    || endBounds.height() != absStartBounds.height();
+        } else {
+            // For legacy transition, we need to trigger a change transition as long as the bounds
+            // is changed, even if it is not resized.
+            return !relStartBounds.equals(mRelativeEmbeddedBounds);
+        }
     }
 
     /** Records the starting bounds of the closing organized TaskFragment. */
@@ -2787,22 +2830,6 @@
         return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
     }
 
-    void onActivityVisibleRequestedChanged() {
-        final boolean isVisibleRequested = isVisibleRequested();
-        if (mVisibleRequested == isVisibleRequested) {
-            return;
-        }
-        mVisibleRequested = isVisibleRequested;
-        final WindowContainer<?> parent = getParent();
-        if (parent == null) {
-            return;
-        }
-        final TaskFragment parentTf = parent.asTaskFragment();
-        if (parentTf != null) {
-            parentTf.onActivityVisibleRequestedChanged();
-        }
-    }
-
     @Nullable
     @Override
     TaskFragment getTaskFragment(Predicate<TaskFragment> callback) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 2b11d54..b7f3cb4 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -636,11 +636,11 @@
                 // No need to clip the display in case seeing the clipped content when during the
                 // display rotation. No need to clip activities because they rely on clipping on
                 // task layers.
-                if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
+                if (target.asTaskFragment() == null) {
                     t.setCrop(targetLeash, null /* crop */);
                 } else {
-                    // Crop to the requested bounds.
-                    final Rect clipRect = target.getRequestedOverrideBounds();
+                    // Crop to the resolved override bounds.
+                    final Rect clipRect = target.getResolvedOverrideBounds();
                     t.setWindowCrop(targetLeash, clipRect.width(), clipRect.height());
                 }
                 t.setCornerRadius(targetLeash, 0);
@@ -992,7 +992,7 @@
         // show here in the same way that we manually hide in finishTransaction.
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-            if (ar == null || !ar.mVisibleRequested) continue;
+            if (ar == null || !ar.isVisibleRequested()) continue;
             transaction.show(ar.getSurfaceControl());
 
             // Also manually show any non-reported parents. This is necessary in a few cases
@@ -1246,7 +1246,7 @@
         ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
-            if (r == null || !r.mVisibleRequested) continue;
+            if (r == null || !r.isVisibleRequested()) continue;
             int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
             // At this point, r is "ready", but if it's not "ALL ready" then it is probably only
             // ready due to starting-window.
@@ -1617,8 +1617,11 @@
 
         WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
 
-        // make leash based on highest (z-order) direct child of ancestor with a participant.
-        WindowContainer leashReference = sortedTargets.get(0);
+        // Make leash based on highest (z-order) direct child of ancestor with a participant.
+        // TODO(b/261418859): Handle the case when the target contains window containers which
+        // belong to a different display. As a workaround we use topApp, from which wallpaper
+        // window container is removed, instead of sortedTargets here.
+        WindowContainer leashReference = topApp;
         while (leashReference.getParent() != ancestor) {
             leashReference = leashReference.getParent();
         }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 99527b1..cf541fc 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -38,6 +38,7 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.window.ITransitionMetricsReporter;
 import android.window.ITransitionPlayer;
@@ -116,6 +117,8 @@
      */
     boolean mBuildingFinishLayers = false;
 
+    private final SurfaceControl.Transaction mWakeT = new SurfaceControl.Transaction();
+
     TransitionController(ActivityTaskManagerService atm,
             TaskSnapshotController taskSnapshotController,
             TransitionTracer transitionTracer) {
@@ -619,8 +622,16 @@
     private void updateRunningRemoteAnimation(Transition transition, boolean isPlaying) {
         if (mTransitionPlayerProc == null) return;
         if (isPlaying) {
+            mWakeT.setEarlyWakeupStart();
+            mWakeT.apply();
+            // Usually transitions put quite a load onto the system already (with all the things
+            // happening in app), so pause task snapshot persisting to not increase the load.
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
             mTransitionPlayerProc.setRunningRemoteAnimation(true);
         } else if (mPlayingTransitions.isEmpty()) {
+            mWakeT.setEarlyWakeupEnd();
+            mWakeT.apply();
+            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
             mTransitionPlayerProc.setRunningRemoteAnimation(false);
             mRemotePlayer.clear();
             return;
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3b30dd1..5de143d9 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -725,9 +725,9 @@
         }
 
         final boolean newTargetHidden = wallpaperTarget.mActivityRecord != null
-                && !wallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !wallpaperTarget.mActivityRecord.isVisibleRequested();
         final boolean oldTargetHidden = prevWallpaperTarget.mActivityRecord != null
-                && !prevWallpaperTarget.mActivityRecord.mVisibleRequested;
+                && !prevWallpaperTarget.mActivityRecord.isVisibleRequested();
 
         ProtoLog.v(WM_DEBUG_WALLPAPER, "Animating wallpapers: "
                 + "old: %s hidden=%b new: %s hidden=%b",
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6ee30bb..87f4ad4 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -41,8 +41,6 @@
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "WallpaperWindowToken" : TAG_WM;
 
-    private boolean mVisibleRequested = false;
-
     WallpaperWindowToken(WindowManagerService service, IBinder token, boolean explicit,
             DisplayContent dc, boolean ownerCanManageAppTokens) {
         this(service, token, explicit, dc, ownerCanManageAppTokens, null /* options */);
@@ -136,7 +134,7 @@
                 recentsAnimationController.linkFixedRotationTransformIfNeeded(this);
             } else if ((wallpaperTarget.mActivityRecord == null
                     // Ignore invisible activity because it may be moving to background.
-                    || wallpaperTarget.mActivityRecord.mVisibleRequested)
+                    || wallpaperTarget.mActivityRecord.isVisibleRequested())
                     && wallpaperTarget.mToken.hasFixedRotationTransform()) {
                 // If the wallpaper target has a fixed rotation, we want the wallpaper to follow its
                 // rotation
@@ -221,15 +219,17 @@
         return false;
     }
 
-    void setVisibleRequested(boolean visible) {
-        if (mVisibleRequested == visible) return;
-        mVisibleRequested = visible;
+    @Override
+    protected boolean setVisibleRequested(boolean visible) {
+        if (!super.setVisibleRequested(visible)) return false;
         setInsetsFrozen(!visible);
+        return true;
     }
 
     @Override
-    boolean isVisibleRequested() {
-        return mVisibleRequested;
+    protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
+        // Wallpaper manages visibleRequested directly (it's not determined by children)
+        return false;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 6e61071..0c2cc43 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -315,6 +315,13 @@
     private boolean mIsFocusable = true;
 
     /**
+     * This indicates whether this window is visible by policy. This can precede physical
+     * visibility ({@link #isVisible} - whether it has a surface showing on the screen) in
+     * cases where an animation is on-going.
+     */
+    protected boolean mVisibleRequested;
+
+    /**
      * Used as a unique, cross-process identifier for this Container. It also serves a minimal
      * interface to other processes.
      */
@@ -772,6 +779,7 @@
             parent.mTreeWeight += child.mTreeWeight;
             parent = parent.getParent();
         }
+        onChildVisibleRequestedChanged(child);
         onChildPositionChanged(child);
     }
 
@@ -800,6 +808,7 @@
             parent.mTreeWeight -= child.mTreeWeight;
             parent = parent.getParent();
         }
+        onChildVisibleRequestedChanged(null);
         onChildPositionChanged(child);
     }
 
@@ -1288,13 +1297,41 @@
      * the transition is finished.
      */
     boolean isVisibleRequested() {
-        for (int i = mChildren.size() - 1; i >= 0; --i) {
-            final WindowContainer child = mChildren.get(i);
-            if (child.isVisibleRequested()) {
-                return true;
+        return mVisibleRequested;
+    }
+
+    /** @return `true` if visibleRequested changed. */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    boolean setVisibleRequested(boolean visible) {
+        if (mVisibleRequested == visible) return false;
+        mVisibleRequested = visible;
+        final WindowContainer parent = getParent();
+        if (parent != null) {
+            parent.onChildVisibleRequestedChanged(this);
+        }
+        return true;
+    }
+
+    /**
+     * @param child The changed or added child. `null` if a child was removed.
+     * @return `true` if visibleRequested changed.
+     */
+    protected boolean onChildVisibleRequestedChanged(@Nullable WindowContainer child) {
+        final boolean childVisReq = child != null && child.isVisibleRequested();
+        boolean newVisReq = mVisibleRequested;
+        if (childVisReq && !mVisibleRequested) {
+            newVisReq = true;
+        } else if (!childVisReq && mVisibleRequested) {
+            newVisReq = false;
+            for (int i = mChildren.size() - 1; i >= 0; --i) {
+                final WindowContainer wc = mChildren.get(i);
+                if (wc != child && wc.isVisibleRequested()) {
+                    newVisReq = true;
+                    break;
+                }
             }
         }
-        return false;
+        return setVisibleRequested(newVisReq);
     }
 
     /**
@@ -3025,13 +3062,16 @@
                     // When there are more than one changing containers, it may leave part of the
                     // screen empty. Show background color to cover that.
                     showBackdrop = getDisplayContent().mChangingContainers.size() > 1;
+                    backdropColor = appTransition.getNextAppTransitionBackgroundColor();
                 } else {
                     // Check whether the app has requested to show backdrop for open/close
                     // transition.
                     final Animation a = appTransition.getNextAppRequestedAnimation(enter);
-                    showBackdrop = a != null && a.getShowBackdrop();
+                    if (a != null) {
+                        showBackdrop = a.getShowBackdrop();
+                        backdropColor = a.getBackdropColor();
+                    }
                 }
-                backdropColor = appTransition.getNextAppTransitionBackgroundColor();
             }
             final Rect localBounds = new Rect(mTmpRect);
             localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 2838304..46a30fb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -1221,6 +1221,12 @@
             pw.println("Default position for vertical reachability: "
                     + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
                     mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+            pw.println("Current position for horizontal reachability:"
+                    + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+            pw.println("Current position for vertical reachability:"
+                    + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+                        mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
             pw.println("Is education enabled: "
                     + mLetterboxConfiguration.getIsEducationEnabled());
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 738adc3..7241172 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -148,7 +148,8 @@
     @VisibleForTesting
     final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
 
-    private final Rect mTmpBounds = new Rect();
+    private final Rect mTmpBounds0 = new Rect();
+    private final Rect mTmpBounds1 = new Rect();
 
     WindowOrganizerController(ActivityTaskManagerService atm) {
         mService = atm;
@@ -797,14 +798,15 @@
         // When the TaskFragment is resized, we may want to create a change transition for it, for
         // which we want to defer the surface update until we determine whether or not to start
         // change transition.
-        mTmpBounds.set(taskFragment.getBounds());
+        mTmpBounds0.set(taskFragment.getBounds());
+        mTmpBounds1.set(taskFragment.getRelativeEmbeddedBounds());
         taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
         final int effects = applyChanges(taskFragment, c, errorCallbackToken);
-        if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
-            taskFragment.initializeChangeTransition(mTmpBounds);
+        taskFragment.updateRelativeEmbeddedBounds();
+        if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
+            taskFragment.initializeChangeTransition(mTmpBounds0);
         }
         taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
-        mTmpBounds.set(0, 0, 0, 0);
         return effects;
     }
 
@@ -1907,7 +1909,18 @@
         // actions.
         taskFragment.setTaskFragmentOrganizer(creationParams.getOrganizer(),
                 ownerActivity.getUid(), ownerActivity.info.processName);
-        ownerTask.addChild(taskFragment, POSITION_TOP);
+        final int position;
+        if (creationParams.getPairedPrimaryFragmentToken() != null) {
+            // When there is a paired primary TaskFragment, we want to place the new TaskFragment
+            // right above the paired one to make sure there is no other window in between.
+            final TaskFragment pairedPrimaryTaskFragment = getTaskFragment(
+                    creationParams.getPairedPrimaryFragmentToken());
+            final int pairedPosition = ownerTask.mChildren.indexOf(pairedPrimaryTaskFragment);
+            position = pairedPosition != -1 ? pairedPosition + 1 : POSITION_TOP;
+        } else {
+            position = POSITION_TOP;
+        }
+        ownerTask.addChild(taskFragment, position);
         taskFragment.setWindowingMode(creationParams.getWindowingMode());
         taskFragment.setBounds(creationParams.getInitialBounds());
         mLaunchTaskFragments.put(creationParams.getFragmentToken(), taskFragment);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 8f63e93..d2cd8f8 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -765,7 +765,7 @@
         // - no longer visible OR
         // - not focusable (in PiP mode for instance)
         if (topDisplay == null
-                || !mPreQTopResumedActivity.mVisibleRequested
+                || !mPreQTopResumedActivity.isVisibleRequested()
                 || !mPreQTopResumedActivity.isFocusable()) {
             canUpdate = true;
         }
@@ -874,7 +874,7 @@
             // to those activities that are part of the package whose app-specific settings changed
             if (packageName.equals(r.packageName)
                     && r.applyAppSpecificConfig(nightMode, localesOverride)
-                    && r.mVisibleRequested) {
+                    && r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
         }
@@ -956,7 +956,7 @@
             }
             // Don't consider any activities that are currently not in a state where they
             // can be destroyed.
-            if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
+            if (r.isVisibleRequested() || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
                     || r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
                 if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
                 continue;
@@ -1002,7 +1002,7 @@
                 final int displayId = r.getDisplayId();
                 final Context c = root.getDisplayUiContext(displayId);
 
-                if (c != null && r.mVisibleRequested && !displayContexts.contains(c)) {
+                if (c != null && r.isVisibleRequested() && !displayContexts.contains(c)) {
                     displayContexts.add(c);
                 }
             }
@@ -1070,7 +1070,7 @@
             if (task != null && task.mLayerRank != Task.LAYER_RANK_INVISIBLE) {
                 stateFlags |= ACTIVITY_STATE_FLAG_HAS_ACTIVITY_IN_VISIBLE_TASK;
             }
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 if (r.isState(RESUMED)) {
                     stateFlags |= ACTIVITY_STATE_FLAG_HAS_RESUMED;
                 }
@@ -1282,7 +1282,7 @@
         }
         for (int i = activities.size() - 1; i >= 0; i--) {
             final ActivityRecord r = activities.get(i);
-            if (r.mVisibleRequested || r.isVisible()) {
+            if (r.isVisibleRequested() || r.isVisible()) {
                 // While an activity launches a new activity, it's possible that the old activity
                 // is already requested to be hidden (mVisibleRequested=false), but this visibility
                 // is not yet committed, so isVisible()=true.
@@ -1503,7 +1503,7 @@
             Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration());
             overrideConfig.assetsSeq = assetSeq;
             r.onRequestedOverrideConfigurationChanged(overrideConfig);
-            if (r.mVisibleRequested) {
+            if (r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0, true);
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 73759d3..1b7bd9e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1957,7 +1957,7 @@
      */
     // TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
     boolean isWinVisibleLw() {
-        return (mActivityRecord == null || mActivityRecord.mVisibleRequested
+        return (mActivityRecord == null || mActivityRecord.isVisibleRequested()
                 || mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
     }
 
@@ -1994,7 +1994,7 @@
         final ActivityRecord atoken = mActivityRecord;
         return (mHasSurface || (!mRelayoutCalled && mViewVisibility == View.VISIBLE))
                 && isVisibleByPolicy() && !isParentWindowHidden()
-                && (atoken == null || atoken.mVisibleRequested)
+                && (atoken == null || atoken.isVisibleRequested())
                 && !mAnimatingExit && !mDestroying;
     }
 
@@ -2090,7 +2090,10 @@
         } else {
             final Task task = getTask();
             final boolean canFromTask = task != null && task.canAffectSystemUiFlags();
-            return canFromTask && mActivityRecord.isVisible();
+            return canFromTask && mActivityRecord.isVisible()
+            // Do not let snapshot window control the bar
+                    && (mAttrs.type != TYPE_APPLICATION_STARTING
+                    || !(mStartingData instanceof SnapshotStartingData));
         }
     }
 
@@ -2101,7 +2104,7 @@
     boolean isDisplayed() {
         final ActivityRecord atoken = mActivityRecord;
         return isDrawn() && isVisibleByPolicy()
-                && ((!isParentWindowHidden() && (atoken == null || atoken.mVisibleRequested))
+                && ((!isParentWindowHidden() && (atoken == null || atoken.isVisibleRequested()))
                         || isAnimating(TRANSITION | PARENTS));
     }
 
@@ -2123,7 +2126,7 @@
                 // a layout since they can request relayout when client visibility is false.
                 // TODO (b/157682066) investigate if we can clean up isVisible
                 || (atoken == null && !(wouldBeVisibleIfPolicyIgnored() && isVisibleByPolicy()))
-                || (atoken != null && !atoken.mVisibleRequested)
+                || (atoken != null && !atoken.isVisibleRequested())
                 || isParentWindowGoneForLayout()
                 || (mAnimatingExit && !isAnimatingLw())
                 || mDestroying;
@@ -2170,7 +2173,7 @@
             return;
         }
         if (mActivityRecord != null) {
-            if (!mActivityRecord.mVisibleRequested) return;
+            if (!mActivityRecord.isVisibleRequested()) return;
             if (mActivityRecord.allDrawn) {
                 // The allDrawn of activity is reset when the visibility is changed to visible, so
                 // the content should be ready if allDrawn is set.
@@ -2743,7 +2746,7 @@
                         + " exiting=" + mAnimatingExit + " destroying=" + mDestroying);
                 if (mActivityRecord != null) {
                     Slog.i(TAG_WM, "  mActivityRecord.visibleRequested="
-                            + mActivityRecord.mVisibleRequested);
+                            + mActivityRecord.isVisibleRequested());
                 }
             }
         }
@@ -3219,7 +3222,7 @@
         }
 
         return !mActivityRecord.getTask().getRootTask().shouldIgnoreInput()
-                && mActivityRecord.mVisibleRequested;
+                && mActivityRecord.isVisibleRequested();
     }
 
     /**
@@ -3875,7 +3878,7 @@
         // the client erroneously accepting a configuration that would have otherwise caused an
         // activity restart. We instead hand back the last reported {@link MergedConfiguration}.
         if (useLatestConfig || (relayoutVisible && (mActivityRecord == null
-                || mActivityRecord.mVisibleRequested))) {
+                || mActivityRecord.isVisibleRequested()))) {
             final Configuration globalConfig = getProcessGlobalConfiguration();
             final Configuration overrideConfig = getMergedOverrideConfiguration();
             outMergedConfiguration.setConfiguration(globalConfig, overrideConfig);
@@ -4734,7 +4737,7 @@
                     + " during animation: policyVis=" + isVisibleByPolicy()
                     + " parentHidden=" + isParentWindowHidden()
                     + " tok.visibleRequested="
-                    + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                    + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                     + " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
                     + " animating=" + isAnimating(TRANSITION | PARENTS)
                     + " tok animating="
@@ -5195,7 +5198,7 @@
                         + " pv=" + isVisibleByPolicy()
                         + " mDrawState=" + mWinAnimator.mDrawState
                         + " ph=" + isParentWindowHidden()
-                        + " th=" + (mActivityRecord != null && mActivityRecord.mVisibleRequested)
+                        + " th=" + (mActivityRecord != null && mActivityRecord.isVisibleRequested())
                         + " a=" + isAnimating(TRANSITION | PARENTS));
             }
         }
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index f2527b6..c6b7898 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -582,9 +582,7 @@
                 .setCallsite("WindowToken.getOrCreateFixedRotationLeash")
                 .build();
         t.setPosition(leash, mLastSurfacePosition.x, mLastSurfacePosition.y);
-        t.show(leash);
         t.reparent(getSurfaceControl(), leash);
-        t.setAlpha(getSurfaceControl(), 1.f);
         mFixedRotationTransformLeash = leash;
         updateSurfaceRotation(t, rotation, mFixedRotationTransformLeash);
         return mFixedRotationTransformLeash;
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index e661688..07819b9 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -71,6 +71,7 @@
         "com_android_server_PersistentDataBlockService.cpp",
         "com_android_server_am_LowMemDetector.cpp",
         "com_android_server_pm_PackageManagerShellCommandDataLoader.cpp",
+        "com_android_server_pm_Settings.cpp",
         "com_android_server_sensor_SensorService.cpp",
         "com_android_server_wm_TaskFpsCallbackController.cpp",
         "onload.cpp",
@@ -152,6 +153,7 @@
         "libpsi",
         "libdataloader",
         "libincfs",
+        "liblz4",
         "android.hardware.audio.common@2.0",
         "android.media.audio.common.types-V1-ndk",
         "android.hardware.broadcastradio@1.0",
@@ -232,3 +234,26 @@
         "com_android_server_app_GameManagerService.cpp",
     ],
 }
+
+// Settings JNI library for unit tests.
+cc_library_shared {
+    name: "libservices.core.settings.testonly",
+    defaults: ["libservices.core-libs"],
+
+    cpp_std: "c++2a",
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+        "-Wthread-safety",
+    ],
+
+    srcs: [
+        "com_android_server_pm_Settings.cpp",
+        "onload_settings.cpp",
+    ],
+
+    header_libs: [
+        "bionic_libc_platform_headers",
+    ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 969056e..145e088 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1578,6 +1578,12 @@
     return vec;
 }
 
+static void nativeAddKeyRemapping(JNIEnv* env, jobject nativeImplObj, jint deviceId,
+                                  jint fromKeyCode, jint toKeyCode) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    im->getInputManager()->getReader().addKeyRemapping(deviceId, fromKeyCode, toKeyCode);
+}
+
 static jboolean nativeHasKeys(JNIEnv* env, jobject nativeImplObj, jint deviceId, jint sourceMask,
                               jintArray keyCodes, jbooleanArray outFlags) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2360,6 +2366,7 @@
         {"getScanCodeState", "(III)I", (void*)nativeGetScanCodeState},
         {"getKeyCodeState", "(III)I", (void*)nativeGetKeyCodeState},
         {"getSwitchState", "(III)I", (void*)nativeGetSwitchState},
+        {"addKeyRemapping", "(III)V", (void*)nativeAddKeyRemapping},
         {"hasKeys", "(II[I[Z)Z", (void*)nativeHasKeys},
         {"getKeyCodeForKeyLocation", "(II)I", (void*)nativeGetKeyCodeForKeyLocation},
         {"createInputChannel", "(Ljava/lang/String;)Landroid/view/InputChannel;",
diff --git a/services/core/jni/com_android_server_pm_Settings.cpp b/services/core/jni/com_android_server_pm_Settings.cpp
new file mode 100644
index 0000000..9633a11
--- /dev/null
+++ b/services/core/jni/com_android_server_pm_Settings.cpp
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+#define ATRACE_TAG ATRACE_TAG_ADB
+#define LOG_TAG "Settings-jni"
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/no_destructor.h>
+#include <core_jni_helpers.h>
+#include <lz4frame.h>
+#include <nativehelper/JNIHelp.h>
+
+#include <vector>
+
+namespace android {
+
+namespace {
+
+struct LZ4FCContextDeleter {
+    void operator()(LZ4F_cctx* cctx) { LZ4F_freeCompressionContext(cctx); }
+};
+
+static constexpr int LZ4_BUFFER_SIZE = 64 * 1024;
+
+static bool writeToFile(std::vector<char>& outBuffer, int fdOut) {
+    if (!android::base::WriteFully(fdOut, outBuffer.data(), outBuffer.size())) {
+        PLOG(ERROR) << "Error to write to output file";
+        return false;
+    }
+    outBuffer.clear();
+    return true;
+}
+
+static bool compressAndWriteLz4(LZ4F_cctx* context, std::vector<char>& inBuffer,
+                                std::vector<char>& outBuffer, int fdOut) {
+    auto inSize = inBuffer.size();
+    if (inSize > 0) {
+        auto prvSize = outBuffer.size();
+        auto outSize = LZ4F_compressBound(inSize, nullptr);
+        outBuffer.resize(prvSize + outSize);
+        auto rc = LZ4F_compressUpdate(context, outBuffer.data() + prvSize, outSize, inBuffer.data(),
+                                      inSize, nullptr);
+        if (LZ4F_isError(rc)) {
+            LOG(ERROR) << "LZ4F_compressUpdate failed: " << LZ4F_getErrorName(rc);
+            return false;
+        }
+        outBuffer.resize(prvSize + rc);
+    }
+
+    if (outBuffer.size() > LZ4_BUFFER_SIZE) {
+        return writeToFile(outBuffer, fdOut);
+    }
+
+    return true;
+}
+
+static jboolean nativeCompressLz4(JNIEnv* env, jclass klass, jint fdIn, jint fdOut) {
+    LZ4F_cctx* cctx;
+    if (LZ4F_createCompressionContext(&cctx, LZ4F_VERSION) != 0) {
+        LOG(ERROR) << "Failed to initialize LZ4 compression context.";
+        return false;
+    }
+    std::unique_ptr<LZ4F_cctx, LZ4FCContextDeleter> context(cctx);
+
+    std::vector<char> inBuffer, outBuffer;
+    inBuffer.reserve(LZ4_BUFFER_SIZE);
+    outBuffer.reserve(2 * LZ4_BUFFER_SIZE);
+
+    LZ4F_preferences_t prefs;
+
+    memset(&prefs, 0, sizeof(prefs));
+
+    // Set compression parameters.
+    prefs.autoFlush = 0;
+    prefs.compressionLevel = 0;
+    prefs.frameInfo.blockMode = LZ4F_blockLinked;
+    prefs.frameInfo.blockSizeID = LZ4F_default;
+    prefs.frameInfo.blockChecksumFlag = LZ4F_noBlockChecksum;
+    prefs.frameInfo.contentChecksumFlag = LZ4F_contentChecksumEnabled;
+    prefs.favorDecSpeed = 0;
+
+    struct stat sb;
+    if (fstat(fdIn, &sb) == -1) {
+        PLOG(ERROR) << "Failed to obtain input file size.";
+        return false;
+    }
+    prefs.frameInfo.contentSize = sb.st_size;
+
+    // Write header first.
+    outBuffer.resize(LZ4F_HEADER_SIZE_MAX);
+    auto rc = LZ4F_compressBegin(context.get(), outBuffer.data(), outBuffer.size(), &prefs);
+    if (LZ4F_isError(rc)) {
+        LOG(ERROR) << "LZ4F_compressBegin failed: " << LZ4F_getErrorName(rc);
+        return false;
+    }
+    outBuffer.resize(rc);
+
+    bool eof = false;
+    while (!eof) {
+        constexpr auto capacity = LZ4_BUFFER_SIZE;
+        inBuffer.resize(capacity);
+        auto read = TEMP_FAILURE_RETRY(::read(fdIn, inBuffer.data(), inBuffer.size()));
+        if (read < 0) {
+            PLOG(ERROR) << "Failed to read from input file.";
+            return false;
+        }
+
+        inBuffer.resize(read);
+
+        if (read == 0) {
+            eof = true;
+        }
+
+        if (!compressAndWriteLz4(context.get(), inBuffer, outBuffer, fdOut)) {
+            return false;
+        }
+    }
+
+    // Footer.
+    auto prvSize = outBuffer.size();
+    outBuffer.resize(outBuffer.capacity());
+    rc = LZ4F_compressEnd(context.get(), outBuffer.data() + prvSize, outBuffer.size() - prvSize,
+                          nullptr);
+    if (LZ4F_isError(rc)) {
+        LOG(ERROR) << "LZ4F_compressEnd failed: " << LZ4F_getErrorName(rc);
+        return false;
+    }
+    outBuffer.resize(prvSize + rc);
+
+    if (!writeToFile(outBuffer, fdOut)) {
+        return false;
+    }
+
+    return true;
+}
+
+static const JNINativeMethod method_table[] = {
+        {"nativeCompressLz4", "(II)Z", (void*)nativeCompressLz4},
+};
+
+} // namespace
+
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env) {
+    return jniRegisterNativeMethods(env, "com/android/server/pm/Settings", method_table,
+                                    NELEM(method_table));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 00f851f..1845057 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -56,6 +56,7 @@
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
 int register_android_server_FaceService(JNIEnv* env);
 int register_android_server_GpuService(JNIEnv* env);
@@ -114,6 +115,7 @@
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(env);
     register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
+    register_android_server_com_android_server_pm_Settings(env);
     register_android_server_AdbDebuggingManager(env);
     register_android_server_FaceService(env);
     register_android_server_GpuService(env);
diff --git a/services/core/jni/onload_settings.cpp b/services/core/jni/onload_settings.cpp
new file mode 100644
index 0000000..b21c34a
--- /dev/null
+++ b/services/core/jni/onload_settings.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#include "jni.h"
+#include "utils/Log.h"
+
+namespace android {
+int register_android_server_com_android_server_pm_Settings(JNIEnv* env);
+};
+
+using namespace android;
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) {
+    JNIEnv* env = NULL;
+    jint result = -1;
+
+    if (vm->GetEnv((void**)&env, JNI_VERSION_1_4) != JNI_OK) {
+        ALOGE("GetEnv failed!");
+        return result;
+    }
+    ALOG_ASSERT(env, "Could not retrieve the env!");
+
+    register_android_server_com_android_server_pm_Settings(env);
+
+    return JNI_VERSION_1_4;
+}
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index 0f6ef03..3453cbd 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -42,8 +42,11 @@
                                std::shared_ptr<ITvInputWrapper>(new ITvInputWrapper(hidlITvInput)),
                                looper);
     }
-    ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
-    std::shared_ptr<AidlITvInput> aidlITvInput = AidlITvInput::fromBinder(binder);
+    std::shared_ptr<AidlITvInput> aidlITvInput = nullptr;
+    if (AServiceManager_isDeclared(TV_INPUT_AIDL_SERVICE_NAME)) {
+        ::ndk::SpAIBinder binder(AServiceManager_waitForService(TV_INPUT_AIDL_SERVICE_NAME));
+        aidlITvInput = AidlITvInput::fromBinder(binder);
+    }
     if (aidlITvInput == nullptr) {
         ALOGE("Couldn't get tv.input service.");
         return nullptr;
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index d3b9e10..2f951ed 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -36,7 +36,7 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.service.credentials.BeginCreateCredentialRequest;
-import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.BeginGetCredentialsRequest;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -169,8 +169,8 @@
 
             // Iterate over all provider sessions and invoke the request
             providerSessions.forEach(providerGetSession -> {
-                providerGetSession.getRemoteCredentialService().onGetCredentials(
-                        (GetCredentialsRequest) providerGetSession.getProviderRequest(),
+                providerGetSession.getRemoteCredentialService().onBeginGetCredentials(
+                        (BeginGetCredentialsRequest) providerGetSession.getProviderRequest(),
                         /*callback=*/providerGetSession);
             });
             return cancelTransport;
diff --git a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
index d0bc074..7f9e57a 100644
--- a/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
+++ b/services/credentials/java/com/android/server/credentials/PendingIntentResultHandler.java
@@ -19,7 +19,7 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.credentials.CreateCredentialResponse;
-import android.credentials.Credential;
+import android.credentials.GetCredentialResponse;
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.CredentialsResponseContent;
@@ -43,8 +43,7 @@
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService
-                        .EXTRA_GET_CREDENTIALS_CONTENT_RESULT,
+                CredentialProviderService.EXTRA_CREDENTIALS_RESPONSE_CONTENT,
                 CredentialsResponseContent.class);
     }
 
@@ -54,17 +53,17 @@
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESULT,
+                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_RESPONSE,
                 CreateCredentialResponse.class);
     }
 
-    /** Extracts the {@link Credential} object added to the result data. */
-    public static Credential extractCredential(Intent resultData) {
+    /** Extracts the {@link GetCredentialResponse} object added to the result data. */
+    public static GetCredentialResponse extractGetCredentialResponse(Intent resultData) {
         if (resultData == null) {
             return null;
         }
         return resultData.getParcelableExtra(
-                CredentialProviderService.EXTRA_CREDENTIAL_RESULT,
-                Credential.class);
+                CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE,
+                GetCredentialResponse.class);
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
index 6cd011b..9888cc0 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -19,19 +19,23 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.PendingIntent;
 import android.content.Context;
-import android.credentials.Credential;
+import android.content.Intent;
 import android.credentials.GetCredentialOption;
 import android.credentials.GetCredentialResponse;
 import android.credentials.ui.Entry;
 import android.credentials.ui.GetCredentialProviderData;
 import android.credentials.ui.ProviderPendingIntentResponse;
 import android.service.credentials.Action;
+import android.service.credentials.BeginGetCredentialOption;
+import android.service.credentials.BeginGetCredentialsRequest;
+import android.service.credentials.BeginGetCredentialsResponse;
 import android.service.credentials.CredentialEntry;
 import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialProviderService;
 import android.service.credentials.CredentialsResponseContent;
-import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.GetCredentialRequest;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -41,6 +45,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import java.util.stream.Collectors;
 
 /**
  * Central provider session that listens for provider callbacks, and maintains provider state.
@@ -48,10 +53,10 @@
  *
  * @hide
  */
-public final class ProviderGetSession extends ProviderSession<GetCredentialsRequest,
-        GetCredentialsResponse>
+public final class ProviderGetSession extends ProviderSession<BeginGetCredentialsRequest,
+        BeginGetCredentialsResponse>
         implements
-        RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+        RemoteCredentialService.ProviderCallbacks<BeginGetCredentialsResponse> {
     private static final String TAG = "ProviderGetSession";
 
     // Key to be used as an entry key for a credential entry
@@ -69,6 +74,9 @@
     @Nullable
     private Pair<String, Action> mUiAuthenticationAction = null;
 
+    /** The complete request to be used in the second round. */
+    private final GetCredentialRequest mCompleteRequest;
+
     /** Creates a new provider session to be used by the request session. */
     @Nullable public static ProviderGetSession createNewSession(
             Context context,
@@ -76,20 +84,34 @@
             CredentialProviderInfo providerInfo,
             GetRequestSession getRequestSession,
             RemoteCredentialService remoteCredentialService) {
-        GetCredentialsRequest providerRequest =
+        GetCredentialRequest completeRequest =
                 createProviderRequest(providerInfo.getCapabilities(),
                         getRequestSession.mClientRequest,
                         getRequestSession.mClientCallingPackage);
-        if (providerRequest != null) {
+        if (completeRequest != null) {
+            // TODO: Update to using query data when ready
+            BeginGetCredentialsRequest beginGetCredentialsRequest =
+                    new BeginGetCredentialsRequest.Builder(
+                            completeRequest.getCallingPackage())
+                            .setBeginGetCredentialOptions(
+                                    completeRequest.getGetCredentialOptions().stream().map(
+                                            option -> {
+                                                //TODO : Replace with option.getCandidateQueryData
+                                                // when ready
+                                                return new BeginGetCredentialOption(
+                                                    option.getType(),
+                                                        option.getCandidateQueryData());
+                                            }).collect(Collectors.toList()))
+                            .build();
             return new ProviderGetSession(context, providerInfo, getRequestSession, userId,
-                    remoteCredentialService, providerRequest);
+                    remoteCredentialService, beginGetCredentialsRequest, completeRequest);
         }
         Log.i(TAG, "Unable to create provider session");
         return null;
     }
 
     @Nullable
-    private static GetCredentialsRequest createProviderRequest(List<String> providerCapabilities,
+    private static GetCredentialRequest createProviderRequest(List<String> providerCapabilities,
             android.credentials.GetCredentialRequest clientRequest,
             String clientCallingPackage) {
         List<GetCredentialOption> filteredOptions = new ArrayList<>();
@@ -104,7 +126,7 @@
             }
         }
         if (!filteredOptions.isEmpty()) {
-            return new GetCredentialsRequest.Builder(clientCallingPackage).setGetCredentialOptions(
+            return new GetCredentialRequest.Builder(clientCallingPackage).setGetCredentialOptions(
                     filteredOptions).build();
         }
         Log.i(TAG, "In createProviderRequest - returning null");
@@ -115,8 +137,10 @@
             CredentialProviderInfo info,
             ProviderInternalCallback callbacks,
             int userId, RemoteCredentialService remoteCredentialService,
-            GetCredentialsRequest request) {
-        super(context, info, request, callbacks, userId, remoteCredentialService);
+            BeginGetCredentialsRequest beginGetRequest,
+            GetCredentialRequest completeGetRequest) {
+        super(context, info, beginGetRequest, callbacks, userId, remoteCredentialService);
+        mCompleteRequest = completeGetRequest;
         setStatus(Status.PENDING);
     }
 
@@ -128,7 +152,7 @@
 
     /** Called when the provider response has been updated by an external source. */
     @Override // Callback from the remote provider
-    public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
+    public void onProviderResponseSuccess(@Nullable BeginGetCredentialsResponse response) {
         Log.i(TAG, "in onProviderResponseSuccess");
         onUpdateResponse(response);
     }
@@ -254,19 +278,26 @@
             mUiCredentialEntries.put(entryId, credentialEntry);
             Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
             if (credentialEntry.getPendingIntent() != null) {
+                setUpFillInIntent(credentialEntry.getPendingIntent());
                 credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
                         credentialEntry.getSlice(), credentialEntry.getPendingIntent(),
                         /*fillInIntent=*/null));
-            } else if (credentialEntry.getCredential() != null) {
-                credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
-                        credentialEntry.getSlice()));
             } else {
-                Log.i(TAG, "No credential or pending intent. Should not happen.");
+                Log.i(TAG, "No pending intent. Should not happen.");
             }
         }
         return credentialUiEntries;
     }
 
+    private Intent setUpFillInIntent(PendingIntent pendingIntent) {
+        Intent intent = pendingIntent.getIntent();
+        intent.putExtra(
+                CredentialProviderService
+                        .EXTRA_GET_CREDENTIAL_REQUEST,
+                mCompleteRequest);
+        return intent;
+    }
+
     private List<Entry> prepareUiActionEntries(@Nullable List<Action> actions) {
         List<Entry> actionEntries = new ArrayList<>();
         for (Action action : actions) {
@@ -292,17 +323,14 @@
 
     private void onCredentialEntrySelected(CredentialEntry credentialEntry,
             ProviderPendingIntentResponse providerPendingIntentResponse) {
-        if (credentialEntry.getCredential() != null) {
-            mCallbacks.onFinalResponseReceived(mComponentName, new GetCredentialResponse(
-                    credentialEntry.getCredential()));
-            return;
-        } else if (providerPendingIntentResponse != null) {
+        if (providerPendingIntentResponse != null) {
             if (PendingIntentResultHandler.isSuccessfulResponse(providerPendingIntentResponse)) {
-                Credential credential = PendingIntentResultHandler.extractCredential(
-                        providerPendingIntentResponse.getResultData());
-                if (credential != null) {
-                    mCallbacks.onFinalResponseReceived(mComponentName,
-                            new GetCredentialResponse(credential));
+                // TODO: Remove credential extraction when flow is fully transitioned
+                GetCredentialResponse getCredentialResponse = PendingIntentResultHandler
+                        .extractGetCredentialResponse(
+                                providerPendingIntentResponse.getResultData());
+                if (getCredentialResponse != null) {
+                    mCallbacks.onFinalResponseReceived(mComponentName, getCredentialResponse);
                     return;
                 }
             }
@@ -320,7 +348,8 @@
                         .extractResponseContent(providerPendingIntentResponse
                                 .getResultData());
                 if (content != null) {
-                    onUpdateResponse(GetCredentialsResponse.createWithResponseContent(content));
+                    onUpdateResponse(
+                            BeginGetCredentialsResponse.createWithResponseContent(content));
                     return;
                 }
             }
@@ -337,7 +366,7 @@
 
 
     /** Updates the response being maintained in state by this provider session. */
-    private void onUpdateResponse(GetCredentialsResponse response) {
+    private void onUpdateResponse(BeginGetCredentialsResponse response) {
         mProviderResponse = response;
         if (response.getAuthenticationAction() != null) {
             Log.i(TAG , "updateResponse with authentication entry");
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index e385bcb..7a883b3 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -26,14 +26,14 @@
 import android.os.RemoteException;
 import android.service.credentials.BeginCreateCredentialRequest;
 import android.service.credentials.BeginCreateCredentialResponse;
+import android.service.credentials.BeginGetCredentialsRequest;
+import android.service.credentials.BeginGetCredentialsResponse;
 import android.service.credentials.CredentialProviderException;
 import android.service.credentials.CredentialProviderException.CredentialProviderError;
 import android.service.credentials.CredentialProviderService;
-import android.service.credentials.GetCredentialsRequest;
-import android.service.credentials.GetCredentialsResponse;
 import android.service.credentials.IBeginCreateCredentialCallback;
+import android.service.credentials.IBeginGetCredentialsCallback;
 import android.service.credentials.ICredentialProviderService;
-import android.service.credentials.IGetCredentialsCallback;
 import android.text.format.DateUtils;
 import android.util.Log;
 import android.util.Slog;
@@ -106,19 +106,21 @@
      * @param callback the callback to be used to send back the provider response to the
      *                 {@link ProviderGetSession} class that maintains provider state
      */
-    public void onGetCredentials(@NonNull GetCredentialsRequest request,
-            ProviderCallbacks<GetCredentialsResponse> callback) {
+    public void onBeginGetCredentials(@NonNull BeginGetCredentialsRequest request,
+            ProviderCallbacks<BeginGetCredentialsResponse> callback) {
         Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
         AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
-        AtomicReference<CompletableFuture<GetCredentialsResponse>> futureRef =
+        AtomicReference<CompletableFuture<BeginGetCredentialsResponse>> futureRef =
                 new AtomicReference<>();
 
-        CompletableFuture<GetCredentialsResponse> connectThenExecute = postAsync(service -> {
-            CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
+        CompletableFuture<BeginGetCredentialsResponse> connectThenExecute = postAsync(service -> {
+            CompletableFuture<BeginGetCredentialsResponse> getCredentials =
+                    new CompletableFuture<>();
             ICancellationSignal cancellationSignal =
-                    service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
+                    service.onBeginGetCredentials(request,
+                            new IBeginGetCredentialsCallback.Stub() {
                         @Override
-                        public void onSuccess(GetCredentialsResponse response) {
+                        public void onSuccess(BeginGetCredentialsResponse response) {
                             Log.i(TAG, "In onSuccess in RemoteCredentialService");
                             getCredentials.complete(response);
                         }
@@ -132,7 +134,7 @@
                                     errorCode, errorMsg));
                         }
                     });
-            CompletableFuture<GetCredentialsResponse> future = futureRef.get();
+            CompletableFuture<BeginGetCredentialsResponse> future = futureRef.get();
             if (future != null && future.isCancelled()) {
                 dispatchCancellationSignal(cancellationSignal);
             } else {
@@ -159,7 +161,8 @@
         AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef =
                 new AtomicReference<>();
 
-        CompletableFuture<BeginCreateCredentialResponse> connectThenExecute = postAsync(service -> {
+        CompletableFuture<BeginCreateCredentialResponse> connectThenExecute =
+                postAsync(service -> {
             CompletableFuture<BeginCreateCredentialResponse> createCredentialFuture =
                     new CompletableFuture<>();
             ICancellationSignal cancellationSignal = service.onBeginCreateCredential(
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 775e3d8..d91f633 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -718,6 +718,16 @@
     private static final String ENABLE_COEXISTENCE_FLAG = "enable_coexistence";
     private static final boolean DEFAULT_ENABLE_COEXISTENCE_FLAG = false;
 
+    // TODO(b/258425381) remove the flag after rollout.
+    private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running";
+    private static final boolean DEFAULT_KEEP_PROFILES_RUNNING_FLAG = false;
+
+    /**
+     * This feature flag is checked once after boot and this value us used until the next reboot to
+     * avoid needing to handle the flag changing on the fly.
+     */
+    private final boolean mKeepProfilesRunning = isKeepProfilesRunningFlagEnabled();
+
     /**
      * For apps targeting U+
      * Enable multiple admins to coexist on the same device.
@@ -1935,10 +1945,11 @@
     }
 
     private Owners makeOwners(Injector injector, PolicyPathProvider pathProvider) {
-        return new Owners(injector.getUserManager(), injector.getUserManagerInternal(),
+        return new Owners(
+                injector.getUserManager(), injector.getUserManagerInternal(),
                 injector.getPackageManagerInternal(),
                 injector.getActivityTaskManagerInternal(),
-                injector.getActivityManagerInternal(), pathProvider);
+                injector.getActivityManagerInternal(), mStateCache, pathProvider);
     }
 
     /**
@@ -9892,6 +9903,8 @@
                             (size == 1 ? "" : "s"));
                 }
                 pw.println();
+                pw.println("Keep profiles running: " + mKeepProfilesRunning);
+                pw.println();
 
                 mPolicyCache.dump(pw);
                 pw.println();
@@ -13240,7 +13253,7 @@
             CallerIdentity caller = new CallerIdentity(callerUid, null, null);
             if (isUserAffiliatedWithDevice(UserHandle.getUserId(callerUid))
                     && (isActiveProfileOwner(callerUid)
-                        || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) {
+                    || isDefaultDeviceOwner(caller) || isFinancedDeviceOwner(caller))) {
                 // device owner or a profile owner affiliated with the device owner
                 return true;
             }
@@ -13533,11 +13546,21 @@
             }
         }
 
+        @Override
+        public boolean isKeepProfilesRunningEnabled() {
+            return mKeepProfilesRunning;
+        }
+
         private @Mode int findInteractAcrossProfilesResetMode(String packageName) {
             return getDefaultCrossProfilePackages().contains(packageName)
                     ? AppOpsManager.MODE_ALLOWED
                     : AppOpsManager.opToDefaultMode(AppOpsManager.OP_INTERACT_ACROSS_PROFILES);
         }
+
+        @Override
+        public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
+            return getDeviceStateCache().isUserOrganizationManaged(userHandle);
+        }
     }
 
     private Intent createShowAdminSupportIntent(int userId) {
@@ -14387,7 +14410,7 @@
             // Bail out if we are trying to provision a work profile but one already exists.
             if (!mUserManager.canAddMoreManagedProfiles(
                     callingUserId, /* allowedToRemoveOne= */ false)) {
-                Slogf.i(LOG_TAG, "A work profile already exists.");
+                Slogf.i(LOG_TAG, "Cannot add more managed profiles.");
                 return STATUS_CANNOT_ADD_MANAGED_PROFILE;
             }
         } finally {
@@ -19167,4 +19190,11 @@
                 ENABLE_COEXISTENCE_FLAG,
                 DEFAULT_ENABLE_COEXISTENCE_FLAG);
     }
+
+    private static boolean isKeepProfilesRunningFlagEnabled() {
+        return DeviceConfig.getBoolean(
+                NAMESPACE_DEVICE_POLICY_MANAGER,
+                KEEP_PROFILES_RUNNING_FLAG,
+                DEFAULT_KEEP_PROFILES_RUNNING_FLAG);
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
index 1215253..011a282 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceStateCacheImpl.java
@@ -15,11 +15,17 @@
  */
 package com.android.server.devicepolicy;
 
+import android.annotation.UserIdInt;
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.DeviceStateCache;
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.GuardedBy;
 
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
 /**
  * Implementation of {@link DeviceStateCache}, to which {@link DevicePolicyManagerService} pushes
  * device state.
@@ -32,6 +38,11 @@
      */
     private final Object mLock = new Object();
 
+    public static final int NO_DEVICE_OWNER = -1;
+
+    private AtomicInteger mDeviceOwnerType = new AtomicInteger(NO_DEVICE_OWNER);
+    private Map<Integer, Boolean> mHasProfileOwner = new ConcurrentHashMap<>();
+
     @GuardedBy("mLock")
     private boolean mIsDeviceProvisioned = false;
 
@@ -47,11 +58,43 @@
         }
     }
 
+    void setDeviceOwnerType(int deviceOwnerType) {
+        mDeviceOwnerType.set(deviceOwnerType);
+    }
+
+    void setHasProfileOwner(int userId, boolean hasProfileOwner) {
+        if (hasProfileOwner) {
+            mHasProfileOwner.put(userId, true);
+        } else {
+            mHasProfileOwner.remove(userId);
+        }
+    }
+
+    @Override
+    public boolean isUserOrganizationManaged(@UserIdInt int userHandle) {
+        if (mHasProfileOwner.getOrDefault(userHandle, false)
+                || hasEnterpriseDeviceOwner()) {
+            return true;
+        }
+
+        // TODO: Support role holder override
+        return false;
+    }
+
+    private boolean hasEnterpriseDeviceOwner() {
+        return mDeviceOwnerType.get() == DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
+    }
+
     /** Dump content */
     public void dump(IndentingPrintWriter pw) {
         pw.println("Device state cache:");
         pw.increaseIndent();
         pw.println("Device provisioned: " + mIsDeviceProvisioned);
+        pw.println("Device Owner Type: " + mDeviceOwnerType.get());
+        pw.println("Has PO:");
+        for (Integer id : mHasProfileOwner.keySet()) {
+            pw.println("User " + id + ": " + mHasProfileOwner.get(id));
+        }
         pw.decreaseIndent();
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
index 3b46d52..6f172e4 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/Owners.java
@@ -16,8 +16,12 @@
 
 package com.android.server.devicepolicy;
 
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT;
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
 import static android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_DEFAULT;
 
+import static com.android.server.devicepolicy.DeviceStateCacheImpl.NO_DEVICE_OWNER;
+
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManagerInternal;
@@ -31,6 +35,7 @@
 import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.DeviceConfig;
 import android.util.ArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
@@ -69,6 +74,7 @@
     private final PackageManagerInternal mPackageManagerInternal;
     private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
     private final ActivityManagerInternal mActivityManagerInternal;
+    private final DeviceStateCacheImpl mDeviceStateCache;
 
     @GuardedBy("mData")
     private final OwnersData mData;
@@ -81,12 +87,14 @@
             PackageManagerInternal packageManagerInternal,
             ActivityTaskManagerInternal activityTaskManagerInternal,
             ActivityManagerInternal activityManagerInternal,
+            DeviceStateCacheImpl deviceStateCache,
             PolicyPathProvider pathProvider) {
         mUserManager = userManager;
         mUserManagerInternal = userManagerInternal;
         mPackageManagerInternal = packageManagerInternal;
         mActivityTaskManagerInternal = activityTaskManagerInternal;
         mActivityManagerInternal = activityManagerInternal;
+        mDeviceStateCache = deviceStateCache;
         mData = new OwnersData(pathProvider);
     }
 
@@ -99,9 +107,24 @@
                     mUserManager.getAliveUsers().stream().mapToInt(u -> u.id).toArray();
             mData.load(usersIds);
 
-            mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
-            for (int userId : usersIds) {
-                mUserManagerInternal.setUserManaged(userId, hasProfileOwner(userId));
+            // TODO(b/258213147): Remove
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+                if (hasDeviceOwner()) {
+                    int deviceOwnerType = mData.mDeviceOwnerTypes.getOrDefault(
+                            mData.mDeviceOwner.packageName,
+                            /* defaultValue= */ DEVICE_OWNER_TYPE_DEFAULT);
+                    mDeviceStateCache.setDeviceOwnerType(deviceOwnerType);
+                } else {
+                    mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
+                }
+
+            } else {
+                mUserManagerInternal.setDeviceManaged(hasDeviceOwner());
+                for (int userId : usersIds) {
+                    mUserManagerInternal.setUserManaged(userId, hasProfileOwner(userId));
+                }
             }
 
             notifyChangeLocked();
@@ -224,7 +247,18 @@
                     /* remoteBugreportHash =*/ null, /* isOrganizationOwnedDevice =*/ true);
             mData.mDeviceOwnerUserId = userId;
 
-            mUserManagerInternal.setDeviceManaged(true);
+            // TODO(b/258213147): Remove
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+                int deviceOwnerType = mData.mDeviceOwnerTypes.getOrDefault(
+                        mData.mDeviceOwner.packageName,
+                        /* defaultValue= */ DEVICE_OWNER_TYPE_DEFAULT);
+                mDeviceStateCache.setDeviceOwnerType(deviceOwnerType);
+            } else {
+                mUserManagerInternal.setDeviceManaged(true);
+            }
+
             notifyChangeLocked();
             pushToActivityTaskManagerLocked();
         }
@@ -236,7 +270,14 @@
             mData.mDeviceOwner = null;
             mData.mDeviceOwnerUserId = UserHandle.USER_NULL;
 
-            mUserManagerInternal.setDeviceManaged(false);
+            // TODO(b/258213147): Remove
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+                mDeviceStateCache.setDeviceOwnerType(NO_DEVICE_OWNER);
+            } else {
+                mUserManagerInternal.setDeviceManaged(false);
+            }
             notifyChangeLocked();
             pushToActivityTaskManagerLocked();
         }
@@ -248,7 +289,15 @@
             mData.mProfileOwners.put(userId, new OwnerInfo(admin,
                     /* remoteBugreportUri =*/ null, /* remoteBugreportHash =*/ null,
                     /* isOrganizationOwnedDevice =*/ false));
-            mUserManagerInternal.setUserManaged(userId, true);
+
+            // TODO(b/258213147): Remove
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+                mDeviceStateCache.setHasProfileOwner(userId, true);
+            } else {
+                mUserManagerInternal.setUserManaged(userId, true);
+            }
             notifyChangeLocked();
         }
     }
@@ -256,7 +305,14 @@
     void removeProfileOwner(int userId) {
         synchronized (mData) {
             mData.mProfileOwners.remove(userId);
-            mUserManagerInternal.setUserManaged(userId, false);
+            // TODO(b/258213147): Remove
+            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG,
+                    DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_DEFAULT)) {
+                mDeviceStateCache.setHasProfileOwner(userId, false);
+            } else {
+                mUserManagerInternal.setUserManaged(userId, false);
+            }
             notifyChangeLocked();
         }
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c346b2f..e41e781 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -187,6 +187,7 @@
 import com.android.server.security.FileIntegrityService;
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.rkp.RemoteProvisioningService;
 import com.android.server.sensorprivacy.SensorPrivacyService;
 import com.android.server.sensors.SensorService;
 import com.android.server.signedconfig.SignedConfigService;
@@ -211,6 +212,7 @@
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.vibrator.VibratorManagerService;
 import com.android.server.vr.VrManagerService;
+import com.android.server.wearable.WearableSensingManagerService;
 import com.android.server.webkit.WebViewUpdateService;
 import com.android.server.wm.ActivityTaskManagerService;
 import com.android.server.wm.WindowManagerGlobalLock;
@@ -1360,11 +1362,16 @@
         mSystemServiceManager.startService(BugreportManagerService.class);
         t.traceEnd();
 
-        // Serivce for GPU and GPU driver.
+        // Service for GPU and GPU driver.
         t.traceBegin("GpuService");
         mSystemServiceManager.startService(GpuService.class);
         t.traceEnd();
 
+        // Handles system process requests for remotely provisioned keys & data.
+        t.traceBegin("StartRemoteProvisioningService");
+        mSystemServiceManager.startService(RemoteProvisioningService.class);
+        t.traceEnd();
+
         t.traceEnd(); // startCoreServices
     }
 
@@ -1830,6 +1837,7 @@
             startSystemCaptionsManagerService(context, t);
             startTextToSpeechManagerService(context, t);
             startAmbientContextService(t);
+            startWearableSensingService(t);
 
             // System Speech Recognition Service
             t.traceBegin("StartSpeechRecognitionManagerService");
@@ -3170,6 +3178,12 @@
         t.traceEnd();
     }
 
+    private void startWearableSensingService(@NonNull TimingsTraceAndSlog t) {
+        t.traceBegin("startWearableSensingService");
+        mSystemServiceManager.startService(WearableSensingManagerService.class);
+        t.traceEnd();
+    }
+
     private static void startSystemUi(Context context, WindowManagerService windowManager) {
         PackageManagerInternal pm = LocalServices.getService(PackageManagerInternal.class);
         Intent intent = new Intent();
diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java
index b519a782..4aba30a 100644
--- a/services/midi/java/com/android/server/midi/MidiService.java
+++ b/services/midi/java/com/android/server/midi/MidiService.java
@@ -23,7 +23,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-// import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -31,6 +30,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.XmlResourceParser;
+import android.media.MediaMetrics;
 import android.media.midi.IBluetoothMidiService;
 import android.media.midi.IMidiDeviceListener;
 import android.media.midi.IMidiDeviceOpenCallback;
@@ -63,12 +63,16 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 
 // NOTE about locking order:
 // if there is a path that syncs on BOTH mDevicesByInfo AND mDeviceConnections,
@@ -359,6 +363,17 @@
         private final ArrayList<DeviceConnection> mDeviceConnections
                 = new ArrayList<DeviceConnection>();
 
+        // Keep track of number of added and removed collections for logging
+        private AtomicInteger mDeviceConnectionsAdded = new AtomicInteger();
+        private AtomicInteger mDeviceConnectionsRemoved = new AtomicInteger();
+
+        // Keep track of total time with at least one active connection
+        private AtomicLong mTotalTimeConnectedNs = new AtomicLong();
+        private Instant mPreviousCounterInstant = null;
+
+        private AtomicInteger mTotalInputBytes = new AtomicInteger();
+        private AtomicInteger mTotalOutputBytes = new AtomicInteger();
+
         public Device(IMidiDeviceServer server, MidiDeviceInfo deviceInfo,
                 ServiceInfo serviceInfo, int uid) {
             mDeviceInfo = deviceInfo;
@@ -460,6 +475,11 @@
         public void addDeviceConnection(DeviceConnection connection) {
             Log.d(TAG, "addDeviceConnection() [A] connection:" + connection);
             synchronized (mDeviceConnections) {
+                mDeviceConnectionsAdded.incrementAndGet();
+                if (mPreviousCounterInstant == null) {
+                    mPreviousCounterInstant = Instant.now();
+                }
+
                 Log.d(TAG, "  mServer:" + mServer);
                 if (mServer != null) {
                     Log.i(TAG, "++++ A");
@@ -533,6 +553,20 @@
         public void removeDeviceConnection(DeviceConnection connection) {
             synchronized (mDevicesByInfo) {
                 synchronized (mDeviceConnections) {
+                    int numRemovedConnections = mDeviceConnectionsRemoved.incrementAndGet();
+                    if (mPreviousCounterInstant != null) {
+                        mTotalTimeConnectedNs.addAndGet(Duration.between(
+                                mPreviousCounterInstant, Instant.now()).toNanos());
+                    }
+                    // Stop the clock if all devices have been removed.
+                    // Otherwise, start the clock from the current instant.
+                    if (numRemovedConnections >= mDeviceConnectionsAdded.get()) {
+                        mPreviousCounterInstant = null;
+                    } else {
+                        mPreviousCounterInstant = Instant.now();
+                    }
+                    logMetrics(false /* isDeviceDisconnected */);
+
                     mDeviceConnections.remove(connection);
 
                     if (connection.getDevice().getDeviceInfo().getType()
@@ -569,6 +603,16 @@
                     connection.getClient().removeDeviceConnection(connection);
                 }
                 mDeviceConnections.clear();
+
+                // If the timer is still going, some clients have not closed the connection yet.
+                if (mPreviousCounterInstant != null) {
+                    Instant currentInstant = Instant.now();
+                    mTotalTimeConnectedNs.addAndGet(Duration.between(
+                            mPreviousCounterInstant, currentInstant).toNanos());
+                    mPreviousCounterInstant = currentInstant;
+                }
+
+                logMetrics(true /* isDeviceDisconnected */);
             }
             setDeviceServer(null);
 
@@ -585,6 +629,35 @@
             }
         }
 
+        private void logMetrics(boolean isDeviceDisconnected) {
+            // Only log metrics if the device was used in a connection
+            int numDeviceConnectionAdded = mDeviceConnectionsAdded.get();
+            if (mDeviceInfo != null && numDeviceConnectionAdded > 0) {
+                new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIDI)
+                    .setUid(mUid)
+                    .set(MediaMetrics.Property.DEVICE_ID, mDeviceInfo.getId())
+                    .set(MediaMetrics.Property.INPUT_PORT_COUNT, mDeviceInfo.getInputPortCount())
+                    .set(MediaMetrics.Property.OUTPUT_PORT_COUNT,
+                            mDeviceInfo.getOutputPortCount())
+                    .set(MediaMetrics.Property.HARDWARE_TYPE, mDeviceInfo.getType())
+                    .set(MediaMetrics.Property.DURATION_NS, mTotalTimeConnectedNs.get())
+                    .set(MediaMetrics.Property.OPENED_COUNT, numDeviceConnectionAdded)
+                    .set(MediaMetrics.Property.CLOSED_COUNT, mDeviceConnectionsRemoved.get())
+                    .set(MediaMetrics.Property.DEVICE_DISCONNECTED,
+                            isDeviceDisconnected ? "true" : "false")
+                    .set(MediaMetrics.Property.IS_SHARED,
+                            !mDeviceInfo.isPrivate() ? "true" : "false")
+                    .set(MediaMetrics.Property.SUPPORTS_MIDI_UMP, mDeviceInfo.getDefaultProtocol()
+                             != MidiDeviceInfo.PROTOCOL_UNKNOWN ? "true" : "false")
+                    .set(MediaMetrics.Property.USING_ALSA, mDeviceInfo.getProperties().get(
+                            MidiDeviceInfo.PROPERTY_ALSA_CARD) != null ? "true" : "false")
+                    .set(MediaMetrics.Property.EVENT, "deviceClosed")
+                    .set(MediaMetrics.Property.TOTAL_INPUT_BYTES, mTotalInputBytes.get())
+                    .set(MediaMetrics.Property.TOTAL_OUTPUT_BYTES, mTotalOutputBytes.get())
+                    .record();
+            }
+        }
+
         @Override
         public void binderDied() {
             Log.d(TAG, "Device died: " + this);
@@ -593,6 +666,11 @@
             }
         }
 
+        public void updateTotalBytes(int totalInputBytes, int totalOutputBytes) {
+            mTotalInputBytes.set(totalInputBytes);
+            mTotalOutputBytes.set(totalOutputBytes);
+        }
+
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder("Device Info: ");
@@ -1373,6 +1451,17 @@
     }
 
     @Override
+    public void updateTotalBytes(IMidiDeviceServer server, int totalInputBytes,
+            int totalOutputBytes) {
+        synchronized (mDevicesByInfo) {
+            Device device = mDevicesByServer.get(server.asBinder());
+            if (device != null) {
+                device.updateTotalBytes(totalInputBytes, totalOutputBytes);
+            }
+        }
+    }
+
+    @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
         final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
new file mode 100644
index 0000000..a565feb
--- /dev/null
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpsCheckingServiceCompatImpl.kt
@@ -0,0 +1,144 @@
+/*
+ * 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.permission.access.appop
+
+import android.util.ArraySet
+import android.util.SparseBooleanArray
+import android.util.SparseIntArray
+import com.android.server.appop.AppOpsCheckingServiceInterface
+import com.android.server.appop.OnOpModeChangedListener
+import com.android.server.permission.access.AccessCheckingService
+import java.io.PrintWriter
+
+class AppOpsCheckingServiceCompatImpl(
+    private val accessCheckingService: AccessCheckingService
+) : AppOpsCheckingServiceInterface {
+    override fun getNonDefaultUidModes(uid: Int): SparseIntArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun getUidMode(uid: Int, op: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setUidMode(uid: Int, op: Int, mode: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageMode(packageName: String, op: Int, userId: Int): Int {
+        TODO("Not yet implemented")
+    }
+
+    override fun setPackageMode(packageName: String, op: Int, mode: Int, userId: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removePackage(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeUid(uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun areUidModesDefault(uid: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun arePackageModesDefault(packageName: String, userId: Int): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun clearAllModes() {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingOpModeChanged(changedListener: OnOpModeChangedListener, op: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun startWatchingPackageModeChanged(
+        changedListener: OnOpModeChangedListener,
+        packageName: String
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun removeListener(changedListener: OnOpModeChangedListener) {
+        TODO("Not yet implemented")
+    }
+
+    override fun getOpModeChangedListeners(op: Int): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getPackageModeChangedListeners(
+        packageName: String
+    ): ArraySet<OnOpModeChangedListener> {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyWatchersOfChange(op: Int, uid: Int) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChanged(
+        changedListener: OnOpModeChangedListener,
+        op: Int,
+        uid: Int,
+        packageName: String?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun notifyOpChangedForAllPkgsInUid(
+        op: Int,
+        uid: Int,
+        onlyForeground: Boolean,
+        callbackToIgnore: OnOpModeChangedListener?
+    ) {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundUidOps(
+        uid: Int,
+        foregroundOps: SparseBooleanArray?
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun evalForegroundPackageOps(
+        packageName: String,
+        foregroundOps: SparseBooleanArray?,
+        userId: Int
+    ): SparseBooleanArray {
+        TODO("Not yet implemented")
+    }
+
+    override fun dumpListeners(
+        dumpOp: Int,
+        dumpUid: Int,
+        dumpPackage: String?,
+        printWriter: PrintWriter
+    ): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    companion object {
+        private val LOG_TAG = AppOpsCheckingServiceCompatImpl::class.java.simpleName
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
index 939fb6a..70a5c3f 100644
--- a/services/tests/InputMethodSystemServerTests/Android.bp
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -28,7 +28,7 @@
     ],
 
     srcs: [
-        "src/**/*.java",
+        "src/server/**/*.java",
     ],
 
     static_libs: [
@@ -60,3 +60,52 @@
         enabled: false,
     },
 }
+
+android_test {
+    name: "FrameworksImeTests",
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    srcs: [
+        "src/com/android/inputmethodservice/**/*.java",
+    ],
+
+    manifest: "src/com/android/inputmethodservice/AndroidManifest.xml",
+    test_config: "src/com/android/inputmethodservice/AndroidTest.xml",
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.espresso.core",
+        "androidx.test.espresso.contrib",
+        "androidx.test.ext.truth",
+        "frameworks-base-testutils",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "services.core",
+        "servicestests-core-utils",
+        "servicestests-utils-mockito-extended",
+        "truth-prebuilt",
+        "SimpleImeTestingLib",
+        "SimpleImeImsLib",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    data: [
+        ":SimpleTestIme",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
new file mode 100644
index 0000000..0104f71
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.inputmethod.imetests">
+
+    <uses-sdk android:targetSdkVersion="31" />
+
+    <!-- Permissions required for granting and logging -->
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+
+    <!-- Permissions for reading system info -->
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!-- The "targetPackage" reference the instruments APK package, which is the FakeImeApk, while
+    the test package is "com.android.inputmethod.imetests" (FrameworksImeTests.apk).-->
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.apps.inputmethod.simpleime"
+        android:label="Frameworks IME Tests" />
+</manifest>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
new file mode 100644
index 0000000..6c24d6d
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+<configuration description="Runs Frameworks IME Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="FrameworksImeTests.apk" />
+        <option name="test-file-name" value="SimpleTestIme.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksImeTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.inputmethod.imetests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <!-- Collect the files in the dump directory for debugging -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/FrameworksImeTests/" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
new file mode 100644
index 0000000..16a9845
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/inputmethodservice/InputMethodServiceTest.java
@@ -0,0 +1,276 @@
+/*
+ * 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.inputmethodservice;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+import com.android.apps.inputmethod.simpleime.testing.TestActivity;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class InputMethodServiceTest {
+    private static final String TAG = "SimpleIMSTest";
+    private static final String INPUT_METHOD_SERVICE_NAME = ".SimpleInputMethodService";
+    private static final String EDIT_TEXT_DESC = "Input box";
+    private static final long TIMEOUT_IN_SECONDS = 3;
+
+    public Instrumentation mInstrumentation;
+    public UiDevice mUiDevice;
+    public Context mContext;
+    public String mTargetPackageName;
+    public TestActivity mActivity;
+    public EditText mEditText;
+    public InputMethodServiceWrapper mInputMethodService;
+    public String mInputMethodId;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiDevice = UiDevice.getInstance(mInstrumentation);
+        mContext = mInstrumentation.getContext();
+        mTargetPackageName = mInstrumentation.getTargetContext().getPackageName();
+        mInputMethodId = getInputMethodId();
+        prepareIme();
+        prepareEditor();
+
+        // Waits for input binding ready.
+        eventually(
+                () -> {
+                    mInputMethodService =
+                            InputMethodServiceWrapper.getInputMethodServiceWrapperForTesting();
+                    assertThat(mInputMethodService).isNotNull();
+
+                    // The editor won't bring up keyboard by default.
+                    assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+                    assertThat(mInputMethodService.getCurrentInputViewStarted()).isFalse();
+                });
+    }
+
+    @Test
+    public void testShowHideKeyboard_byUserAction() throws InterruptedException {
+        // Performs click on editor box to bring up the soft keyboard.
+        Log.i(TAG, "Click on EditText.");
+        verifyInputViewStatus(() -> clickOnEditorText(), true /* inputViewStarted */);
+
+        // Press back key to hide soft keyboard.
+        Log.i(TAG, "Press back");
+        verifyInputViewStatus(
+                () -> assertThat(mUiDevice.pressHome()).isTrue(), false /* inputViewStarted */);
+    }
+
+    @Test
+    public void testShowHideKeyboard_byApi() throws InterruptedException {
+        // Triggers to show IME via public API.
+        verifyInputViewStatus(
+                () -> assertThat(mActivity.showImeWithWindowInsetsController()).isTrue(),
+                true /* inputViewStarted */);
+
+        // Triggers to hide IME via public API.
+        // TODO(b/242838873): investigate why WIC#hide(ime()) does not work, likely related to
+        //  triggered from IME process.
+        verifyInputViewStatusOnMainSync(
+                () -> assertThat(mActivity.hideImeWithInputMethodManager(0 /* flags */)).isTrue(),
+                false /* inputViewStarted */);
+    }
+
+    @Test
+    public void testShowHideSelf() throws InterruptedException {
+        // IME requests to show itself without any flags: expect shown.
+        Log.i(TAG, "Call IMS#requestShowSelf(0)");
+        verifyInputViewStatusOnMainSync(
+                () -> mInputMethodService.requestShowSelf(0), true /* inputViewStarted */);
+
+        // IME requests to hide itself with flag: HIDE_IMPLICIT_ONLY, expect not hide (shown).
+        Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+        verifyInputViewStatusOnMainSync(
+                () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                true /* inputViewStarted */);
+
+        // IME request to hide itself without any flags: expect hidden.
+        Log.i(TAG, "Call IMS#requestHideSelf(0)");
+        verifyInputViewStatusOnMainSync(
+                () -> mInputMethodService.requestHideSelf(0), false /* inputViewStarted */);
+
+        // IME request to show itself with flag SHOW_IMPLICIT: expect shown.
+        Log.i(TAG, "Call IMS#requestShowSelf(InputMethodManager.SHOW_IMPLICIT)");
+        verifyInputViewStatusOnMainSync(
+                () -> mInputMethodService.requestShowSelf(InputMethodManager.SHOW_IMPLICIT),
+                true /* inputViewStarted */);
+
+        // IME request to hide itself with flag: HIDE_IMPLICIT_ONLY, expect hidden.
+        Log.i(TAG, "Call IMS#requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY)");
+        verifyInputViewStatusOnMainSync(
+                () -> mInputMethodService.requestHideSelf(InputMethodManager.HIDE_IMPLICIT_ONLY),
+                false /* inputViewStarted */);
+    }
+
+    private void verifyInputViewStatus(Runnable runnable, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, inputViewStarted, false /*runOnMainSync*/);
+    }
+
+    private void verifyInputViewStatusOnMainSync(Runnable runnable, boolean inputViewStarted)
+            throws InterruptedException {
+        verifyInputViewStatusInternal(runnable, inputViewStarted, true /*runOnMainSync*/);
+    }
+
+    private void verifyInputViewStatusInternal(
+            Runnable runnable, boolean inputViewStarted, boolean runOnMainSync)
+            throws InterruptedException {
+        CountDownLatch signal = new CountDownLatch(1);
+        mInputMethodService.setCountDownLatchForTesting(signal);
+        // Runnable to trigger onStartInputView()/ onFinishInputView()
+        if (runOnMainSync) {
+            mInstrumentation.runOnMainSync(runnable);
+        } else {
+            runnable.run();
+        }
+        // Waits for onStartInputView() to finish.
+        mInstrumentation.waitForIdleSync();
+        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+        // Input is not finished.
+        assertThat(mInputMethodService.getCurrentInputStarted()).isTrue();
+        assertThat(mInputMethodService.getCurrentInputViewStarted()).isEqualTo(inputViewStarted);
+    }
+
+    @Test
+    public void testFullScreenMode() throws Exception {
+        Log.i(TAG, "Set orientation natural");
+        verifyFullscreenMode(() -> setOrientation(0), true /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation left");
+        verifyFullscreenMode(() -> setOrientation(1), false /* orientationPortrait */);
+
+        Log.i(TAG, "Set orientation right");
+        verifyFullscreenMode(() -> setOrientation(2), false /* orientationPortrait */);
+
+        mUiDevice.unfreezeRotation();
+    }
+
+    private void setOrientation(int orientation) {
+        // Simple wrapper for catching RemoteException.
+        try {
+            switch (orientation) {
+                case 1:
+                    mUiDevice.setOrientationLeft();
+                    break;
+                case 2:
+                    mUiDevice.setOrientationRight();
+                    break;
+                default:
+                    mUiDevice.setOrientationNatural();
+            }
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void verifyFullscreenMode(Runnable runnable, boolean orientationPortrait)
+            throws InterruptedException {
+        CountDownLatch signal = new CountDownLatch(1);
+        mInputMethodService.setCountDownLatchForTesting(signal);
+
+        // Runnable to trigger onConfigurationChanged()
+        try {
+            runnable.run();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        // Waits for onConfigurationChanged() to finish.
+        mInstrumentation.waitForIdleSync();
+        signal.await(TIMEOUT_IN_SECONDS, TimeUnit.SECONDS);
+
+        clickOnEditorText();
+        eventually(() -> assertThat(mInputMethodService.isInputViewShown()).isTrue());
+
+        assertThat(mInputMethodService.getResources().getConfiguration().orientation)
+                .isEqualTo(
+                        orientationPortrait
+                                ? Configuration.ORIENTATION_PORTRAIT
+                                : Configuration.ORIENTATION_LANDSCAPE);
+        EditorInfo editorInfo = mInputMethodService.getCurrentInputEditorInfo();
+        assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_FULLSCREEN).isEqualTo(0);
+        assertThat(editorInfo.internalImeOptions & EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT)
+                .isEqualTo(
+                        orientationPortrait ? EditorInfo.IME_INTERNAL_FLAG_APP_WINDOW_PORTRAIT : 0);
+        assertThat(mInputMethodService.onEvaluateFullscreenMode()).isEqualTo(!orientationPortrait);
+        assertThat(mInputMethodService.isFullscreenMode()).isEqualTo(!orientationPortrait);
+
+        mUiDevice.pressBack();
+    }
+
+    private void prepareIme() throws Exception {
+        executeShellCommand("ime enable " + mInputMethodId);
+        executeShellCommand("ime set " + mInputMethodId);
+        mInstrumentation.waitForIdleSync();
+        Log.i(TAG, "Finish preparing IME");
+    }
+
+    private void prepareEditor() {
+        mActivity = TestActivity.start(mInstrumentation);
+        mEditText = mActivity.mEditText;
+        Log.i(TAG, "Finish preparing activity with editor.");
+    }
+
+    private String getInputMethodId() {
+        return mTargetPackageName + "/" + INPUT_METHOD_SERVICE_NAME;
+    }
+
+    private String executeShellCommand(String cmd) throws Exception {
+        Log.i(TAG, "Run command: " + cmd);
+        return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
+                .executeShellCommand(cmd);
+    }
+
+    private void clickOnEditorText() {
+        // Find the editText and click it.
+        UiObject2 editTextUiObject =
+                mUiDevice.wait(
+                        Until.findObject(By.desc(EDIT_TEXT_DESC)),
+                        TimeUnit.SECONDS.toMillis(TIMEOUT_IN_SECONDS));
+        assertThat(editTextUiObject).isNotNull();
+        editTextUiObject.click();
+        mInstrumentation.waitForIdleSync();
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
new file mode 100644
index 0000000..ef50476
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/Android.bp
@@ -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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test_helper_app {
+    name: "SimpleTestIme",
+
+    srcs: [
+        "src/com/android/apps/inputmethod/simpleime/*.java",
+    ],
+
+    static_libs: [
+        "SimpleImeImsLib",
+        "SimpleImeTestingLib",
+    ],
+    resource_dirs: ["res"],
+    manifest: "AndroidManifest.xml",
+
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    export_package_resources: true,
+    sdk_version: "current",
+}
+
+android_library {
+    name: "SimpleImeImsLib",
+    srcs: [
+        "src/com/android/apps/inputmethod/simpleime/ims/*.java",
+    ],
+    sdk_version: "current",
+}
+
+android_library {
+    name: "SimpleImeTestingLib",
+    srcs: [
+        "src/com/android/apps/inputmethod/simpleime/testing/*.java",
+    ],
+    sdk_version: "current",
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
new file mode 100644
index 0000000..802caf1
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/AndroidManifest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.apps.inputmethod.simpleime">
+
+    <uses-sdk android:targetSdkVersion="31" />
+
+    <application android:debuggable="true"
+                 android:label="@string/app_name">
+        <service
+            android:name="com.android.apps.inputmethod.simpleime.SimpleInputMethodService"
+            android:label="@string/app_name"
+            android:directBootAware="true"
+            android:permission="android.permission.BIND_INPUT_METHOD"
+            android:exported="true">
+
+            <meta-data
+                android:name="android.view.im"
+                android:resource="@xml/method"/>
+
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+        </service>
+
+        <!-- This is for test only. -->
+        <activity android:name="com.android.apps.inputmethod.simpleime.testing.TestActivity"
+                  android:exported="false"
+                  android:label="TestActivity"
+                  android:launchMode="singleInstance"
+                  android:excludeFromRecents="true"
+                  android:noHistory="true"
+                  android:taskAffinity="">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
new file mode 100644
index 0000000..dbfcc30
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/drawable/key_border.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle" >
+    <solid
+        android:color="#FAFAFA" >
+    </solid>
+    <stroke
+        android:width="1dp"
+        android:color="#0F000000" >
+    </stroke>
+    <corners
+        android:radius="2dp"   >
+    </corners>
+</shape>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
new file mode 100644
index 0000000..f229270
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/input_view.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/input"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"/>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
new file mode 100644
index 0000000..ee94ea9
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/layout/qwerty_10_9_9.xml
@@ -0,0 +1,200 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    style="@style/KeyboardArea">
+
+    <View style="@style/KeyboardRow.Header"/>
+
+    <LinearLayout style="@style/KeyboardRow">
+        <TextView
+            android:id="@+id/key_pos_0_0"
+            android:text="q"
+            android:tag="KEYCODE_Q"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_1"
+            android:text="w"
+            android:tag="KEYCODE_W"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_2"
+            android:text="e"
+            android:tag="KEYCODE_E"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_3"
+            android:text="r"
+            android:tag="KEYCODE_R"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_4"
+            android:text="t"
+            android:tag="KEYCODE_T"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_5"
+            android:text="y"
+            android:tag="KEYCODE_Y"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_6"
+            android:text="u"
+            android:tag="KEYCODE_U"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_7"
+            android:text="i"
+            android:tag="KEYCODE_I"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_8"
+            android:text="o"
+            android:tag="KEYCODE_O"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_0_9"
+            android:text="p"
+            android:tag="KEYCODE_P"
+            style="@style/SoftKey"/>
+    </LinearLayout>
+
+    <LinearLayout style="@style/KeyboardRow">
+        <TextView
+            android:id="@+id/key_pos_1_0"
+            android:text="a"
+            android:tag="KEYCODE_A"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_1"
+            android:text="s"
+            android:tag="KEYCODE_S"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_2"
+            android:text="d"
+            android:tag="KEYCODE_D"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_3"
+            android:text="f"
+            android:tag="KEYCODE_F"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_4"
+            android:text="g"
+            android:tag="KEYCODE_G"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_5"
+            android:text="h"
+            android:tag="KEYCODE_H"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_6"
+            android:text="j"
+            android:tag="KEYCODE_J"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_7"
+            android:text="k"
+            android:tag="KEYCODE_K"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_1_8"
+            android:text="l"
+            android:tag="KEYCODE_L"
+            style="@style/SoftKey"/>
+    </LinearLayout>
+
+    <LinearLayout style="@style/KeyboardRow">
+        <TextView
+            android:id="@+id/key_pos_shift"
+            android:text="SHI"
+            android:tag="KEYCODE_SHIFT"
+            style="@style/SoftKey.Function"/>
+        <TextView
+            android:id="@+id/key_pos_2_0"
+            android:text="z"
+            android:tag="KEYCODE_Z"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_2_1"
+            android:text="x"
+            android:tag="KEYCODE_X"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_2_2"
+            style="@style/SoftKey"
+            android:text="c"
+            android:tag="KEYCODE_C"/>
+        <TextView
+            android:id="@+id/key_pos_2_3"
+            android:text="v"
+            android:tag="KEYCODE_V"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_2_4"
+            android:text="b"
+            android:tag="KEYCODE_B"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_2_5"
+            android:text="n"
+            android:tag="KEYCODE_N"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_2_6"
+            android:text="m"
+            android:tag="KEYCODE_M"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_del"
+            android:text="DEL"
+            android:tag="KEYCODE_DEL"
+            style="@style/SoftKey.Function"/>
+    </LinearLayout>
+
+    <LinearLayout style="@style/KeyboardRow">
+        <TextView
+            android:id="@+id/key_pos_symbol"
+            android:text="TAB"
+            android:tag="KEYCODE_TAB"
+            style="@style/SoftKey.Function"/>
+        <TextView
+            android:id="@+id/key_pos_comma"
+            android:text=","
+            android:tag="KEYCODE_COMMA"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_space"
+            android:text="SPACE"
+            android:tag="KEYCODE_SPACE"
+            style="@style/SoftKey.Space"/>
+        <TextView
+            android:id="@+id/key_pos_period"
+            android:text="."
+            android:tag="KEYCODE_PERIOD"
+            style="@style/SoftKey"/>
+        <TextView
+            android:id="@+id/key_pos_enter"
+            android:text="ENT"
+            android:tag="KEYCODE_ENTER"
+            style="@style/SoftKey.Function.Bottom"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
new file mode 100644
index 0000000..1a4959e
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <dimen name="text_size_normal">24dp</dimen>
+    <dimen name="text_size_symbol">14dp</dimen>
+
+    <dimen name="keyboard_header_height">40dp</dimen>
+    <dimen name="keyboard_row_height">50dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
new file mode 100644
index 0000000..11377fa
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <string name="app_name">Fake IME</string>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
new file mode 100644
index 0000000..83f7bc3
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/values/styles.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<resources>
+    <style name="KeyboardArea">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_gravity">bottom</item>
+        <item name="android:orientation">vertical</item>
+        <item name="android:background">#FFFFFFFF</item>
+    </style>
+
+    <style name="KeyboardRow">
+        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_height">@dimen/keyboard_row_height</item>
+        <item name="android:orientation">horizontal</item>
+    </style>
+
+    <style name="KeyboardRow.Header">
+        <item name="android:layout_height">@dimen/keyboard_header_height</item>
+        <item name="android:background">#FFEEEEEE</item>
+    </style>
+
+    <style name="SoftKey">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">match_parent</item>
+        <item name="android:layout_weight">2</item>
+        <item name="android:gravity">center</item>
+        <item name="android:textColor">#FF000000</item>
+        <item name="android:textSize">@dimen/text_size_normal</item>
+        <item name="android:fontFamily">roboto-regular</item>
+        <item name="android:background">@drawable/key_border</item>
+    </style>
+
+    <style name="SoftKey.Function">
+        <item name="android:layout_weight">3</item>
+        <item name="android:textColor">#FF333333</item>
+        <item name="android:textSize">@dimen/text_size_symbol</item>
+    </style>
+
+    <style name="SoftKey.Function.Bottom">
+        <item name="android:layout_weight">3</item>
+        <item name="android:textColor">#FF333333</item>
+        <item name="android:textSize">@dimen/text_size_symbol</item>
+    </style>
+
+    <style name="SoftKey.Space">
+        <item name="android:layout_weight">10</item>
+        <item name="android:textColor">#FF333333</item>
+        <item name="android:textSize">@dimen/text_size_symbol</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
new file mode 100644
index 0000000..872b068
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/res/xml/method.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+    <subtype
+        android:label="FakeIme"
+        android:imeSubtypeLocale="en_US"
+        android:imeSubtypeMode="keyboard"/>
+</input-method>
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
new file mode 100644
index 0000000..990fa24
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/KeyCodeConstants.java
@@ -0,0 +1,68 @@
+/*
+ * 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.apps.inputmethod.simpleime;
+
+import android.view.KeyEvent;
+
+import java.util.HashMap;
+
+/** Holder of key codes and their name. */
+public final class KeyCodeConstants {
+    private KeyCodeConstants() {}
+
+    static final HashMap<String, Integer> KEY_NAME_TO_CODE_MAP = new HashMap<>();
+
+    static {
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_A", KeyEvent.KEYCODE_A);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_B", KeyEvent.KEYCODE_B);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_C", KeyEvent.KEYCODE_C);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_D", KeyEvent.KEYCODE_D);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_E", KeyEvent.KEYCODE_E);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_F", KeyEvent.KEYCODE_F);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_G", KeyEvent.KEYCODE_G);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_H", KeyEvent.KEYCODE_H);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_I", KeyEvent.KEYCODE_I);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_J", KeyEvent.KEYCODE_J);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_K", KeyEvent.KEYCODE_K);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_L", KeyEvent.KEYCODE_L);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_M", KeyEvent.KEYCODE_M);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_N", KeyEvent.KEYCODE_N);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_O", KeyEvent.KEYCODE_O);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_P", KeyEvent.KEYCODE_P);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Q", KeyEvent.KEYCODE_Q);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_R", KeyEvent.KEYCODE_R);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_S", KeyEvent.KEYCODE_S);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_T", KeyEvent.KEYCODE_T);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_U", KeyEvent.KEYCODE_U);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_V", KeyEvent.KEYCODE_V);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_W", KeyEvent.KEYCODE_W);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_X", KeyEvent.KEYCODE_X);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Y", KeyEvent.KEYCODE_Y);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_Z", KeyEvent.KEYCODE_Z);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_SHIFT", KeyEvent.KEYCODE_SHIFT_LEFT);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_DEL", KeyEvent.KEYCODE_DEL);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_SPACE", KeyEvent.KEYCODE_SPACE);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_ENTER", KeyEvent.KEYCODE_ENTER);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_COMMA", KeyEvent.KEYCODE_COMMA);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_PERIOD", KeyEvent.KEYCODE_PERIOD);
+        KEY_NAME_TO_CODE_MAP.put("KEYCODE_TAB", KeyEvent.KEYCODE_TAB);
+    }
+
+    public static boolean isAlphaKeyCode(int keyCode) {
+        return keyCode >= KeyEvent.KEYCODE_A && keyCode <= KeyEvent.KEYCODE_Z;
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
new file mode 100644
index 0000000..48942a3
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleInputMethodService.java
@@ -0,0 +1,67 @@
+/*
+ * 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.apps.inputmethod.simpleime;
+
+import android.inputmethodservice.InputMethodService;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.widget.FrameLayout;
+
+import com.android.apps.inputmethod.simpleime.ims.InputMethodServiceWrapper;
+
+/** The {@link InputMethodService} implementation for SimpeTestIme app. */
+public class SimpleInputMethodService extends InputMethodServiceWrapper {
+    private static final String TAG = "SimpleIMS";
+
+    private FrameLayout mInputView;
+
+    @Override
+    public View onCreateInputView() {
+        Log.i(TAG, "onCreateInputView()");
+        mInputView = (FrameLayout) LayoutInflater.from(this).inflate(R.layout.input_view, null);
+        return mInputView;
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo info, boolean restarting) {
+        super.onStartInputView(info, restarting);
+        mInputView.removeAllViews();
+        SimpleKeyboard keyboard = new SimpleKeyboard(this, R.layout.qwerty_10_9_9);
+        mInputView.addView(keyboard.inflateKeyboardView(LayoutInflater.from(this), mInputView));
+    }
+
+    void handle(String data, int keyboardState) {
+        InputConnection inputConnection = getCurrentInputConnection();
+        Integer keyCode = KeyCodeConstants.KEY_NAME_TO_CODE_MAP.get(data);
+        Log.v(TAG, "keyCode: " + keyCode);
+        if (keyCode != null) {
+            inputConnection.sendKeyEvent(
+                    new KeyEvent(
+                            SystemClock.uptimeMillis(),
+                            SystemClock.uptimeMillis(),
+                            KeyEvent.ACTION_DOWN,
+                            keyCode,
+                            0,
+                            KeyCodeConstants.isAlphaKeyCode(keyCode) ? keyboardState : 0));
+        }
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.java
new file mode 100644
index 0000000..b16ec9eb
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/SimpleKeyboard.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.
+ */
+
+package com.android.apps.inputmethod.simpleime;
+
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/** Controls the visible virtual keyboard view. */
+final class SimpleKeyboard {
+    private static final String TAG = "SimpleKeyboard";
+
+    private static final int[] SOFT_KEY_IDS =
+            new int[] {
+                R.id.key_pos_0_0,
+                R.id.key_pos_0_1,
+                R.id.key_pos_0_2,
+                R.id.key_pos_0_3,
+                R.id.key_pos_0_4,
+                R.id.key_pos_0_5,
+                R.id.key_pos_0_6,
+                R.id.key_pos_0_7,
+                R.id.key_pos_0_8,
+                R.id.key_pos_0_9,
+                R.id.key_pos_1_0,
+                R.id.key_pos_1_1,
+                R.id.key_pos_1_2,
+                R.id.key_pos_1_3,
+                R.id.key_pos_1_4,
+                R.id.key_pos_1_5,
+                R.id.key_pos_1_6,
+                R.id.key_pos_1_7,
+                R.id.key_pos_1_8,
+                R.id.key_pos_2_0,
+                R.id.key_pos_2_1,
+                R.id.key_pos_2_2,
+                R.id.key_pos_2_3,
+                R.id.key_pos_2_4,
+                R.id.key_pos_2_5,
+                R.id.key_pos_2_6,
+                R.id.key_pos_shift,
+                R.id.key_pos_del,
+                R.id.key_pos_symbol,
+                R.id.key_pos_comma,
+                R.id.key_pos_space,
+                R.id.key_pos_period,
+                R.id.key_pos_enter,
+            };
+
+    private final SimpleInputMethodService mSimpleInputMethodService;
+    private final int mViewResId;
+    private final SparseArray<TextView> mSoftKeyViews = new SparseArray<>();
+    private View mKeyboardView;
+    private int mKeyboardState;
+
+    SimpleKeyboard(SimpleInputMethodService simpleInputMethodService, int viewResId) {
+        this.mSimpleInputMethodService = simpleInputMethodService;
+        this.mViewResId = viewResId;
+        this.mKeyboardState = 0;
+    }
+
+    View inflateKeyboardView(LayoutInflater inflater, ViewGroup inputView) {
+        mKeyboardView = inflater.inflate(mViewResId, inputView, false);
+        mapSoftKeys();
+        return mKeyboardView;
+    }
+
+    private void mapSoftKeys() {
+        for (int id : SOFT_KEY_IDS) {
+            TextView softKeyView = mKeyboardView.findViewById(id);
+            mSoftKeyViews.put(id, softKeyView);
+            String tagData = softKeyView.getTag() != null ? softKeyView.getTag().toString() : null;
+            softKeyView.setOnClickListener(v -> handle(tagData));
+        }
+    }
+
+    private void handle(String data) {
+        Log.i(TAG, "handle(): " + data);
+        if (TextUtils.isEmpty(data)) {
+            return;
+        }
+        if ("KEYCODE_SHIFT".equals(data)) {
+            handleShift();
+            return;
+        }
+
+        mSimpleInputMethodService.handle(data, mKeyboardState);
+    }
+
+    private void handleShift() {
+        mKeyboardState = toggleShiftState(mKeyboardState);
+        Log.v(TAG, "currentKeyboardState: " + mKeyboardState);
+        boolean isShiftOn = isShiftOn(mKeyboardState);
+        for (int i = 0; i < mSoftKeyViews.size(); i++) {
+            TextView softKeyView = mSoftKeyViews.valueAt(i);
+            softKeyView.setAllCaps(isShiftOn);
+        }
+    }
+
+    private static boolean isShiftOn(int state) {
+        return (state & KeyEvent.META_SHIFT_ON) == KeyEvent.META_SHIFT_ON;
+    }
+
+    private static int toggleShiftState(int state) {
+        return state ^ KeyEvent.META_SHIFT_ON;
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
new file mode 100644
index 0000000..b706a65
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/ims/InputMethodServiceWrapper.java
@@ -0,0 +1,102 @@
+/*
+ * 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.apps.inputmethod.simpleime.ims;
+
+import android.content.res.Configuration;
+import android.inputmethodservice.InputMethodService;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+
+import java.util.concurrent.CountDownLatch;
+
+/** Wrapper of {@link InputMethodService} to expose interfaces for testing purpose. */
+public class InputMethodServiceWrapper extends InputMethodService {
+    private static final String TAG = "InputMethodServiceWrapper";
+
+    private static InputMethodServiceWrapper sInputMethodServiceWrapper;
+
+    public static InputMethodServiceWrapper getInputMethodServiceWrapperForTesting() {
+        return sInputMethodServiceWrapper;
+    }
+
+    private boolean mInputViewStarted;
+    private CountDownLatch mCountDownLatchForTesting;
+
+    public boolean getCurrentInputViewStarted() {
+        return mInputViewStarted;
+    }
+
+    public void setCountDownLatchForTesting(CountDownLatch countDownLatchForTesting) {
+        mCountDownLatchForTesting = countDownLatchForTesting;
+    }
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreate()");
+        super.onCreate();
+        sInputMethodServiceWrapper = this;
+    }
+
+    @Override
+    public void onStartInput(EditorInfo info, boolean restarting) {
+        Log.i(TAG, "onStartInput() editor=" + info + ", restarting=" + restarting);
+        super.onStartInput(info, restarting);
+    }
+
+    @Override
+    public void onStartInputView(EditorInfo info, boolean restarting) {
+        Log.i(TAG, "onStartInputView() editor=" + info + ", restarting=" + restarting);
+        super.onStartInputView(info, restarting);
+        mInputViewStarted = true;
+        if (mCountDownLatchForTesting != null) {
+            mCountDownLatchForTesting.countDown();
+        }
+    }
+
+    @Override
+    public void onFinishInput() {
+        Log.i(TAG, "onFinishInput()");
+        super.onFinishInput();
+    }
+
+    @Override
+    public void onFinishInputView(boolean finishingInput) {
+        Log.i(TAG, "onFinishInputView()");
+        super.onFinishInputView(finishingInput);
+        mInputViewStarted = false;
+
+        if (mCountDownLatchForTesting != null) {
+            mCountDownLatchForTesting.countDown();
+        }
+    }
+
+    @Override
+    public void requestHideSelf(int flags) {
+        Log.i(TAG, "requestHideSelf() " + flags);
+        super.requestHideSelf(flags);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        Log.i(TAG, "onConfigurationChanged() " + newConfig);
+        super.onConfigurationChanged(newConfig);
+
+        if (mCountDownLatchForTesting != null) {
+            mCountDownLatchForTesting.countDown();
+        }
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
new file mode 100644
index 0000000..0eec7e6
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/test-apps/SimpleTestIme/src/com/android/apps/inputmethod/simpleime/testing/TestActivity.java
@@ -0,0 +1,105 @@
+/*
+ * 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.apps.inputmethod.simpleime.testing;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+/**
+ * A special activity for testing purpose.
+ *
+ * <p>This is used when the instruments package is SimpleTestIme, as the Intent needs to be started
+ * in the instruments package. More details see {@link
+ * Instrumentation#startActivitySync(Intent)}.</>
+ */
+public class TestActivity extends Activity {
+    private static final String TAG = "TestActivity";
+
+    /**
+     * Start a new test activity with an editor and wait for it to begin running before returning.
+     *
+     * @param instrumentation application instrumentation
+     * @return the newly started activity
+     */
+    public static TestActivity start(Instrumentation instrumentation) {
+        Intent intent =
+                new Intent()
+                        .setAction(Intent.ACTION_MAIN)
+                        .setClass(instrumentation.getTargetContext(), TestActivity.class)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        return (TestActivity) instrumentation.startActivitySync(intent);
+    }
+
+    public EditText mEditText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+        LinearLayout rootView = new LinearLayout(this);
+        mEditText = new EditText(this);
+        mEditText.setContentDescription("Input box");
+        rootView.addView(mEditText, new LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+        setContentView(rootView);
+        mEditText.requestFocus();
+        super.onCreate(savedInstanceState);
+    }
+
+    /** Shows soft keyboard via InputMethodManager. */
+    public boolean showImeWithInputMethodManager(int flags) {
+        InputMethodManager imm = getSystemService(InputMethodManager.class);
+        boolean result = imm.showSoftInput(mEditText, flags);
+        Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+        return result;
+    }
+
+    /** Shows soft keyboard via WindowInsetsController. */
+    public boolean showImeWithWindowInsetsController() {
+        WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+        windowInsetsController.show(WindowInsets.Type.ime());
+        Log.i(TAG, "showIme() via WindowInsetsController");
+        return true;
+    }
+
+    /** Hides soft keyboard via InputMethodManager. */
+    public boolean hideImeWithInputMethodManager(int flags) {
+        InputMethodManager imm = getSystemService(InputMethodManager.class);
+        boolean result = imm.hideSoftInputFromWindow(mEditText.getWindowToken(), flags);
+        Log.i(TAG, "hideIme() via InputMethodManager, result=" + result);
+        return result;
+    }
+
+    /** Hides soft keyboard via WindowInsetsController. */
+    public boolean hideImeWithWindowInsetsController() {
+        WindowInsetsController windowInsetsController = mEditText.getWindowInsetsController();
+        windowInsetsController.hide(WindowInsets.Type.ime());
+        Log.i(TAG, "hideIme() via WindowInsetsController");
+        return true;
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index ebd6b64..cc26593 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -94,6 +94,7 @@
         "libunwindstack",
         "libutils",
         "netd_aidl_interface-V5-cpp",
+        "libservices.core.settings.testonly",
     ],
 
     dxflags: ["--multi-dex"],
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
index 3727d66..b3f64b6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/PackageManagerSettingsTests.java
@@ -111,6 +111,10 @@
     private static final String PACKAGE_NAME_3 = "com.android.app3";
     private static final int TEST_RESOURCE_ID = 2131231283;
 
+    static {
+        System.loadLibrary("services.core.settings.testonly");
+    }
+
     @Mock
     RuntimePermissionsPersistence mRuntimePermissionsPersistence;
     @Mock
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
index faa2352..5f26d6f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/PackageManagerLocalSnapshotTest.kt
@@ -53,6 +53,11 @@
         snapshot.use {
             val packageStates = it.packageStates
 
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
             // Check contents
             assertThat(packageStates).containsExactly(
                 packageStateAll.packageName, packageStateAll,
@@ -78,9 +83,14 @@
             assertThat(filteredOne.getPackageState(packageStateUser10.packageName)).isNull()
 
             filteredThree.use {
-                val statesList = mutableListOf<PackageState>()
-                assertThat(it.forAllPackageStates { statesList += it })
-                assertThat(statesList).containsExactly(packageStateAll, packageStateUser10)
+                // Check for unmodifiable
+                assertFailsWith(UnsupportedOperationException::class) {
+                    it.packageStates.clear()
+                }
+                assertThat(it.packageStates).containsExactly(
+                    packageStateAll.packageName, packageStateAll,
+                    packageStateUser10.packageName, packageStateUser10,
+                )
             }
 
             // Call after child close, parent open fails
@@ -96,7 +106,7 @@
 
         // Call after close fails
         assertClosedFailure { snapshot.packageStates }
-        assertClosedFailure { filteredOne.forAllPackageStates {} }
+        assertClosedFailure { filteredOne.packageStates }
         assertClosedFailure {
             filteredTwo.getPackageState(packageStateAll.packageName)
         }
@@ -116,9 +126,15 @@
                 .isEqualTo(packageStateUser0)
             assertThat(it.getPackageState(packageStateUser10.packageName)).isNull()
 
-            val statesList = mutableListOf<PackageState>()
-            assertThat(it.forAllPackageStates { statesList += it })
-            assertThat(statesList).containsExactly(packageStateAll, packageStateUser0)
+            // Check for unmodifiable
+            assertFailsWith(UnsupportedOperationException::class) {
+                it.packageStates.clear()
+            }
+
+            assertThat(it.packageStates).containsExactly(
+                packageStateAll.packageName, packageStateAll,
+                packageStateUser0.packageName, packageStateUser0,
+            )
         }
 
         // Call after close fails
diff --git a/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml
new file mode 100644
index 0000000..1dde7dc
--- /dev/null
+++ b/services/tests/mockingservicestests/assets/AppOpsUpgradeTest/appops-version-1.xml
@@ -0,0 +1,781 @@
+<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>

+<app-ops v="1">

+  <uid n="1001">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="15" m="0" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="1002">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="15" m="0" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10077">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10079">

+    <op n="116" m="1" />

+  </uid>

+  <uid n="10080">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10081">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10086">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10087">

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10090">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+  </uid>

+  <uid n="10096">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10112">

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10113">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10114">

+    <op n="4" m="1" />

+  </uid>

+  <uid n="10115">

+    <op n="0" m="1" />

+  </uid>

+  <uid n="10116">

+    <op n="0" m="1" />

+  </uid>

+  <uid n="10117">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="13" m="1" />

+    <op n="14" m="1" />

+    <op n="16" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10118">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10119">

+    <op n="11" m="1" />

+    <op n="77" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10120">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="6" m="1" />

+    <op n="7" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="54" m="1" />

+    <op n="59" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10121">

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10122">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10123">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10124">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+  </uid>

+  <uid n="10125">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10127">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="65" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10129">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10130">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10131">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10132">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="69" m="1" />

+    <op n="79" m="1" />

+  </uid>

+  <uid n="10133">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10136">

+    <op n="0" m="1" />

+    <op n="4" m="1" />

+    <op n="77" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+    <op n="114" m="1" />

+  </uid>

+  <uid n="10137">

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10138">

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10140">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10141">

+    <op n="11" m="1" />

+    <op n="27" m="1" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10142">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10144">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="27" m="4" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10145">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10149">

+    <op n="11" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10150">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10151">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10152">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10154">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10155">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+  </uid>

+  <uid n="10157">

+    <op n="13" m="1" />

+  </uid>

+  <uid n="10158">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10160">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10161">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="77" m="1" />

+    <op n="87" m="1" />

+    <op n="111" m="1" />

+    <op n="114" m="1" />

+  </uid>

+  <uid n="10162">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="15" m="0" />

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+    <op n="89" m="0" />

+  </uid>

+  <uid n="10163">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="56" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10164">

+    <op n="26" m="1" />

+    <op n="27" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="69" m="1" />

+    <op n="79" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10169">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10170">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10171">

+    <op n="26" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10172">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10173">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="23" m="0" />

+    <op n="26" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+    <op n="65" m="1" />

+  </uid>

+  <uid n="10175">

+    <op n="0" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+  </uid>

+  <uid n="10178">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="27" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10179">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10180">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10181">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="1110181">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="107" m="2" />

+  </uid>

+  <uid n="10182">

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10183">

+    <op n="11" m="1" />

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10184">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="4" />

+  </uid>

+  <uid n="10185">

+    <op n="8" m="1" />

+    <op n="59" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10187">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10189">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10190">

+    <op n="0" m="1" />

+    <op n="13" m="1" />

+  </uid>

+  <uid n="10191">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10192">

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10193">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10197">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10198">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="77" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="90" m="1" />

+    <op n="107" m="0" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10199">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10200">

+    <op n="11" m="1" />

+    <op n="65" m="1" />

+    <op n="107" m="1" />

+  </uid>

+  <uid n="1110200">

+    <op n="11" m="1" />

+    <op n="65" m="1" />

+    <op n="107" m="2" />

+  </uid>

+  <uid n="10201">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="62" m="1" />

+    <op n="84" m="0" />

+    <op n="86" m="0" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10206">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+    <op n="26" m="4" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10209">

+    <op n="11" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10210">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10212">

+    <op n="11" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10214">

+    <op n="26" m="4" />

+  </uid>

+  <uid n="10216">

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10225">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10229">

+    <op n="11" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10231">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10232">

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10234">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="11" m="1" />

+    <op n="13" m="1" />

+    <op n="20" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10235">

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10237">

+    <op n="0" m="4" />

+    <op n="1" m="4" />

+  </uid>

+  <uid n="10238">

+    <op n="26" m="4" />

+    <op n="27" m="4" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10240">

+    <op n="112" m="1" />

+  </uid>

+  <uid n="10241">

+    <op n="59" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10245">

+    <op n="13" m="1" />

+    <op n="51" m="1" />

+  </uid>

+  <uid n="10247">

+    <op n="0" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="0" />

+    <op n="90" m="1" />

+  </uid>

+  <uid n="10254">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10255">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10256">

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10258">

+    <op n="11" m="1" />

+  </uid>

+  <uid n="10260">

+    <op n="51" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <uid n="10262">

+    <op n="15" m="0" />

+  </uid>

+  <uid n="10266">

+    <op n="0" m="4" />

+  </uid>

+  <uid n="10267">

+    <op n="0" m="1" />

+    <op n="1" m="1" />

+    <op n="4" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="62" m="1" />

+    <op n="77" m="1" />

+    <op n="81" m="1" />

+    <op n="83" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+    <op n="107" m="2" />

+    <op n="111" m="1" />

+  </uid>

+  <uid n="10268">

+    <op n="4" m="1" />

+    <op n="11" m="1" />

+    <op n="62" m="1" />

+  </uid>

+  <uid n="10269">

+    <op n="11" m="1" />

+    <op n="26" m="1" />

+    <op n="27" m="1" />

+    <op n="59" m="1" />

+    <op n="60" m="1" />

+    <op n="85" m="1" />

+    <op n="87" m="1" />

+  </uid>

+  <pkg n="com.google.android.iwlan">

+    <uid n="0">

+      <op n="1" />

+      <op n="75" m="0" />

+    </uid>

+  </pkg>

+  <pkg n="com.android.phone">

+    <uid n="0">

+      <op n="1" />

+      <op n="75" m="0" />

+    </uid>

+  </pkg>

+  <pkg n="android">

+    <uid n="1000">

+      <op n="0">

+        <st n="214748364801" t="1670287941040" />

+      </op>

+      <op n="4">

+        <st n="214748364801" t="1670289665522" />

+      </op>

+      <op n="6">

+        <st n="214748364801" t="1670287946650" />

+      </op>

+      <op n="8">

+        <st n="214748364801" t="1670289624396" />

+      </op>

+      <op n="14">

+        <st n="214748364801" t="1670287951031" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670291786337" d="156" />

+      </op>

+      <op n="41">

+        <st id="SensorNotificationService" n="214748364801" t="1670287585567" d="4251183" />

+        <st id="CountryDetector" n="214748364801" t="1670287583306" d="6700" />

+      </op>

+      <op n="43">

+        <st n="214748364801" r="1670291755062" />

+      </op>

+      <op n="61">

+        <st n="214748364801" r="1670291754997" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291473903" />

+        <st id="GnssService" n="214748364801" r="1670288044920" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670291441554" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.server.telecom">

+    <uid n="1000">

+      <op n="6">

+        <st n="214748364801" t="1670287609092" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670287583728" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.settings">

+    <uid n="1000">

+      <op n="43">

+        <st n="214748364801" r="1670291447349" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291399231" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670291756910" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.phone">

+    <uid n="1001">

+      <op n="15">

+        <st n="214748364801" t="1670287951022" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670291786177" />

+      </op>

+      <op n="105">

+        <st n="214748364801" r="1670291756403" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.bluetooth">

+    <uid n="1002">

+      <op n="4">

+        <st n="214748364801" t="1670289671076" />

+      </op>

+      <op n="40">

+        <st n="214748364801" t="1670287585676" d="8" />

+      </op>

+      <op n="43">

+        <st n="214748364801" r="1670287585818" />

+      </op>

+      <op n="77">

+        <st n="214748364801" t="1670288037629" />

+      </op>

+      <op n="111">

+        <st n="214748364801" t="1670287592081" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.vending">

+    <uid n="10136">

+      <op n="40">

+        <st n="429496729601" t="1670289621210" d="114" />

+        <st n="858993459201" t="1670289879730" d="349" />

+        <st n="1288490188801" t="1670287942622" d="937" />

+      </op>

+      <op n="43">

+        <st n="429496729601" r="1670289755305" />

+        <st n="858993459201" r="1670288019246" />

+        <st n="1073741824001" r="1670289571783" />

+        <st n="1288490188801" r="1670289373336" />

+      </op>

+      <op n="76">

+        <st n="429496729601" t="1670289748735" d="15991" />

+        <st n="858993459201" t="1670291395180" d="79201" />

+        <st n="1073741824001" t="1670291395168" d="12" />

+        <st n="1288490188801" t="1670291526029" d="3" />

+        <st n="1503238553601" t="1670291526032" d="310718" />

+      </op>

+      <op n="105">

+        <st n="429496729601" r="1670289538910" />

+        <st n="858993459201" r="1670288054519" />

+        <st n="1073741824001" r="1670287599379" />

+        <st n="1288490188801" r="1670289526854" />

+        <st n="1503238553601" r="1670289528242" />

+      </op>

+    </uid>

+  </pkg>

+  <pkg n="com.android.nfc">

+    <uid n="1027">

+      <op n="40">

+        <st n="214748364801" t="1670291786330" d="22" />

+      </op>

+    </uid>

+  </pkg>

+</app-ops>
\ No newline at end of file
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index c87fd26..8d78cd6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -195,6 +195,12 @@
                 false, null, false, null);
     }
 
+    private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue,
+            BroadcastRecord record, int recordIndex, long enqueueTime) {
+        queue.enqueueOrReplaceBroadcast(record, recordIndex);
+        record.enqueueTime = enqueueTime;
+    }
+
     @Test
     public void testRunnableList_Simple() {
         assertRunnableList(List.of(), mHead);
@@ -549,29 +555,32 @@
         mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 2;
         BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
                 PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        long timeCounter = 100;
 
         // mix of broadcasts, with more than 2 fg/urgent
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)), 0);
-        queue.enqueueOrReplaceBroadcast(
-                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
-        queue.enqueueOrReplaceBroadcast(
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
-                        optInteractive), 0);
-        queue.enqueueOrReplaceBroadcast(
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
-                        optInteractive), 0);
-        queue.enqueueOrReplaceBroadcast(
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
-                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
-        queue.enqueueOrReplaceBroadcast(
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
                 makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
-                        optInteractive), 0);
+                        optInteractive), 0, timeCounter++);
 
         queue.makeActiveNextPending();
         assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
@@ -592,6 +601,133 @@
     }
 
     /**
+     * Verify that offload broadcasts are not starved because of broadcasts in higher priority
+     * queues.
+     */
+    @Test
+    public void testOffloadStarvation() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        mConstants.MAX_CONSECUTIVE_URGENT_DISPATCHES = 1;
+        mConstants.MAX_CONSECUTIVE_NORMAL_DISPATCHES = 2;
+        final BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        long timeCounter = 100;
+
+        // mix of broadcasts, with more than 2 normal
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_APPLICATION_PREFERENCES),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_INPUT_METHOD_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_NEW_OUTGOING_CALL),
+                        optInteractive), 0, timeCounter++);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_APPLICATION_PREFERENCES, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES, again an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES and MAX_CONSECUTIVE_NORMAL_DISPATCHES,
+        // expect an offload one
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_INPUT_METHOD_CHANGED, queue.getActive().intent.getAction());
+    }
+
+    /**
+     * Verify that BroadcastProcessQueue#setPrioritizeEarliest() works as expected.
+     */
+    @Test
+    public void testPrioritizeEarliest() {
+        final BroadcastOptions optInteractive = BroadcastOptions.makeBasic();
+        optInteractive.setInteractive(true);
+
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+        queue.setPrioritizeEarliest(true);
+        long timeCounter = 100;
+
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_PACKAGE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)),
+                        0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, timeCounter++);
+        enqueueOrReplaceBroadcast(queue,
+                makeBroadcastRecord(new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE),
+                        optInteractive), 0, timeCounter++);
+
+        // When we mark BroadcastProcessQueue to prioritize earliest, we should
+        // expect to dispatch broadcasts in the order they were enqueued
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_BOOT_COMPLETED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        // after MAX_CONSECUTIVE_URGENT_DISPATCHES expect an ordinary one next
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_PACKAGE_CHANGED, queue.getActive().intent.getAction());
+        // and then back to prioritizing urgent ones
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        // verify the reset-count-then-resume worked too
+        queue.makeActiveNextPending();
+        assertEquals(AppWidgetManager.ACTION_APPWIDGET_UPDATE,
+                queue.getActive().intent.getAction());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index a8d8945..d3fa92c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -647,15 +647,15 @@
         }
     }
 
-    private void checkReportedOptedInGameModes(GameManagerService gameManagerService,
-            int... requiredOptedInModes) {
-        Arrays.sort(requiredOptedInModes);
-        // check GetModeInfo.getOptedInGameModes
+    private void checkReportedOverriddenGameModes(GameManagerService gameManagerService,
+            int... requiredOverriddenModes) {
+        Arrays.sort(requiredOverriddenModes);
+        // check GetModeInfo.getOverriddenGameModes
         GameModeInfo info = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
         assertNotNull(info);
-        int[] optedInModes = info.getOptedInGameModes();
-        Arrays.sort(optedInModes);
-        assertArrayEquals(requiredOptedInModes, optedInModes);
+        int[] overriddenModes = info.getOverriddenGameModes();
+        Arrays.sort(overriddenModes);
+        assertArrayEquals(requiredOverriddenModes, overriddenModes);
     }
 
     private void checkDownscaling(GameManagerService gameManagerService,
@@ -697,7 +697,7 @@
         assertEquals(fps, config.getGameModeConfiguration(gameMode).getFps());
     }
 
-    private boolean checkOptedIn(GameManagerService gameManagerService, int gameMode) {
+    private boolean checkOverridden(GameManagerService gameManagerService, int gameMode) {
         GameManagerService.GamePackageConfiguration config =
                 gameManagerService.getConfig(mPackageName, USER_ID_1);
         return config.willGamePerformOptimizations(gameMode);
@@ -870,8 +870,8 @@
                 mTestLooper.getLooper());
         startUser(gameManagerService, USER_ID_1);
 
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
         checkFps(gameManagerService, GameManager.GAME_MODE_PERFORMANCE, 0);
 
         gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1, 3, "40",
@@ -884,9 +884,9 @@
         mockInterventionsDisabledAllOptInFromXml();
         gameManagerService.updateConfigsForUser(USER_ID_1, false, mPackageName);
 
-        assertTrue(checkOptedIn(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
+        assertTrue(checkOverridden(gameManagerService, GameManager.GAME_MODE_PERFORMANCE));
         // opt-in is still false for battery mode as override exists
-        assertFalse(checkOptedIn(gameManagerService, GameManager.GAME_MODE_BATTERY));
+        assertFalse(checkOverridden(gameManagerService, GameManager.GAME_MODE_BATTERY));
     }
 
     /**
@@ -1310,7 +1310,7 @@
         checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                 GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                 GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService);
+        checkReportedOverriddenGameModes(gameManagerService);
 
         assertEquals(new GameModeConfiguration.Builder()
                 .setFpsOverride(30)
@@ -1337,7 +1337,7 @@
         checkReportedAvailableGameModes(gameManagerService,
                 GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                 GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService);
+        checkReportedOverriddenGameModes(gameManagerService);
 
         assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1357,7 +1357,7 @@
         checkReportedAvailableGameModes(gameManagerService,
                 GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_STANDARD,
                 GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService);
+        checkReportedOverriddenGameModes(gameManagerService);
 
         assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
@@ -1382,7 +1382,7 @@
     }
 
     @Test
-    public void testGetGameModeInfoWithAllGameModesOptedIn_noDeviceConfig()
+    public void testGetGameModeInfoWithAllGameModesOverridden_noDeviceConfig()
             throws Exception {
         mockModifyGameModeGranted();
         mockInterventionsEnabledAllOptInFromXml();
@@ -1390,14 +1390,14 @@
         GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
         assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
-        verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
 
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
-    public void testGetGameModeInfoWithAllGameModesOptedIn_allDeviceConfig()
+    public void testGetGameModeInfoWithAllGameModesOverridden_allDeviceConfig()
             throws Exception {
         mockModifyGameModeGranted();
         mockInterventionsEnabledAllOptInFromXml();
@@ -1405,26 +1405,26 @@
         GameManagerService gameManagerService = createServiceAndStartUser(USER_ID_1);
         GameModeInfo gameModeInfo = gameManagerService.getGameModeInfo(mPackageName, USER_ID_1);
         assertEquals(GameManager.GAME_MODE_STANDARD, gameModeInfo.getActiveGameMode());
-        verifyAllModesOptedInAndInterventionsAvailable(gameManagerService, gameModeInfo);
+        verifyAllModesOverriddenAndInterventionsAvailable(gameManagerService, gameModeInfo);
 
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
-    private void verifyAllModesOptedInAndInterventionsAvailable(
+    private void verifyAllModesOverriddenAndInterventionsAvailable(
             GameManagerService gameManagerService,
             GameModeInfo gameModeInfo) {
         checkReportedAvailableGameModes(gameManagerService,
                 GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY,
                 GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService,
+        checkReportedOverriddenGameModes(gameManagerService,
                 GameManager.GAME_MODE_PERFORMANCE, GameManager.GAME_MODE_BATTERY);
         assertTrue(gameModeInfo.isFpsOverrideAllowed());
         assertTrue(gameModeInfo.isDownscalingAllowed());
     }
 
     @Test
-    public void testGetGameModeInfoWithBatteryModeOptedIn_withBatteryDeviceConfig()
+    public void testGetGameModeInfoWithBatteryModeOverridden_withBatteryDeviceConfig()
             throws Exception {
         mockModifyGameModeGranted();
         mockInterventionsEnabledBatteryOptInFromXml();
@@ -1435,14 +1435,14 @@
 
         checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY,
                 GameManager.GAME_MODE_STANDARD, GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
+        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_BATTERY);
 
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
     }
 
     @Test
-    public void testGetGameModeInfoWithPerformanceModeOptedIn_withAllDeviceConfig()
+    public void testGetGameModeInfoWithPerformanceModeOverridden_withAllDeviceConfig()
             throws Exception {
         mockModifyGameModeGranted();
         mockInterventionsEnabledPerformanceOptInFromXml();
@@ -1454,7 +1454,7 @@
         checkReportedAvailableGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE,
                 GameManager.GAME_MODE_BATTERY, GameManager.GAME_MODE_STANDARD,
                 GameManager.GAME_MODE_CUSTOM);
-        checkReportedOptedInGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
+        checkReportedOverriddenGameModes(gameManagerService, GameManager.GAME_MODE_PERFORMANCE);
 
         assertNotNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_BATTERY));
         assertNull(gameModeInfo.getGameModeConfiguration(GameManager.GAME_MODE_PERFORMANCE));
@@ -1993,7 +1993,7 @@
     }
 
     @Test
-    public void testResetInterventions_onGameModeOptedIn() throws Exception {
+    public void testResetInterventions_onGameModeOverridden() throws Exception {
         mockModifyGameModeGranted();
         String configStringBefore =
                 "mode=2,downscaleFactor=1.0,fps=90";
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 298dbf4..302fa0f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUpgradeTest.java
@@ -16,23 +16,32 @@
 
 package com.android.server.appop;
 
+import static android.app.AppOpsManager.OP_SCHEDULE_EXACT_ALARM;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.res.AssetManager;
 import android.os.Handler;
-import android.os.HandlerThread;
+import android.os.UserHandle;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -42,11 +51,20 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.modules.utils.TypedXmlPullParser;
+import com.android.server.LocalServices;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.File;
@@ -64,29 +82,39 @@
     private static final String TAG = AppOpsUpgradeTest.class.getSimpleName();
     private static final String APP_OPS_UNVERSIONED_ASSET_PATH =
             "AppOpsUpgradeTest/appops-unversioned.xml";
+    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 int CURRENT_VERSION = 1;
 
-    private File mAppOpsFile;
-    private Context mContext;
+    private static final Context sContext = InstrumentationRegistry.getTargetContext();
+    private static final File sAppOpsFile = new File(sContext.getFilesDir(), APP_OPS_FILENAME);
+
+    private Context mTestContext;
+    private MockitoSession mMockitoSession;
+
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private UserManagerInternal mUserManagerInternal;
+    @Mock
+    private PermissionManagerServiceInternal mPermissionManagerInternal;
+    @Mock
     private Handler mHandler;
 
-    private void extractAppOpsFile() {
-        mAppOpsFile.getParentFile().mkdirs();
-        if (mAppOpsFile.exists()) {
-            mAppOpsFile.delete();
-        }
-        try (FileOutputStream out = new FileOutputStream(mAppOpsFile);
-             InputStream in = mContext.getAssets().open(APP_OPS_UNVERSIONED_ASSET_PATH,
-                     AssetManager.ACCESS_BUFFER)) {
+    private static void extractAppOpsFile(String assetPath) {
+        sAppOpsFile.getParentFile().mkdirs();
+        try (FileOutputStream out = new FileOutputStream(sAppOpsFile);
+             InputStream in = sContext.getAssets().open(assetPath, AssetManager.ACCESS_BUFFER)) {
             byte[] buffer = new byte[4096];
             int bytesRead;
             while ((bytesRead = in.read(buffer)) >= 0) {
                 out.write(buffer, 0, bytesRead);
             }
             out.flush();
-            Log.d(TAG, "Successfully copied xml to " + mAppOpsFile.getAbsolutePath());
+            Log.d(TAG, "Successfully copied xml to " + sAppOpsFile.getAbsolutePath());
         } catch (IOException exc) {
             Log.e(TAG, "Exception while copying appops xml", exc);
             fail();
@@ -98,7 +126,7 @@
         int numberOfNonDefaultOps = 0;
         final int defaultModeOp1 = AppOpsManager.opToDefaultMode(op1);
         final int defaultModeOp2 = AppOpsManager.opToDefaultMode(op2);
-        for(int i = 0; i < uidStates.size(); i++) {
+        for (int i = 0; i < uidStates.size(); i++) {
             final AppOpsServiceImpl.UidState uidState = uidStates.valueAt(i);
             SparseIntArray opModes = uidState.getNonDefaultUidModes();
             if (opModes != null) {
@@ -132,41 +160,191 @@
 
     @Before
     public void setUp() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mAppOpsFile = new File(mContext.getFilesDir(), APP_OPS_FILENAME);
-        extractAppOpsFile();
-        HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        mHandler = new Handler(handlerThread.getLooper());
+        if (sAppOpsFile.exists()) {
+            sAppOpsFile.delete();
+        }
+
+        mMockitoSession = mockitoSession()
+                .initMocks(this)
+                .spyStatic(LocalServices.class)
+                .mockStatic(SystemServerInitThreadPool.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+
+        doReturn(mPermissionManagerInternal).when(
+                () -> LocalServices.getService(PermissionManagerServiceInternal.class));
+        doReturn(mUserManagerInternal).when(
+                () -> LocalServices.getService(UserManagerInternal.class));
+        doReturn(mPackageManagerInternal).when(
+                () -> LocalServices.getService(PackageManagerInternal.class));
+
+        mTestContext = spy(sContext);
+
+        // Pretend everybody has all permissions
+        doNothing().when(mTestContext).enforcePermission(anyString(), anyInt(), anyInt(),
+                nullable(String.class));
+
+        doReturn(mPackageManager).when(mTestContext).getPackageManager();
+
+        // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
+        doReturn(null).when(mPackageManager).getPackagesForUid(anyInt());
+    }
+
+    @After
+    public void tearDown() {
+        mMockitoSession.finishMocking();
     }
 
     @Test
-    public void testUpgradeFromNoVersion() throws Exception {
-        AppOpsDataParser parser = new AppOpsDataParser(mAppOpsFile);
+    public void upgradeRunAnyInBackground() {
+        extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
+
+        AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+
+        testService.upgradeRunAnyInBackgroundLocked();
+        assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
+                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
+    }
+
+    private static int getModeInFile(int uid) {
+        switch (uid) {
+            case 10198:
+                return 0;
+            case 10200:
+                return 1;
+            case 1110200:
+            case 10267:
+            case 1110181:
+                return 2;
+            default:
+                return AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM);
+        }
+    }
+
+    @Test
+    public void upgradeScheduleExactAlarm() {
+        extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH);
+
+        String[] packageNames = {"p1", "package2", "pkg3", "package.4", "pkg-5", "pkg.6"};
+        int[] appIds = {10267, 10181, 10198, 10199, 10200, 4213};
+        int[] userIds = {0, 10, 11};
+
+        doReturn(userIds).when(mUserManagerInternal).getUserIds();
+
+        doReturn(packageNames).when(mPermissionManagerInternal).getAppOpPermissionPackages(
+                AppOpsManager.opToPermission(OP_SCHEDULE_EXACT_ALARM));
+
+        doAnswer(invocation -> {
+            String pkg = invocation.getArgument(0);
+            int index = ArrayUtils.indexOf(packageNames, pkg);
+            if (index < 0) {
+                return index;
+            }
+            int userId = invocation.getArgument(2);
+            return UserHandle.getUid(userId, appIds[index]);
+        }).when(mPackageManagerInternal).getPackageUid(anyString(), anyLong(), anyInt());
+
+        AppOpsServiceImpl testService = new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext);
+
+        testService.upgradeScheduleExactAlarmLocked();
+
+        for (int userId : userIds) {
+            for (int appId : appIds) {
+                final int uid = UserHandle.getUid(userId, appId);
+                final int previousMode = getModeInFile(uid);
+
+                final int expectedMode;
+                if (previousMode == AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM)) {
+                    expectedMode = AppOpsManager.MODE_ALLOWED;
+                } else {
+                    expectedMode = previousMode;
+                }
+                final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+                assertEquals(expectedMode, uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+            }
+        }
+
+        // These uids don't even declare the permission. So should stay as default / empty.
+        int[] unrelatedUidsInFile = {10225, 10178};
+
+        for (int uid : unrelatedUidsInFile) {
+            final AppOpsServiceImpl.UidState uidState = testService.mUidStates.get(uid);
+            assertEquals(AppOpsManager.opToDefaultMode(OP_SCHEDULE_EXACT_ALARM),
+                    uidState.getUidMode(OP_SCHEDULE_EXACT_ALARM));
+        }
+    }
+
+    @Test
+    public void upgradeFromNoFile() {
+        assertFalse(sAppOpsFile.exists());
+
+        AppOpsServiceImpl testService = spy(
+                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+
+        doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+        doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+
+        // trigger upgrade
+        testService.systemReady();
+
+        verify(testService, never()).upgradeRunAnyInBackgroundLocked();
+        verify(testService, never()).upgradeScheduleExactAlarmLocked();
+
+        testService.writeState();
+
+        assertTrue(sAppOpsFile.exists());
+
+        AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
+        assertTrue(parser.parse());
+        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+    }
+
+    @Test
+    public void upgradeFromNoVersion() {
+        extractAppOpsFile(APP_OPS_UNVERSIONED_ASSET_PATH);
+        AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
         assertTrue(parser.parse());
         assertEquals(AppOpsDataParser.NO_VERSION, parser.mVersion);
 
-        // Use mock context and package manager to fake permision package manager calls.
-        Context testContext = spy(mContext);
-
-        // Pretent everybody has all permissions
-        doNothing().when(testContext).enforcePermission(anyString(), anyInt(), anyInt(),
-                nullable(String.class));
-
-        PackageManager testPM = mock(PackageManager.class);
-        when(testContext.getPackageManager()).thenReturn(testPM);
-
-        // Stub out package calls to disable AppOpsService#updatePermissionRevokedCompat
-        when(testPM.getPackagesForUid(anyInt())).thenReturn(null);
-
         AppOpsServiceImpl testService = spy(
-                new AppOpsServiceImpl(mAppOpsFile, mHandler, testContext)); // trigger upgrade
-        assertSameModes(testService.mUidStates, AppOpsManager.OP_RUN_IN_BACKGROUND,
-                AppOpsManager.OP_RUN_ANY_IN_BACKGROUND);
-        mHandler.removeCallbacks(testService.mWriteRunner);
+                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+
+        doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+        doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+
+        // trigger upgrade
+        testService.systemReady();
+
+        verify(testService).upgradeRunAnyInBackgroundLocked();
+        verify(testService).upgradeScheduleExactAlarmLocked();
+
         testService.writeState();
         assertTrue(parser.parse());
-        assertEquals(CURRENT_VERSION, parser.mVersion);
+        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
+    }
+
+    @Test
+    public void upgradeFromVersion1() {
+        extractAppOpsFile(APP_OPS_VERSION_1_ASSET_PATH);
+        AppOpsDataParser parser = new AppOpsDataParser(sAppOpsFile);
+        assertTrue(parser.parse());
+        assertEquals(1, parser.mVersion);
+
+        AppOpsServiceImpl testService = spy(
+                new AppOpsServiceImpl(sAppOpsFile, mHandler, mTestContext));
+
+        doNothing().when(testService).upgradeRunAnyInBackgroundLocked();
+        doNothing().when(testService).upgradeScheduleExactAlarmLocked();
+
+        // trigger upgrade
+        testService.systemReady();
+
+        verify(testService, never()).upgradeRunAnyInBackgroundLocked();
+        verify(testService).upgradeScheduleExactAlarmLocked();
+
+        testService.writeState();
+        assertTrue(parser.parse());
+        assertEquals(AppOpsServiceImpl.CURRENT_VERSION, parser.mVersion);
     }
 
     /**
@@ -174,7 +352,7 @@
      * Other fields may be added as and when required for testing.
      */
     private static final class AppOpsDataParser {
-        static final int NO_VERSION = -1;
+        static final int NO_VERSION = -123;
         int mVersion;
         private File mFile;
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index be32b79..5e5cbdc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -212,7 +212,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 0);
         displayMode.supportedHdrTypes = new int[0];
         FakeDisplay display = new FakeDisplay(PORT_A, new SurfaceControl.DisplayMode[]{displayMode},
-                0);
+                0, 0);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -393,7 +393,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -449,7 +449,7 @@
         SurfaceControl.DisplayMode displayMode = createFakeDisplayMode(0, 1920, 1080, 60f);
         SurfaceControl.DisplayMode[] modes =
                 new SurfaceControl.DisplayMode[]{displayMode};
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, 0, displayMode.refreshRate);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -504,7 +504,7 @@
                 createFakeDisplayMode(0, 1920, 1080, 60f),
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0, 60f);
         setUpDisplay(display);
         updateAvailableDisplays();
         mAdapter.registerLocked();
@@ -538,6 +538,49 @@
     }
 
     @Test
+    public void testAfterDisplayChange_RenderFrameRateIsUpdated() throws Exception {
+        SurfaceControl.DisplayMode[] modes = new SurfaceControl.DisplayMode[]{
+                createFakeDisplayMode(0, 1920, 1080, 60f),
+        };
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, /* activeMode */ 0,
+                /* renderFrameRate */30f);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(0)
+                .getDisplayDeviceInfoLocked();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(30f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+
+        // Change the render frame rate
+        display.dynamicInfo.renderFrameRate = 60f;
+        setUpDisplay(display);
+        mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertTrue(mListener.traversalRequested);
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(1920, 1080, 60f)).isTrue();
+        assertEquals(Float.floatToIntBits(60f),
+                Float.floatToIntBits(displayDeviceInfo.renderFrameRate));
+    }
+
+    @Test
     public void testAfterDisplayChange_HdrCapabilitiesAreUpdated() throws Exception {
         FakeDisplay display = new FakeDisplay(PORT_A);
         Display.HdrCapabilities initialHdrCapabilities = new Display.HdrCapabilities(new int[0],
@@ -722,7 +765,7 @@
                 createFakeDisplayMode(1, 1920, 1080, 50f)
         };
         final int activeMode = 0;
-        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode);
+        FakeDisplay display = new FakeDisplay(PORT_A, modes, activeMode, 60f);
         display.desiredDisplayModeSpecs.defaultMode = 1;
 
         setUpDisplay(display);
@@ -979,11 +1022,13 @@
             dynamicInfo.activeDisplayModeId = 0;
         }
 
-        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode) {
+        private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
+                float renderFrameRate) {
             address = createDisplayAddress(port);
             info = createFakeDisplayInfo();
             dynamicInfo.supportedDisplayModes = modes;
             dynamicInfo.activeDisplayModeId = activeMode;
+            dynamicInfo.renderFrameRate = renderFrameRate;
         }
 
         private FakeDisplay(int port, SurfaceControl.DisplayMode[] modes, int activeMode,
@@ -1001,9 +1046,9 @@
         mAddresses.add(display.address);
         when(mSurfaceControlProxy.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()))
                 .thenReturn(display.token);
-        when(mSurfaceControlProxy.getStaticDisplayInfo(display.token))
+        when(mSurfaceControlProxy.getStaticDisplayInfo(display.address.getPhysicalDisplayId()))
                 .thenReturn(display.info);
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.token))
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(display.address.getPhysicalDisplayId()))
                 .thenReturn(display.dynamicInfo);
         when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token))
                 .thenReturn(display.desiredDisplayModeSpecs);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index 6e4d214..a9dc4af 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -113,7 +113,8 @@
 
         @Override
         JobServiceContext createJobServiceContext(JobSchedulerService service,
-                JobConcurrencyManager concurrencyManager, IBatteryStats batteryStats,
+                JobConcurrencyManager concurrencyManager,
+                JobNotificationCoordinator notificationCoordinator, IBatteryStats batteryStats,
                 JobPackageTracker tracker, Looper looper) {
             final JobServiceContext context = mock(JobServiceContext.class);
             doAnswer((Answer<Boolean>) invocationOnMock -> {
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
new file mode 100644
index 0000000..b4104db
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java
@@ -0,0 +1,440 @@
+/*
+ * 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.job;
+
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.job.JobService;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+
+import com.android.server.LocalServices;
+import com.android.server.notification.NotificationManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+public class JobNotificationCoordinatorTest {
+    private static final String TEST_PACKAGE = "com.android.test";
+    private static final String NOTIFICATION_CHANNEL_ID = "validNotificationChannelId";
+
+    private MockitoSession mMockingSession;
+
+    @Mock
+    private NotificationManagerInternal mNotificationManagerInternal;
+
+    @Before
+    public void setUp() {
+        mMockingSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(LocalServices.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        doReturn(mNotificationManagerInternal)
+                .when(() -> LocalServices.getService(NotificationManagerInternal.class));
+        doNothing().when(mNotificationManagerInternal)
+                .enqueueNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), any(), anyInt());
+        doNothing().when(mNotificationManagerInternal)
+                .enqueueNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), any(), anyInt());
+        doReturn(mock(NotificationChannel.class)).when(mNotificationManagerInternal)
+                .getNotificationChannel(anyString(), anyInt(), eq(NOTIFICATION_CHANNEL_ID));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    @Test
+    public void testParameterValidation() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        try {
+            coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, null,
+                    JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+            fail("Successfully enqueued a null notification");
+        } catch (NullPointerException e) {
+            // Success
+        }
+
+        Notification notification = createValidNotification();
+        doReturn(null).when(notification).getSmallIcon();
+        try {
+            coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId,
+                    notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+            fail("Successfully enqueued a notification with no small icon");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        notification = createValidNotification();
+        doReturn(null).when(notification).getChannelId();
+        try {
+            coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId,
+                    notification, JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+            fail("Successfully enqueued a notification with no valid channel");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        notification = createValidNotification();
+        try {
+            coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId,
+                    notification, Integer.MAX_VALUE);
+            fail("Successfully enqueued a notification with an invalid job end notification "
+                    + "policy");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+    }
+
+    @Test
+    public void testSingleJob_DetachOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+
+        coordinator.removeNotificationAssociation(jsc);
+        verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+    }
+
+    @Test
+    public void testSingleJob_RemoveOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid)));
+
+        coordinator.removeNotificationAssociation(jsc);
+        verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testSingleJob_EnqueueDifferentNotificationId_DetachOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId1 = 23;
+        final int notificationId2 = 46;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId1, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId1), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId2, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testSingleJob_EnqueueDifferentNotificationId_RemoveOnStop() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId1 = 23;
+        final int notificationId2 = 46;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId1, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId1), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId2, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId1), eq(UserHandle.getUserId(uid)));
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testSingleJob_EnqueueSameNotificationId() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc, TEST_PACKAGE, pid, uid, notificationId, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testMultipleJobs_sameApp_EnqueueDifferentNotificationId() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc1 = mock(JobServiceContext.class);
+        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId1 = 23;
+        final int notificationId2 = 46;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid, notificationId1,
+                notification1, JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId1), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid, notificationId2,
+                notification2, JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid)));
+
+        // Remove the first job. Only the first notification should be removed.
+        coordinator.removeNotificationAssociation(jsc1);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId1), eq(UserHandle.getUserId(uid)));
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        eq(notificationId2), anyInt());
+
+        coordinator.removeNotificationAssociation(jsc2);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId2), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testMultipleJobs_sameApp_EnqueueSameNotificationId() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc1 = mock(JobServiceContext.class);
+        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid, notificationId, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_DETACH);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid, notificationId, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
+
+        // Remove the first job. The notification shouldn't be touched because of the 2nd job.
+        coordinator.removeNotificationAssociation(jsc1);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+
+        coordinator.removeNotificationAssociation(jsc2);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+    }
+
+    @Test
+    public void testMultipleJobs_sameApp_DifferentUsers() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final JobServiceContext jsc1 = mock(JobServiceContext.class);
+        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid1 = 10123;
+        final int uid2 = 1010123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc1, TEST_PACKAGE, pid, uid1, notificationId,
+                notification1, JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
+                        eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid1)));
+
+        coordinator.enqueueNotification(jsc2, TEST_PACKAGE, pid, uid2, notificationId,
+                notification2, JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
+                        eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2)));
+
+        // Remove the first job. Only the first notification should be removed.
+        coordinator.removeNotificationAssociation(jsc1);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid1)));
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(),
+                        anyInt(), anyInt());
+
+        coordinator.removeNotificationAssociation(jsc2);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid2)));
+    }
+
+    @Test
+    public void testMultipleJobs_differentApps() {
+        final JobNotificationCoordinator coordinator = new JobNotificationCoordinator();
+        final String pkg1 = "pkg1";
+        final String pkg2 = "pkg2";
+        final JobServiceContext jsc1 = mock(JobServiceContext.class);
+        final JobServiceContext jsc2 = mock(JobServiceContext.class);
+        final Notification notification1 = createValidNotification();
+        final Notification notification2 = createValidNotification();
+        final int uid = 10123;
+        final int pid = 42;
+        final int notificationId = 23;
+
+        InOrder inOrder = inOrder(mNotificationManagerInternal);
+
+        coordinator.enqueueNotification(jsc1, pkg1, pid, uid, notificationId, notification1,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification1), eq(UserHandle.getUserId(uid)));
+
+        coordinator.enqueueNotification(jsc2, pkg2, pid, uid, notificationId, notification2,
+                JobService.JOB_END_NOTIFICATION_POLICY_REMOVE);
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(),
+                        anyInt(), anyInt());
+        inOrder.verify(mNotificationManagerInternal)
+                .enqueueNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid)));
+
+        // Remove the first job. Only the first notification should be removed.
+        coordinator.removeNotificationAssociation(jsc1);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+        inOrder.verify(mNotificationManagerInternal, never())
+                .cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(),
+                        anyInt(), anyInt());
+
+        coordinator.removeNotificationAssociation(jsc2);
+        inOrder.verify(mNotificationManagerInternal)
+                .cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(),
+                        eq(notificationId), eq(UserHandle.getUserId(uid)));
+    }
+
+    private Notification createValidNotification() {
+        final Notification notification = mock(Notification.class);
+        doReturn(mock(Icon.class)).when(notification).getSmallIcon();
+        doReturn(NOTIFICATION_CHANNEL_ID).when(notification).getChannelId();
+        return notification;
+    }
+}
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 fc737d0..8e48490 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -34,6 +34,8 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -46,6 +48,7 @@
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.PermissionChecker;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.res.Resources;
@@ -63,6 +66,7 @@
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
 import com.android.server.SystemServiceManager;
+import com.android.server.job.controllers.ConnectivityController;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.usage.AppStandbyInternal;
@@ -102,6 +106,7 @@
                 .initMocks(this)
                 .strictness(Strictness.LENIENT)
                 .mockStatic(LocalServices.class)
+                .mockStatic(PermissionChecker.class)
                 .mockStatic(ServiceManager.class)
                 .startMocking();
 
@@ -193,6 +198,15 @@
                 jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
     }
 
+    private void grantRunLongJobsPermission(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),
+                        anyInt(), anyInt(), anyString()));
+    }
+
     @Test
     public void testGetMinJobExecutionGuaranteeMs() {
         JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
@@ -207,6 +221,15 @@
                 createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
         JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
                 createJobInfo(6));
+        JobStatus jobDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(7)
+                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobUI = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                createJobInfo(8)); // TODO(255371817): add setUserInitiated(true)
+        JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
+                // TODO(255371817): add setUserInitiated(true)
+                createJobInfo(9)
+                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
 
         spyOn(ejMax);
         spyOn(ejHigh);
@@ -214,6 +237,9 @@
         spyOn(ejHighDowngraded);
         spyOn(jobHigh);
         spyOn(jobDef);
+        spyOn(jobDT);
+        spyOn(jobUI);
+        spyOn(jobUIDT);
 
         when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
         when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
@@ -221,6 +247,16 @@
         when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
         when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
         when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
+        when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+        ConnectivityController connectivityController = mService.getConnectivityController();
+        spyOn(connectivityController);
+        mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -234,8 +270,81 @@
                 mService.getMinJobExecutionGuaranteeMs(jobHigh));
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
+        grantRunLongJobsPermission(false); // Without permission
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDT));
+        grantRunLongJobsPermission(true); // With permission
+        doReturn(ConnectivityController.UNKNOWN_TIME)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDT));
+        doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS / 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDT));
+        doReturn(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS * 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDT));
+        doReturn(mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS * 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobDT));
+        // UserInitiated
+        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUI));
+        grantRunLongJobsPermission(false);
+        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        grantRunLongJobsPermission(true); // With permission
+        doReturn(ConnectivityController.UNKNOWN_TIME)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS / 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        doReturn(mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS * 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(
+                (long) (mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS
+                        * 2 * 1.5),
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        doReturn(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS * 2)
+                .when(connectivityController).getEstimatedTransferTimeMs(any());
+        assertEquals(mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
     }
 
+    @Test
+    public void testGetMaxJobExecutionTimeMs() {
+        JobStatus jobDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+                createJobInfo(7)
+                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+        JobStatus jobUI = createJobStatus("testGetMaxJobExecutionTimeMs",
+                createJobInfo(9)); // TODO(255371817): add setUserInitiated(true)
+        JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
+                // TODO(255371817): add setUserInitiated(true)
+                createJobInfo(10)
+                        .setDataTransfer(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+        spyOn(jobDT);
+        spyOn(jobUI);
+        spyOn(jobUIDT);
+
+        when(jobUI.shouldTreatAsUserInitiated()).thenReturn(true);
+        when(jobUIDT.shouldTreatAsUserInitiated()).thenReturn(true);
+
+        grantRunLongJobsPermission(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));
+    }
 
     /**
      * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 1f85f2c..42e22f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -24,6 +24,7 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.net.NetworkCapabilities.TRANSPORT_VPN;
+import static android.text.format.DateUtils.SECOND_IN_MILLIS;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -35,6 +36,7 @@
 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -240,20 +242,20 @@
                         .setLinkDownstreamBandwidthKbps(1).build(), mConstants));
         // Slow downstream
         assertFalse(controller.isSatisfied(createJobStatus(job), net,
-                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
+                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
                         .setLinkDownstreamBandwidthKbps(1).build(), mConstants));
         // Slow upstream
         assertFalse(controller.isSatisfied(createJobStatus(job), net,
                 createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(1)
-                        .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+                        .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
         // Network good enough
         assertTrue(controller.isSatisfied(createJobStatus(job), net,
-                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(137)
-                        .setLinkDownstreamBandwidthKbps(137).build(), mConstants));
+                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(140)
+                        .setLinkDownstreamBandwidthKbps(140).build(), mConstants));
         // Network slightly too slow given reduced time
         assertFalse(controller.isSatisfied(createJobStatus(job), net,
-                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(130)
-                        .setLinkDownstreamBandwidthKbps(130).build(), mConstants));
+                createCapabilitiesBuilder().setLinkUpstreamBandwidthKbps(139)
+                        .setLinkDownstreamBandwidthKbps(139).build(), mConstants));
         // Slow network is too slow, but device is charging and network is unmetered.
         when(mService.isBatteryCharging()).thenReturn(true);
         controller.onBatteryStateChangedLocked();
@@ -1188,6 +1190,78 @@
         assertFalse(unnetworked.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY));
     }
 
+    @Test
+    public void testCalculateTransferTimeMs() {
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                ConnectivityController.calculateTransferTimeMs(1, 0));
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                ConnectivityController.calculateTransferTimeMs(JobInfo.NETWORK_BYTES_UNKNOWN, 512));
+        assertEquals(1, ConnectivityController.calculateTransferTimeMs(1, 8));
+        assertEquals(1000, ConnectivityController.calculateTransferTimeMs(1000, 8));
+        assertEquals(8, ConnectivityController.calculateTransferTimeMs(1024, 1024));
+    }
+
+    @Test
+    public void testGetEstimatedTransferTimeMs() {
+        final ArgumentCaptor<NetworkCallback> callbackCaptor =
+                ArgumentCaptor.forClass(NetworkCallback.class);
+        doNothing().when(mConnManager).registerNetworkCallback(any(), callbackCaptor.capture());
+
+        final JobStatus job = createJobStatus(createJob()
+                .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(10_000),
+                        DataUnit.MEBIBYTES.toBytes(1_000))
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
+
+        final ConnectivityController controller = new ConnectivityController(mService,
+                mFlexibilityController);
+
+        final JobStatus jobNoEstimates = createJobStatus(createJob());
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                controller.getEstimatedTransferTimeMs(jobNoEstimates));
+
+        // No network
+        job.network = null;
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                controller.getEstimatedTransferTimeMs(job));
+
+        final NetworkCallback generalCallback = callbackCaptor.getValue();
+
+        // No capabilities
+        final Network network = mock(Network.class);
+        answerNetwork(generalCallback, null, null, network, null);
+        job.network = network;
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                controller.getEstimatedTransferTimeMs(job));
+
+        // Capabilities don't have bandwidth values
+        NetworkCapabilities caps = createCapabilitiesBuilder().build();
+        answerNetwork(generalCallback, null, null, network, caps);
+        assertEquals(ConnectivityController.UNKNOWN_TIME,
+                controller.getEstimatedTransferTimeMs(job));
+
+        // Capabilities only has downstream bandwidth
+        caps = createCapabilitiesBuilder()
+                .setLinkDownstreamBandwidthKbps(1024)
+                .build();
+        answerNetwork(generalCallback, null, null, network, caps);
+        assertEquals(81920 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+        // Capabilities only has upstream bandwidth
+        caps = createCapabilitiesBuilder()
+                .setLinkUpstreamBandwidthKbps(2 * 1024)
+                .build();
+        answerNetwork(generalCallback, null, null, network, caps);
+        assertEquals(4096 * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+
+        // Capabilities only both stream bandwidths
+        caps = createCapabilitiesBuilder()
+                .setLinkDownstreamBandwidthKbps(1024)
+                .setLinkUpstreamBandwidthKbps(2 * 1024)
+                .build();
+        answerNetwork(generalCallback, null, null, network, caps);
+        assertEquals((81920 + 4096) * SECOND_IN_MILLIS, controller.getEstimatedTransferTimeMs(job));
+    }
+
     private void answerNetwork(@NonNull NetworkCallback generalCallback,
             @Nullable NetworkCallback uidCallback, @Nullable Network lastNetwork,
             @Nullable Network net, @Nullable NetworkCapabilities caps) {
@@ -1198,11 +1272,15 @@
             }
         } else {
             generalCallback.onAvailable(net);
-            generalCallback.onCapabilitiesChanged(net, caps);
+            if (caps != null) {
+                generalCallback.onCapabilitiesChanged(net, caps);
+            }
             if (uidCallback != null) {
                 uidCallback.onAvailable(net);
                 uidCallback.onBlockedStatusChanged(net, ConnectivityManager.BLOCKED_REASON_NONE);
-                uidCallback.onCapabilitiesChanged(net, caps);
+                if (caps != null) {
+                    uidCallback.onCapabilitiesChanged(net, caps);
+                }
             }
         }
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
new file mode 100644
index 0000000..34b17c7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
@@ -0,0 +1,218 @@
+/*
+ * 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.power;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.hardware.thermal.CoolingType;
+import android.hardware.thermal.IThermal;
+import android.hardware.thermal.IThermalChangedCallback;
+import android.hardware.thermal.TemperatureThreshold;
+import android.hardware.thermal.TemperatureType;
+import android.hardware.thermal.ThrottlingSeverity;
+import android.os.Binder;
+import android.os.CoolingDevice;
+import android.os.RemoteException;
+import android.os.Temperature;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+
+public class ThermalManagerServiceMockingTest {
+    @Mock private IThermal mAidlHalMock;
+    private Binder mAidlBinder = new Binder();
+    private CompletableFuture<Temperature> mTemperatureFuture;
+    private ThermalManagerService.ThermalHalWrapper.TemperatureChangedCallback mTemperatureCallback;
+    private ThermalManagerService.ThermalHalAidlWrapper mAidlWrapper;
+    @Captor
+    ArgumentCaptor<IThermalChangedCallback> mAidlCallbackCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        Mockito.when(mAidlHalMock.asBinder()).thenReturn(mAidlBinder);
+        mAidlBinder.attachInterface(mAidlHalMock, IThermal.class.getName());
+        mTemperatureFuture = new CompletableFuture<>();
+        mTemperatureCallback = temperature -> mTemperatureFuture.complete(temperature);
+        mAidlWrapper = new ThermalManagerService.ThermalHalAidlWrapper();
+        mAidlWrapper.setCallback(mTemperatureCallback);
+        mAidlWrapper.initProxyAndRegisterCallback(mAidlBinder);
+    }
+
+    @Test
+    public void setCallback_aidl() throws Exception {
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).registerThermalChangedCallback(
+                mAidlCallbackCaptor.capture());
+        android.hardware.thermal.Temperature halT =
+                new android.hardware.thermal.Temperature();
+        halT.type = TemperatureType.SOC;
+        halT.name = "test";
+        halT.throttlingStatus = ThrottlingSeverity.SHUTDOWN;
+        halT.value = 99.0f;
+        mAidlCallbackCaptor.getValue().notifyThrottling(halT);
+        Temperature temperature = mTemperatureFuture.get(100, TimeUnit.MILLISECONDS);
+        assertEquals(halT.name, temperature.getName());
+        assertEquals(halT.type, temperature.getType());
+        assertEquals(halT.value, temperature.getValue(), 0.1f);
+        assertEquals(halT.throttlingStatus, temperature.getStatus());
+    }
+
+    @Test
+    public void getCurrentTemperatures_withFilter_aidl() throws RemoteException {
+        android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
+        halT1.type = TemperatureType.MODEM;
+        halT1.name = "test1";
+        halT1.throttlingStatus = ThrottlingSeverity.EMERGENCY;
+        halT1.value = 99.0f;
+        android.hardware.thermal.Temperature halT2 = new android.hardware.thermal.Temperature();
+        halT2.name = "test2";
+        halT2.type = TemperatureType.MODEM;
+        halT2.throttlingStatus = ThrottlingSeverity.NONE;
+
+        android.hardware.thermal.Temperature halT3WithDiffType =
+                new android.hardware.thermal.Temperature();
+        halT3WithDiffType.type = TemperatureType.BCL_CURRENT;
+        halT3WithDiffType.throttlingStatus = ThrottlingSeverity.CRITICAL;
+
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(Mockito.anyInt())).thenReturn(
+                new android.hardware.thermal.Temperature[]{
+                        halT2, halT1, halT3WithDiffType,
+                });
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+
+        Temperature expectedT1 = new Temperature(halT1.value, halT1.type, halT1.name,
+                halT1.throttlingStatus);
+        Temperature expectedT2 = new Temperature(halT2.value, halT2.type, halT2.name,
+                halT2.throttlingStatus);
+        List<Temperature> expectedRet = List.of(expectedT1, expectedT2);
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
+    public void getCurrentTemperatures_invalidStatus_aidl() throws RemoteException {
+        android.hardware.thermal.Temperature halTInvalid =
+                new android.hardware.thermal.Temperature();
+        halTInvalid.name = "test";
+        halTInvalid.type = TemperatureType.MODEM;
+        halTInvalid.throttlingStatus = 99;
+
+        Mockito.when(mAidlHalMock.getTemperatures()).thenReturn(
+                new android.hardware.thermal.Temperature[]{
+                        halTInvalid
+                });
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+
+        List<Temperature> expectedRet = List.of(
+                new Temperature(halTInvalid.value, halTInvalid.type, halTInvalid.name,
+                        ThrottlingSeverity.NONE));
+        assertEquals(expectedRet, ret);
+    }
+
+    @Test
+    public void getCurrentCoolingDevices_withFilter_aidl() throws RemoteException {
+        android.hardware.thermal.CoolingDevice halC1 = new android.hardware.thermal.CoolingDevice();
+        halC1.type = CoolingType.SPEAKER;
+        halC1.name = "test1";
+        halC1.value = 10;
+        android.hardware.thermal.CoolingDevice halC2 = new android.hardware.thermal.CoolingDevice();
+        halC2.type = CoolingType.MODEM;
+        halC2.name = "test2";
+        halC2.value = 110;
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenReturn(
+                new android.hardware.thermal.CoolingDevice[]{
+                        halC1, halC2
+                }
+        );
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(true, CoolingType.SPEAKER);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.SPEAKER);
+
+        CoolingDevice expectedC1 = new CoolingDevice(halC1.value, halC1.type, halC1.name);
+        List<CoolingDevice> expectedRet = List.of(expectedC1);
+        assertTrue("Got cooling device list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
+    public void getCurrentCoolingDevices_invalidType_aidl() throws RemoteException {
+        android.hardware.thermal.CoolingDevice halC1 = new android.hardware.thermal.CoolingDevice();
+        halC1.type = 99;
+        halC1.name = "test1";
+        halC1.value = 10;
+        android.hardware.thermal.CoolingDevice halC2 = new android.hardware.thermal.CoolingDevice();
+        halC2.type = -1;
+        halC2.name = "test2";
+        halC2.value = 110;
+
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenReturn(
+                new android.hardware.thermal.CoolingDevice[]{
+                        halC1, halC2
+                }
+        );
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+
+        assertTrue("Got cooling device list as " + ret + ", expecting empty list", ret.isEmpty());
+    }
+
+    @Test
+    public void getTemperatureThresholds_withFilter_aidl() throws RemoteException {
+        TemperatureThreshold halT1 = new TemperatureThreshold();
+        halT1.name = "test1";
+        halT1.type = Temperature.TYPE_SKIN;
+        halT1.hotThrottlingThresholds = new float[]{1, 2, 3};
+        halT1.coldThrottlingThresholds = new float[]{};
+
+        TemperatureThreshold halT2 = new TemperatureThreshold();
+        halT1.name = "test2";
+        halT1.type = Temperature.TYPE_SOC;
+        halT1.hotThrottlingThresholds = new float[]{};
+        halT1.coldThrottlingThresholds = new float[]{3, 2, 1};
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenReturn(
+                new TemperatureThreshold[]{halT1, halT2}
+        );
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+
+        assertEquals("Got unexpected temperature thresholds size", 1, ret.size());
+        TemperatureThreshold threshold = ret.get(0);
+        assertEquals(halT1.name, threshold.name);
+        assertEquals(halT1.type, threshold.type);
+        assertArrayEquals(halT1.hotThrottlingThresholds, threshold.hotThrottlingThresholds, 0.1f);
+        assertArrayEquals(halT1.coldThrottlingThresholds, threshold.coldThrottlingThresholds, 0.1f);
+    }
+}
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9eb3b92..7149265 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -98,6 +98,7 @@
     <uses-permission android:name="android.permission.MAINLINE_NETWORK_STACK"/>
     <uses-permission
         android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
     <uses-permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY" />
     <uses-permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
new file mode 100644
index 0000000..5335f96
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<device name="Android">
+    <!-- Cellular modem related values. These constants are deprecated, but still supported and
+         need to be tested -->
+    <item name="radio.scanning">720</item>
+    <item name="modem.controller.sleep">70</item>
+    <item name="modem.controller.idle">360</item>
+    <item name="modem.controller.rx">1440</item>
+    <array name="modem.controller.tx"> <!-- Strength 0 to 4 -->
+        <value>720</value>
+        <value>1080</value>
+        <value>1440</value>
+        <value>1800</value>
+        <value>2160</value>
+    </array>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
new file mode 100644
index 0000000..f57bc0f
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
new file mode 100644
index 0000000..4f5e674
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <sleep>70</sleep>
+        <idle>360</idle>
+        <active rat="DEFAULT">
+            <receive>1440</receive>
+            <transmit level="0">720</transmit>
+            <transmit level="1">1080</transmit>
+            <transmit level="2">1440</transmit>
+            <transmit level="3">1800</transmit>
+            <transmit level="4">2160</transmit>
+        </active>
+        <active rat="LTE">
+            <receive>2000</receive>
+            <transmit level="0">800</transmit>
+            <transmit level="1">1200</transmit>
+            <transmit level="2">1600</transmit>
+            <transmit level="3">2000</transmit>
+            <transmit level="4">2400</transmit>
+        </active>
+        <active rat="NR" nrFrequency="DEFAULT">
+            <receive>2222</receive>
+            <transmit level="0">999</transmit>
+            <transmit level="1">1333</transmit>
+            <transmit level="2">1888</transmit>
+            <transmit level="3">2222</transmit>
+            <transmit level="4">2666</transmit>
+        </active>
+        <active rat="NR" nrFrequency="HIGH">
+            <receive>2727</receive>
+            <transmit level="0">1818</transmit>
+            <transmit level="1">2727</transmit>
+            <transmit level="2">3636</transmit>
+            <transmit level="3">4545</transmit>
+            <transmit level="4">5454</transmit>
+        </active>
+        <active rat="NR" nrFrequency="MMWAVE">
+            <receive>3456</receive>
+            <transmit level="0">2345</transmit>
+            <transmit level="1">3456</transmit>
+            <transmit level="2">4567</transmit>
+            <transmit level="3">5678</transmit>
+            <transmit level="4">6789</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
new file mode 100644
index 0000000..ab016fb
--- /dev/null
+++ b/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ 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.
+  -->
+
+<device name="Android">
+    <modem>
+        <!-- Modem sleep drain current value in mA. -->
+        <sleep>10</sleep>
+        <!-- Modem idle drain current value in mA. -->
+        <idle>20</idle>
+        <active rat="DEFAULT">
+            <!-- Transmit current drain in mA. -->
+            <receive>30</receive>
+            <!-- Transmit current drains in mA. Must be defined for all levels (0 to 4) -->
+            <transmit level="0">40</transmit>
+            <transmit level="1">50</transmit>
+            <transmit level="2">60</transmit>
+            <transmit level="3">70</transmit>
+            <transmit level="4">80</transmit>
+        </active>
+    </modem>
+</device>
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
new file mode 100644
index 0000000..17a5876
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/NetworkManagementServiceTest.java
@@ -0,0 +1,368 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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;
+
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_LOW_POWER_STANDBY;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_RESTRICTED;
+import static android.net.ConnectivityManager.FIREWALL_CHAIN_STANDBY;
+import static android.util.DebugUtils.valueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.annotation.NonNull;
+import android.content.AttributionSource;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.INetd;
+import android.net.INetdUnsolicitedEventListener;
+import android.net.LinkAddress;
+import android.net.NetworkPolicyManager;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.PermissionEnforcer;
+import android.os.Process;
+import android.os.RemoteException;
+import android.permission.PermissionCheckerManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArrayMap;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.server.NetworkManagementService.Dependencies;
+import com.android.server.net.BaseNetworkObserver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.BiFunction;
+
+/**
+ * Tests for {@link NetworkManagementService}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class NetworkManagementServiceTest {
+    private NetworkManagementService mNMService;
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mCm;
+    @Mock private IBatteryStats.Stub mBatteryStatsService;
+    @Mock private INetd.Stub mNetdService;
+
+    private static final int TEST_UID = 111;
+
+    @NonNull
+    @Captor
+    private ArgumentCaptor<INetdUnsolicitedEventListener> mUnsolListenerCaptor;
+
+    private final MockDependencies mDeps = new MockDependencies();
+    private final MockPermissionEnforcer mPermissionEnforcer = new MockPermissionEnforcer();
+
+    private final class MockDependencies extends Dependencies {
+        @Override
+        public IBinder getService(String name) {
+            switch (name) {
+                case BatteryStats.SERVICE_NAME:
+                    return mBatteryStatsService;
+                default:
+                    throw new UnsupportedOperationException("Unknown service " + name);
+            }
+        }
+
+        @Override
+        public void registerLocalService(NetworkManagementInternal nmi) {
+        }
+
+        @Override
+        public INetd getNetd() {
+            return mNetdService;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return Process.SYSTEM_UID;
+        }
+    }
+
+    private static final class MockPermissionEnforcer extends PermissionEnforcer {
+        @Override
+        protected int checkPermission(@NonNull String permission,
+                @NonNull AttributionSource source) {
+            String[] granted = new String [] {
+                android.Manifest.permission.NETWORK_SETTINGS,
+                android.Manifest.permission.OBSERVE_NETWORK_POLICY,
+                android.Manifest.permission.SHUTDOWN
+            };
+            for (String p : granted) {
+                if (p.equals(permission)) {
+                    return PermissionCheckerManager.PERMISSION_GRANTED;
+                }
+            }
+            return PermissionCheckerManager.PERMISSION_HARD_DENIED;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        doNothing().when(mNetdService)
+                .registerUnsolicitedEventListener(mUnsolListenerCaptor.capture());
+        doReturn(Context.CONNECTIVITY_SERVICE).when(mContext).getSystemServiceName(
+                eq(ConnectivityManager.class));
+        doReturn(mCm).when(mContext).getSystemService(eq(Context.CONNECTIVITY_SERVICE));
+        // The AIDL stub will use PermissionEnforcer to check permission from the caller.
+        // Mock the service. See MockPermissionEnforcer above.
+        doReturn(Context.PERMISSION_ENFORCER_SERVICE).when(mContext).getSystemServiceName(
+                eq(PermissionEnforcer.class));
+        doReturn(mPermissionEnforcer).when(mContext).getSystemService(
+                eq(Context.PERMISSION_ENFORCER_SERVICE));
+
+        // Start the service and wait until it connects to our socket.
+        mNMService = NetworkManagementService.create(mContext, mDeps);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mNMService.shutdown();
+    }
+
+    private static <T> T expectSoon(T mock) {
+        return verify(mock, timeout(200));
+    }
+
+    /**
+     * Tests that network observers work properly.
+     */
+    @Test
+    public void testNetworkObservers() throws Exception {
+        BaseNetworkObserver observer = mock(BaseNetworkObserver.class);
+        doReturn(new Binder()).when(observer).asBinder();  // Used by registerObserver.
+        mNMService.registerObserver(observer);
+
+        // Forget everything that happened to the mock so far, so we can explicitly verify
+        // everything that happens and does not happen to it from now on.
+
+        INetdUnsolicitedEventListener unsolListener = mUnsolListenerCaptor.getValue();
+        reset(observer);
+        // Now call unsolListener methods and ensure that the observer methods are
+        // called. After every method we expect a callback soon after; to ensure that
+        // invalid messages don't cause any callbacks, we call verifyNoMoreInteractions at the end.
+
+        /**
+         * Interface changes.
+         */
+        unsolListener.onInterfaceAdded("rmnet12");
+        expectSoon(observer).interfaceAdded("rmnet12");
+
+        unsolListener.onInterfaceRemoved("eth1");
+        expectSoon(observer).interfaceRemoved("eth1");
+
+        unsolListener.onInterfaceChanged("clat4", true);
+        expectSoon(observer).interfaceStatusChanged("clat4", true);
+
+        unsolListener.onInterfaceLinkStateChanged("rmnet0", false);
+        expectSoon(observer).interfaceLinkStateChanged("rmnet0", false);
+
+        /**
+         * Bandwidth control events.
+         */
+        unsolListener.onQuotaLimitReached("data", "rmnet_usb0");
+        expectSoon(observer).limitReached("data", "rmnet_usb0");
+
+        /**
+         * Interface class activity.
+         */
+        unsolListener.onInterfaceClassActivityChanged(true, 1, 1234, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(1, true, 1234, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 5678, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 5678, TEST_UID);
+
+        unsolListener.onInterfaceClassActivityChanged(false, 9, 4321, TEST_UID);
+        expectSoon(observer).interfaceClassDataActivityChanged(9, false, 4321, TEST_UID);
+
+        /**
+         * IP address changes.
+         */
+        unsolListener.onInterfaceAddressUpdated("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressUpdated("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("fe80::1/64", "wlan0", 128, 253);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("fe80::1/64", 128, 253));
+
+        unsolListener.onInterfaceAddressRemoved("2001:db8::1/64", "wlan0", 1, 0);
+        expectSoon(observer).addressRemoved("wlan0", new LinkAddress("2001:db8::1/64", 1, 0));
+
+        /**
+         * DNS information broadcasts.
+         */
+        unsolListener.onInterfaceDnsServerInfo("rmnet_usb0", 3600, new String[]{"2001:db8::1"});
+        expectSoon(observer).interfaceDnsServerInfo("rmnet_usb0", 3600,
+                new String[]{"2001:db8::1"});
+
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 14400,
+                new String[]{"2001:db8::1", "2001:db8::2"});
+
+        // We don't check for negative lifetimes, only for parse errors.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", -3600, new String[]{"::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", -3600,
+                new String[]{"::1"});
+
+        // No syntax checking on the addresses.
+        unsolListener.onInterfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+        expectSoon(observer).interfaceDnsServerInfo("wlan0", 600,
+                new String[]{"", "::", "", "foo", "::1"});
+
+        // Make sure nothing else was called.
+        verifyNoMoreInteractions(observer);
+    }
+
+    @Test
+    public void testFirewallEnabled() {
+        mNMService.setFirewallEnabled(true);
+        assertTrue(mNMService.isFirewallEnabled());
+
+        mNMService.setFirewallEnabled(false);
+        assertFalse(mNMService.isFirewallEnabled());
+    }
+
+    @Test
+    public void testNetworkRestrictedDefault() {
+        assertFalse(mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testMeteredNetworkRestrictions() throws RemoteException {
+        // Make sure the mocked netd method returns true.
+        doReturn(true).when(mNetdService).bandwidthEnableDataSaver(anyBoolean());
+
+        // Restrict usage of mobile data in background
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, true);
+        assertTrue("Should be true since mobile data usage is restricted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setDataSaverModeEnabled(true);
+        verify(mNetdService).bandwidthEnableDataSaver(true);
+
+        mNMService.setUidOnMeteredNetworkDenylist(TEST_UID, false);
+        assertTrue("Should be true since data saver is on and the uid is not allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).removeUidFromMeteredNetworkDenyList(TEST_UID);
+
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, true);
+        assertFalse("Should be false since data saver is on and the uid is allowlisted",
+                mNMService.isNetworkRestricted(TEST_UID));
+        verify(mCm).addUidToMeteredNetworkAllowList(TEST_UID);
+
+        // remove uid from allowlist and turn datasaver off again
+        mNMService.setUidOnMeteredNetworkAllowlist(TEST_UID, false);
+        verify(mCm).removeUidFromMeteredNetworkAllowList(TEST_UID);
+        mNMService.setDataSaverModeEnabled(false);
+        verify(mNetdService).bandwidthEnableDataSaver(false);
+        assertFalse("Network should not be restricted when data saver is off",
+                mNMService.isNetworkRestricted(TEST_UID));
+    }
+
+    @Test
+    public void testFirewallChains() {
+        final ArrayMap<Integer, ArrayMap<Integer, Boolean>> expected = new ArrayMap<>();
+        // Dozable chain
+        final ArrayMap<Integer, Boolean> isRestrictedForDozable = new ArrayMap<>();
+        isRestrictedForDozable.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForDozable.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_DOZABLE, isRestrictedForDozable);
+        // Powersaver chain
+        final ArrayMap<Integer, Boolean> isRestrictedForPowerSave = new ArrayMap<>();
+        isRestrictedForPowerSave.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForPowerSave.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_POWERSAVE, isRestrictedForPowerSave);
+        // Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForStandby = new ArrayMap<>();
+        isRestrictedForStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_STANDBY, isRestrictedForStandby);
+        // Restricted mode chain
+        final ArrayMap<Integer, Boolean> isRestrictedForRestrictedMode = new ArrayMap<>();
+        isRestrictedForRestrictedMode.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForRestrictedMode.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_RESTRICTED, isRestrictedForRestrictedMode);
+        // Low Power Standby chain
+        final ArrayMap<Integer, Boolean> isRestrictedForLowPowerStandby = new ArrayMap<>();
+        isRestrictedForLowPowerStandby.put(NetworkPolicyManager.FIREWALL_RULE_DEFAULT, true);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_ALLOW, false);
+        isRestrictedForLowPowerStandby.put(INetd.FIREWALL_RULE_DENY, true);
+        expected.put(FIREWALL_CHAIN_LOW_POWER_STANDBY, isRestrictedForLowPowerStandby);
+
+        final int[] chains = {
+                FIREWALL_CHAIN_STANDBY,
+                FIREWALL_CHAIN_POWERSAVE,
+                FIREWALL_CHAIN_DOZABLE,
+                FIREWALL_CHAIN_RESTRICTED,
+                FIREWALL_CHAIN_LOW_POWER_STANDBY
+        };
+        final int[] states = {
+                INetd.FIREWALL_RULE_ALLOW,
+                INetd.FIREWALL_RULE_DENY,
+                NetworkPolicyManager.FIREWALL_RULE_DEFAULT
+        };
+        BiFunction<Integer, Integer, String> errorMsg = (chain, state) -> {
+            return String.format("Unexpected value for chain: %s and state: %s",
+                    valueToString(INetd.class, "FIREWALL_CHAIN_", chain),
+                    valueToString(INetd.class, "FIREWALL_RULE_", state));
+        };
+        for (int chain : chains) {
+            final ArrayMap<Integer, Boolean> expectedValues = expected.get(chain);
+            mNMService.setFirewallChainEnabled(chain, true);
+            verify(mCm).setFirewallChainEnabled(chain, true /* enabled */);
+            for (int state : states) {
+                mNMService.setFirewallUidRule(chain, TEST_UID, state);
+                assertEquals(errorMsg.apply(chain, state),
+                        expectedValues.get(state), mNMService.isNetworkRestricted(TEST_UID));
+            }
+            mNMService.setFirewallChainEnabled(chain, false);
+            verify(mCm).setFirewallChainEnabled(chain, false /* enabled */);
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 6e446f0..98037d7 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -38,6 +38,8 @@
 import static com.android.server.am.UserController.USER_CURRENT_MSG;
 import static com.android.server.am.UserController.USER_START_MSG;
 import static com.android.server.am.UserController.USER_SWITCH_TIMEOUT_MSG;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_BACKGROUND;
+import static com.android.server.pm.UserManagerInternal.USER_START_MODE_FOREGROUND;
 
 import static com.google.android.collect.Lists.newArrayList;
 import static com.google.android.collect.Sets.newHashSet;
@@ -190,7 +192,7 @@
             // that's not the case, the test should call mockAssignUserToMainDisplay()
             doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE)
                     .when(mInjector.mUserManagerInternalMock)
-                    .assignUserToDisplayOnStart(anyInt(), anyInt(), anyBoolean(), anyInt());
+                    .assignUserToDisplayOnStart(anyInt(), anyInt(), anyInt(), anyInt());
 
             mUserController = new UserController(mInjector);
             mUserController.setAllowUserUnlocking(true);
@@ -207,7 +209,7 @@
 
     @Test
     public void testStartUser_foreground() {
-        mUserController.startUser(TEST_USER_ID, true /* foreground */);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         verify(mInjector.getWindowManager()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
         verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
@@ -219,7 +221,7 @@
 
     @Test
     public void testStartUser_background() {
-        boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
+        boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
         assertWithMessage("startUser(%s, foreground=false)", TEST_USER_ID).that(started).isTrue();
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
@@ -232,9 +234,10 @@
     public void testStartUser_displayAssignmentFailed() {
         doReturn(UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE)
                 .when(mInjector.mUserManagerInternalMock)
-                .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(), eq(true), anyInt());
+                .assignUserToDisplayOnStart(eq(TEST_USER_ID), anyInt(),
+                        eq(USER_START_MODE_FOREGROUND), anyInt());
 
-        boolean started = mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+        boolean started = mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
 
         assertWithMessage("startUser(%s, foreground=true)", TEST_USER_ID).that(started).isFalse();
     }
@@ -266,7 +269,7 @@
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
-        mUserController.startUser(TEST_USER_ID, /* foreground= */ true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
         verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
         verify(mInjector.getWindowManager(), never()).setSwitchingUser(anyBoolean());
@@ -275,7 +278,8 @@
 
     @Test
     public void testStartPreCreatedUser_foreground() {
-        assertFalse(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ true));
+        assertFalse(
+                mUserController.startUser(TEST_PRE_CREATED_USER_ID, USER_START_MODE_FOREGROUND));
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
 
@@ -284,7 +288,7 @@
 
     @Test
     public void testStartPreCreatedUser_background() throws Exception {
-        assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, /* foreground= */ false));
+        assertTrue(mUserController.startUser(TEST_PRE_CREATED_USER_ID, USER_START_MODE_BACKGROUND));
         // Make sure no intents have been fired for pre-created users.
         assertTrue(mInjector.mSentIntents.isEmpty());
 
@@ -352,7 +356,7 @@
         }).when(observer).onUserSwitching(anyInt(), any());
         mUserController.registerUserSwitchObserver(observer, "mock");
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -382,7 +386,7 @@
         when(observer.asBinder()).thenReturn(new Binder());
         mUserController.registerUserSwitchObserver(observer, "mock");
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -408,7 +412,7 @@
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -429,7 +433,7 @@
         mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -450,7 +454,7 @@
                 /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
 
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, /* foreground=*/ true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -487,7 +491,7 @@
         when(observer.asBinder()).thenReturn(new Binder());
         mUserController.registerUserSwitchObserver(observer, "mock");
         // Start user -- this will update state of mUserController
-        mUserController.startUser(TEST_USER_ID, true);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         int oldUserId = reportMsg.arg1;
@@ -511,7 +515,8 @@
     public void testExplicitSystemUserStartInBackground() {
         setUpUser(UserHandle.USER_SYSTEM, 0);
         assertFalse(mUserController.isSystemUserStarted());
-        assertTrue(mUserController.startUser(UserHandle.USER_SYSTEM, false, null));
+        assertTrue(mUserController.startUser(UserHandle.USER_SYSTEM, USER_START_MODE_BACKGROUND,
+                null));
         assertTrue(mUserController.isSystemUserStarted());
     }
 
@@ -638,6 +643,39 @@
                 /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
     }
 
+    @Test
+    public void testStopUser_invalidUser() {
+        int userId = -1;
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mUserController.stopUser(userId, /* force= */ true,
+                        /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                        /* keyEvictedCallback= */ null));
+    }
+
+    @Test
+    public void testStopUser_systemUser() {
+        int userId = UserHandle.USER_SYSTEM;
+
+        int r = mUserController.stopUser(userId, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+    }
+
+    @Test
+    public void testStopUser_currentUser() {
+        setUpUser(TEST_USER_ID1, /* flags= */ 0);
+        mUserController.startUser(TEST_USER_ID1, USER_START_MODE_FOREGROUND);
+
+        int r = mUserController.stopUser(TEST_USER_ID1, /* force= */ true,
+                /* allowDelayedLocking= */ true, /* stopUserCallback= */ null,
+                /* keyEvictedCallback= */ null);
+
+        assertThat(r).isEqualTo(ActivityManager.USER_OP_IS_CURRENT);
+    }
+
     /**
      * Test conditional delayed locking with mDelayUserDataLocking true.
      */
@@ -668,7 +706,7 @@
     public void testUserNotUnlockedBeforeAllowed() throws Exception {
         mUserController.setAllowUserUnlocking(false);
 
-        mUserController.startUser(TEST_USER_ID, /* foreground= */ false);
+        mUserController.startUser(TEST_USER_ID, USER_START_MODE_BACKGROUND);
 
         verify(mInjector.mStorageManagerMock, never())
                 .unlockUserKey(eq(TEST_USER_ID), anyInt(), any());
@@ -829,10 +867,10 @@
         setUpUser(user1, 0);
         setUpUser(user2, 0);
 
-        mUserController.startUser(user1, /* foreground= */ true);
+        mUserController.startUser(user1, USER_START_MODE_FOREGROUND);
         mUserController.getStartedUserState(user1).setState(UserState.STATE_RUNNING_UNLOCKED);
 
-        mUserController.startUser(user2, /* foreground= */ false);
+        mUserController.startUser(user2, USER_START_MODE_BACKGROUND);
         mUserController.getStartedUserState(user2).setState(UserState.STATE_RUNNING_LOCKED);
 
         final int event1a = SystemService.UserCompletedEventType.EVENT_TYPE_USER_STARTING;
@@ -869,7 +907,7 @@
 
     private void setUpAndStartUserInBackground(int userId) throws Exception {
         setUpUser(userId, 0);
-        mUserController.startUser(userId, /* foreground= */ false);
+        mUserController.startUser(userId, USER_START_MODE_BACKGROUND);
         verify(mInjector.mLockPatternUtilsMock, times(1)).unlockUserKeyIfUnsecured(userId);
         mUserStates.put(userId, mUserController.getStartedUserState(userId));
     }
@@ -913,7 +951,7 @@
     private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
             int expectedNumberOfCalls, boolean expectOldUserStopping) {
         // Start user -- this will update state of mUserController
-        mUserController.startUser(newUserId, true);
+        mUserController.startUser(newUserId, USER_START_MODE_FOREGROUND);
         Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
         assertNotNull(reportMsg);
         UserState userState = (UserState) reportMsg.obj;
@@ -972,12 +1010,12 @@
 
     private void verifyUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
         verify(mInjector.getUserManagerInternal()).assignUserToDisplayOnStart(eq(userId), anyInt(),
-                anyBoolean(), eq(displayId));
+                anyInt(), eq(displayId));
     }
 
     private void verifyUserNeverAssignedToDisplay() {
         verify(mInjector.getUserManagerInternal(), never()).assignUserToDisplayOnStart(anyInt(),
-                anyInt(), anyBoolean(), anyInt());
+                anyInt(), anyInt(), anyInt());
     }
 
     private void verifyUserUnassignedFromDisplay(@UserIdInt int userId) {
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 9de6190..ded8179 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceManager.DEFAULT_DEVICE_ID;
+import static android.companion.virtual.VirtualDeviceManager.INVALID_DEVICE_ID;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_SENSORS;
@@ -47,7 +49,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.companion.AssociationInfo;
 import android.companion.virtual.IVirtualDeviceActivityListener;
-import android.companion.virtual.VirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
@@ -84,6 +85,7 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArraySet;
+import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.KeyEvent;
 import android.view.WindowManager;
@@ -178,6 +180,7 @@
     private AssociationInfo mAssociationInfo;
     private VirtualDeviceManagerService mVdms;
     private VirtualDeviceManagerInternal mLocalService;
+    private VirtualDeviceManagerService.VirtualDeviceManagerImpl mVdm;
     @Mock
     private InputController.NativeWrapper mNativeWrapperMock;
     @Mock
@@ -303,45 +306,68 @@
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
+        mVdm = mVdms.new VirtualDeviceManagerImpl();
 
         VirtualDeviceParams params = new VirtualDeviceParams
                 .Builder()
                 .setBlockedActivities(getBlockedActivities())
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mAssociationInfo, new Binder(), /* ownerUid= */ 0, VIRTUAL_DEVICE_ID,
                 mInputController, mSensorController, (int associationId) -> {},
                 mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
     }
 
     @Test
+    public void getDeviceIdForDisplayId_invalidDisplayId_returnsDefault() {
+        assertThat(mVdm.getDeviceIdForDisplayId(Display.INVALID_DISPLAY))
+                .isEqualTo(DEFAULT_DEVICE_ID);
+    }
+
+    @Test
+    public void getDeviceIdForDisplayId_defaultDisplayId_returnsDefault() {
+        assertThat(mVdm.getDeviceIdForDisplayId(Display.DEFAULT_DISPLAY))
+                .isEqualTo(DEFAULT_DEVICE_ID);
+    }
+
+    @Test
+    public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
+        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID);
+
+        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID))
+                .isEqualTo(DEFAULT_DEVICE_ID);
+    }
+
+    @Test
+    public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+
+        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID))
+                .isEqualTo(mDeviceImpl.getDeviceId());
+    }
+
+    @Test
     public void getDevicePolicy_invalidDeviceId_returnsDefault() {
-        assertThat(
-                mLocalService.getDevicePolicy(
-                        VirtualDeviceManager.INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(INVALID_DEVICE_ID, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_defaultDeviceId_returnsDefault() {
-        assertThat(
-                mLocalService.getDevicePolicy(
-                        VirtualDeviceManager.DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(DEFAULT_DEVICE_ID, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_nonExistentDeviceId_returnsDefault() {
-        assertThat(
-                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId() + 1, POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
     @Test
     public void getDevicePolicy_unspecifiedPolicy_returnsDefault() {
-        assertThat(
-                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_DEFAULT);
     }
 
@@ -358,8 +384,7 @@
                 mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
         mVdms.addVirtualDevice(mDeviceImpl);
 
-        assertThat(
-                mLocalService.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
+        assertThat(mVdm.getDevicePolicy(mDeviceImpl.getDeviceId(), POLICY_TYPE_SENSORS))
                 .isEqualTo(DEVICE_POLICY_CUSTOM);
     }
 
@@ -1160,5 +1185,4 @@
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
     }
-
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 3ca648c..aaa1351 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -16,7 +16,6 @@
 
 package com.android.server.companion.virtual.audio;
 
-import static android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING;
 import static android.media.AudioAttributes.FLAG_SECURE;
 import static android.media.AudioPlaybackConfiguration.PLAYER_STATE_STARTED;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -86,8 +85,9 @@
                         /* pipBlockedCallback= */ null,
                         /* activityBlockedCallback= */ null,
                         /* secureWindowCallback= */ null,
-                        /* deviceProfile= */ DEVICE_PROFILE_APP_STREAMING,
-                        /* displayCategories= */ new ArrayList<>());
+                        /* displayCategories= */ new ArrayList<>(),
+                        /* recentsPolicy= */
+                        VirtualDeviceParams.RECENTS_POLICY_ALLOW_IN_HOST_DEVICE_RECENTS);
     }
 
 
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index a02b0f1..88314ab 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -4310,7 +4310,7 @@
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         setupDeviceOwner();
         assertExpectException(SecurityException.class, null, () ->
-                dpm.setSystemSetting(admin1, Settings.System.SCREEN_BRIGHTNESS_FOR_VR, "0"));
+                dpm.setSystemSetting(admin1, Settings.System.SCREEN_AUTO_BRIGHTNESS_ADJ, "0"));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
index f535fda..85a2446 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/OwnersTest.java
@@ -44,6 +44,7 @@
 public class OwnersTest extends DpmTestBase {
 
     private static final String TESTDPC_PACKAGE = "com.afwsamples.testdpc";
+    private final DeviceStateCacheImpl mDeviceStateCache = new DeviceStateCacheImpl();
 
     @Before
     public void setUp() throws Exception {
@@ -120,6 +121,6 @@
         final MockSystemServices services = getServices();
         return new Owners(services.userManager, services.userManagerInternal,
                 services.packageManagerInternal, services.activityTaskManagerInternal,
-                services.activityManagerInternal, services.pathProvider);
+                services.activityManagerInternal, mDeviceStateCache, services.pathProvider);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 303b240..f676a3f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -214,7 +214,7 @@
                 .thenReturn(mMockDisplayToken);
         SurfaceControl.StaticDisplayInfo staticDisplayInfo = new SurfaceControl.StaticDisplayInfo();
         staticDisplayInfo.isInternal = true;
-        when(mSurfaceControlProxy.getStaticDisplayInfo(mMockDisplayToken))
+        when(mSurfaceControlProxy.getStaticDisplayInfo(anyLong()))
                 .thenReturn(staticDisplayInfo);
         SurfaceControl.DynamicDisplayInfo dynamicDisplayMode =
                 new SurfaceControl.DynamicDisplayInfo();
@@ -223,7 +223,7 @@
         displayMode.height = 200;
         displayMode.supportedHdrTypes = new int[]{1, 2};
         dynamicDisplayMode.supportedDisplayModes = new SurfaceControl.DisplayMode[] {displayMode};
-        when(mSurfaceControlProxy.getDynamicDisplayInfo(mMockDisplayToken))
+        when(mSurfaceControlProxy.getDynamicDisplayInfo(anyLong()))
                 .thenReturn(dynamicDisplayMode);
         when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(mMockDisplayToken))
                 .thenReturn(new SurfaceControl.DesiredDisplayModeSpecs());
@@ -1253,6 +1253,76 @@
     }
 
     /**
+     * Tests that there is a display change notification if the render frame rate is updated
+     */
+    @Test
+    public void testShouldNotifyChangeWhenDisplayInfoRenderFrameRateChanged() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager, new float[]{60f});
+        FakeDisplayManagerCallback callback = registerDisplayListenerCallback(displayManager,
+                displayManagerBinderService, displayDevice);
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+
+        updateRenderFrameRate(displayManager, displayDevice, 30f);
+        assertFalse(callback.mDisplayChangedCalled);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        assertTrue(callback.mDisplayChangedCalled);
+        callback.clear();
+    }
+
+    /**
+     * Tests that the DisplayInfo is updated correctly with a render frame rate
+     */
+    @Test
+    public void testDisplayInfoRenderFrameRate() throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+    }
+
+    /**
+     * Tests that the mode reflects the render frame rate is in compat mode
+     */
+    @Test
+    @DisableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateModeCompat() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ false);
+    }
+
+    /**
+     * Tests that the mode reflects the physical display refresh rate when not in compat mode.
+     */
+    @Test
+    @EnableCompatChanges({DisplayManagerService.DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE})
+    public  void testDisplayInfoRenderFrameRateMode() throws Exception {
+        testDisplayInfoRenderFrameRateModeCompat(/*compatChangeEnabled*/ true);
+    }
+
+    /**
      * Tests that EVENT_DISPLAY_ADDED is sent when a display is added.
      */
     @Test
@@ -1472,6 +1542,34 @@
         assertEquals(expectedMode, displayInfo.getMode());
     }
 
+    private void testDisplayInfoRenderFrameRateModeCompat(boolean compatChangeEnabled)
+            throws Exception {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mShortMockedInjector);
+        DisplayManagerService.BinderService displayManagerBinderService =
+                displayManager.new BinderService();
+        registerDefaultDisplays(displayManager);
+        displayManager.onBootPhase(SystemService.PHASE_WAIT_FOR_DEFAULT_DISPLAY);
+
+        FakeDisplayDevice displayDevice = createFakeDisplayDevice(displayManager,
+                new float[]{60f, 30f, 20f});
+        int displayId = getDisplayIdForDisplayDevice(displayManager, displayManagerBinderService,
+                displayDevice);
+        DisplayInfo displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(60f, displayInfo.getRefreshRate(), 0.01f);
+
+        updateRenderFrameRate(displayManager, displayDevice, 20f);
+        displayInfo = displayManagerBinderService.getDisplayInfo(displayId);
+        assertEquals(20f, displayInfo.getRefreshRate(), 0.01f);
+        Display.Mode expectedMode;
+        if (compatChangeEnabled) {
+            expectedMode = new Display.Mode(1, 100, 200, 60f);
+        } else {
+            expectedMode = new Display.Mode(3, 100, 200, 20f);
+        }
+        assertEquals(expectedMode, displayInfo.getMode());
+    }
+
     private void testDisplayInfoNonNativeFrameRateOverrideMode(boolean compatChangeEnabled) {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
@@ -1541,6 +1639,15 @@
         updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
     }
 
+    private void updateRenderFrameRate(DisplayManagerService displayManager,
+            FakeDisplayDevice displayDevice,
+            float renderFrameRate) {
+        DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
+        displayDeviceInfo.copyFrom(displayDevice.getDisplayDeviceInfoLocked());
+        displayDeviceInfo.renderFrameRate = renderFrameRate;
+        updateDisplayDeviceInfo(displayManager, displayDevice, displayDeviceInfo);
+    }
+
     private void updateModeId(DisplayManagerService displayManager,
             FakeDisplayDevice displayDevice,
             int modeId) {
@@ -1580,6 +1687,7 @@
                     new Display.Mode(i + 1, width, height, refreshRates[i]);
         }
         displayDeviceInfo.modeId = 1;
+        displayDeviceInfo.renderFrameRate = displayDeviceInfo.supportedModes[0].getRefreshRate();
         displayDeviceInfo.width = width;
         displayDeviceInfo.height = height;
         final Rect zeroRect = new Rect();
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 3b0a22f..35a677e 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -344,6 +344,40 @@
         assertEquals(85.3f, newDataStore.getUserPreferredRefreshRate(testDisplayDevice), 0.1f);
     }
 
+    @Test
+    public void testBrightnessInitialisesWithInvalidFloat() {
+        final String uniqueDisplayId = "test:123";
+        DisplayDevice testDisplayDevice = new DisplayDevice(null, null, uniqueDisplayId, null) {
+            @Override
+            public boolean hasStableUniqueId() {
+                return true;
+            }
+
+            @Override
+            public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+                return null;
+            }
+        };
+
+        // Set any value which initialises Display state
+        float refreshRate = 85.3f;
+        mDataStore.loadIfNeeded();
+        mDataStore.setUserPreferredRefreshRate(testDisplayDevice, refreshRate);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
+    }
+
+
     public class TestInjector extends PersistentDataStore.Injector {
         private InputStream mReadStream;
         private OutputStream mWriteStream;
diff --git a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
index 9672085..68e5ebf 100644
--- a/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
+++ b/services/tests/servicestests/src/com/android/server/graphics/fonts/UpdatableFontDirTest.java
@@ -109,17 +109,16 @@
 
         @Override
         public boolean isFromTrustedProvider(String path, byte[] signature) {
-            return mHasFsverityPaths.contains(path);
+            if (!mHasFsverityPaths.contains(path)) {
+                return false;
+            }
+            String fakeSignature = new String(signature, StandardCharsets.UTF_8);
+            return GOOD_SIGNATURE.equals(fakeSignature);
         }
 
         @Override
-        public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
-            String fakeSignature = new String(pkcs7Signature, StandardCharsets.UTF_8);
-            if (GOOD_SIGNATURE.equals(fakeSignature)) {
-                mHasFsverityPaths.add(path);
-            } else {
-                throw new IOException("Failed to set up fake fs-verity");
-            }
+        public void setUpFsverity(String path) throws IOException {
+            mHasFsverityPaths.add(path);
         }
 
         @Override
@@ -813,8 +812,8 @@
             }
 
             @Override
-            public void setUpFsverity(String path, byte[] pkcs7Signature) throws IOException {
-                mFakeFsverityUtil.setUpFsverity(path, pkcs7Signature);
+            public void setUpFsverity(String path) throws IOException {
+                mFakeFsverityUtil.setUpFsverity(path);
             }
 
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
index 09cd47a..2cb46da 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/ArcTerminationActionFromAvrTest.java
@@ -24,6 +24,8 @@
 
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -52,6 +54,7 @@
     private Context mContextSpy;
     private HdmiCecLocalDeviceAudioSystem mHdmiCecLocalDeviceAudioSystem;
     private FakePowerManagerWrapper mPowerManager;
+    private TestCallback mCallback;
     private ArcTerminationActionFromAvr mAction;
 
     private FakeNativeWrapper mNativeWrapper;
@@ -112,7 +115,9 @@
             }
         };
         mHdmiCecLocalDeviceAudioSystem.init();
-        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem);
+        mCallback = new TestCallback();
+        mAction = new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem,
+                mCallback);
 
         mLocalDevices.add(mHdmiCecLocalDeviceAudioSystem);
         hdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
@@ -121,6 +126,20 @@
         mTestLooper.dispatchAll();
     }
 
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+
+        private int getResult() {
+            assertThat(mCallbackResult.size()).isEqualTo(1);
+            return mCallbackResult.get(0);
+        }
+    }
+
     @Test
     public void testSendMessage_sendFailed() {
         mNativeWrapper.setMessageSendResult(Constants.MESSAGE_TERMINATE_ARC,
@@ -133,6 +152,7 @@
         assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 
     @Test
@@ -149,6 +169,7 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
     }
 
     @Test
@@ -167,5 +188,28 @@
         mTestLooper.dispatchAll();
 
         assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void testReportArcTerminated_featureAbort() {
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(mAction);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                Constants.ADDR_AUDIO_SYSTEM, Constants.ADDR_TV);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(terminateArc);
+
+        HdmiCecMessage arcTerminatedResponse = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                Constants.ADDR_TV,
+                Constants.ADDR_AUDIO_SYSTEM,
+                Constants.MESSAGE_TERMINATE_ARC,
+                Constants.ABORT_REFUSED);
+
+        mNativeWrapper.onCecMessage(arcTerminatedResponse);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isFalse();
+        assertThat(mCallback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index 7c6c990..de2c218 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -535,6 +535,25 @@
     }
 
     @Test
+    public void handleRequestArcTerminate_callbackIsPreserved() throws Exception {
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceAudioSystem.setArcStatus(true);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.isArcEnabled()).isTrue();
+        mHdmiCecLocalDeviceAudioSystem.addAndStartAction(
+                new ArcTerminationActionFromAvr(mHdmiCecLocalDeviceAudioSystem, callback));
+
+        HdmiCecMessage message =
+                HdmiCecMessageBuilder.buildRequestArcTermination(ADDR_TV, ADDR_AUDIO_SYSTEM);
+        assertThat(mHdmiCecLocalDeviceAudioSystem.handleRequestArcTermination(message))
+                .isEqualTo(Constants.HANDLED);
+
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiCecLocalDeviceAudioSystem.getActions(
+                ArcTerminationActionFromAvr.class).get(0).mCallbacks.get(0)).isEqualTo(callback);
+    }
+
+    @Test
     public void handleRequestArcInit_arcIsNotSupported() throws Exception {
         HdmiCecMessage message =
                 HdmiCecMessageBuilder.buildRequestArcInitiation(ADDR_TV, ADDR_AUDIO_SYSTEM);
@@ -880,4 +899,13 @@
         assertThat(mNativeWrapper.getResultMessages()).doesNotContain(
                 systemAudioModeRequest_fromAudioSystem);
     }
+
+    private static class TestCallback extends IHdmiControlCallback.Stub {
+        private final ArrayList<Integer> mCallbackResult = new ArrayList<Integer>();
+
+        @Override
+        public void onComplete(int result) {
+            mCallbackResult.add(result);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index ef2b212..49a0a9a52 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -1036,6 +1036,7 @@
 
     @Test
     public void setSoundbarMode_enabled_addAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
         // Initialize the local devices excluding the audio system.
         mHdmiControlServiceSpy.clearCecLocalDevices();
         mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1053,6 +1054,7 @@
 
     @Test
     public void setSoundbarMode_disabled_removeAudioSystemLocalDevice() {
+        mHdmiControlServiceSpy.setPowerStatus(HdmiControlManager.POWER_STATUS_ON);
         // Initialize the local devices excluding the audio system.
         mHdmiControlServiceSpy.clearCecLocalDevices();
         mLocalDevices.remove(mAudioSystemDeviceSpy);
@@ -1073,6 +1075,10 @@
                 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE,
                 HdmiControlManager.SOUNDBAR_MODE_DISABLED);
         mTestLooper.dispatchAll();
+
+        // Wait for ArcTerminationActionFromAvr timeout for the logical address allocation to start.
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
         assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
new file mode 100644
index 0000000..c22782c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/KeyRemapperTests.kt
@@ -0,0 +1,150 @@
+/*
+ * 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.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IInputManager
+import android.hardware.input.InputManager
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.IOException
+import java.io.InputStream
+
+private fun createKeyboard(deviceId: Int): InputDevice =
+    InputDevice.Builder()
+        .setId(deviceId)
+        .setName("Device $deviceId")
+        .setDescriptor("descriptor $deviceId")
+        .setSources(InputDevice.SOURCE_KEYBOARD)
+        .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
+        .setExternal(true)
+        .build()
+
+/**
+ * Tests for {@link KeyRemapper}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:KeyRemapperTests
+ */
+@Presubmit
+class KeyRemapperTests {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val REMAPPABLE_KEYS = intArrayOf(
+            KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.KEYCODE_CTRL_RIGHT,
+            KeyEvent.KEYCODE_META_LEFT, KeyEvent.KEYCODE_META_RIGHT,
+            KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.KEYCODE_ALT_RIGHT,
+            KeyEvent.KEYCODE_SHIFT_LEFT, KeyEvent.KEYCODE_SHIFT_RIGHT,
+            KeyEvent.KEYCODE_CAPS_LOCK
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    @Mock
+    private lateinit var iInputManager: IInputManager
+    @Mock
+    private lateinit var native: NativeInputManagerService
+    private lateinit var mKeyRemapper: KeyRemapper
+    private lateinit var context: Context
+    private lateinit var dataStore: PersistentDataStore
+    private lateinit var testLooper: TestLooper
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        dataStore = PersistentDataStore(object : PersistentDataStore.Injector() {
+            override fun openRead(): InputStream? {
+                throw FileNotFoundException()
+            }
+
+            override fun startWrite(): FileOutputStream? {
+                throw IOException()
+            }
+
+            override fun finishWrite(fos: FileOutputStream?, success: Boolean) {}
+        })
+        testLooper = TestLooper()
+        mKeyRemapper = KeyRemapper(
+            context,
+            native,
+            dataStore,
+            testLooper.looper
+        )
+        val inputManager = InputManager.resetInstance(iInputManager)
+        Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+            .thenReturn(inputManager)
+        Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID))
+    }
+
+    @After
+    fun tearDown() {
+        InputManager.clearInstance()
+    }
+
+    @Test
+    fun testKeyRemapping() {
+        val keyboard = createKeyboard(DEVICE_ID)
+        Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard)
+
+        for (i in REMAPPABLE_KEYS.indices) {
+            val fromKeyCode = REMAPPABLE_KEYS[i]
+            val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+            mKeyRemapper.remapKey(fromKeyCode, toKeyCode)
+            testLooper.dispatchNext()
+        }
+
+        val remapping = mKeyRemapper.keyRemapping
+        val expectedSize = REMAPPABLE_KEYS.size
+        assertEquals("Remapping size should be $expectedSize", expectedSize, remapping.size)
+
+        for (i in REMAPPABLE_KEYS.indices) {
+            val fromKeyCode = REMAPPABLE_KEYS[i]
+            val toKeyCode = REMAPPABLE_KEYS[(i + 1) % REMAPPABLE_KEYS.size]
+            assertEquals(
+                "Remapping should include mapping from $fromKeyCode to $toKeyCode",
+                toKeyCode,
+                remapping.getOrDefault(fromKeyCode, -1)
+            )
+        }
+
+        mKeyRemapper.clearAllKeyRemappings()
+        testLooper.dispatchNext()
+
+        assertEquals(
+            "Remapping size should be 0 after clearAllModifierKeyRemappings",
+            0,
+            mKeyRemapper.keyRemapping.size
+        )
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 426b943..4b318de 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -25,8 +25,16 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
+import android.content.ContentResolver;
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.IContentProvider;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -35,6 +43,9 @@
 import android.os.Build;
 import android.os.LocaleList;
 import android.os.Parcel;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.IntArray;
@@ -1214,6 +1225,42 @@
                 StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
     }
 
+    @Test
+    public void testInputMethodSettings_SwitchCurrentUser() {
+        TestContext ownerUserContext = createMockContext(0 /* userId */);
+        final InputMethodInfo systemIme = createFakeInputMethodInfo(
+                "SystemIme", "fake.latin", true /* isSystem */);
+        final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
+                "fake.voice0", false /* isSystem */);
+        final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+        methodMap.put(systemIme.getId(), systemIme);
+
+        // Init InputMethodSettings for the owner user (userId=0), verify calls can get the
+        // corresponding user's context, contentResolver and the resources configuration.
+        InputMethodUtils.InputMethodSettings settings = new InputMethodUtils.InputMethodSettings(
+                ownerUserContext, methodMap, 0 /* userId */, true);
+        assertEquals(0, settings.getCurrentUserId());
+
+        settings.isShowImeWithHardKeyboardEnabled();
+        verify(ownerUserContext.getContentResolver(), atLeastOnce()).getAttributionSource();
+
+        settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
+        verify(ownerUserContext.getResources(), atLeastOnce()).getConfiguration();
+
+        // Calling switchCurrentUser to the secondary user (userId=10), verify calls can get the
+        // corresponding user's context, contentResolver and the resources configuration.
+        settings.switchCurrentUser(10 /* userId */, true);
+        assertEquals(10, settings.getCurrentUserId());
+
+        settings.isShowImeWithHardKeyboardEnabled();
+        verify(TestContext.getSecondaryUserContext().getContentResolver(),
+                atLeastOnce()).getAttributionSource();
+
+        settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
+        verify(TestContext.getSecondaryUserContext().getResources(),
+                atLeastOnce()).getConfiguration();
+    }
+
     private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
         final IntArray subtypes = new IntArray();
         final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
@@ -1236,6 +1283,63 @@
                         imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
     }
 
+    private static TestContext createMockContext(int userId) {
+        return new TestContext(InstrumentationRegistry.getInstrumentation()
+                .getTargetContext(), userId);
+    }
+
+    private static class TestContext extends ContextWrapper {
+        private int mUserId;
+        private ContentResolver mResolver;
+        private Resources mResources;
+
+        private static TestContext sSecondaryUserContext;
+
+        TestContext(@NonNull Context context, int userId) {
+            super(context);
+            mUserId = userId;
+            mResolver = mock(MockContentResolver.class);
+            when(mResolver.acquireProvider(Settings.Secure.CONTENT_URI)).thenReturn(
+                    mock(IContentProvider.class));
+            mResources = mock(Resources.class);
+
+            final Configuration configuration = new Configuration();
+            if (userId == 0) {
+                configuration.setLocale(LOCALE_EN_US);
+            } else {
+                configuration.setLocale(LOCALE_FR_CA);
+            }
+            doReturn(configuration).when(mResources).getConfiguration();
+        }
+
+        @Override
+        public Context createContextAsUser(UserHandle user, int flags) {
+            if (user.getIdentifier() != UserHandle.USER_SYSTEM) {
+                return sSecondaryUserContext = new TestContext(this, user.getIdentifier());
+            }
+            return this;
+        }
+
+        @Override
+        public int getUserId() {
+            return mUserId;
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mResolver;
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
+
+        static Context getSecondaryUserContext() {
+            return sSecondaryUserContext;
+        }
+    }
+
     @Test
     public void updateEnabledImeStringTest() {
         // No change cases
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index dc47b5e..0589b3a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -8,6 +8,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -22,6 +23,7 @@
 import android.os.PersistableBundle;
 import android.os.SystemClock;
 import android.test.RenamingDelegatingContext;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 
@@ -38,9 +40,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.File;
 import java.time.Clock;
 import java.time.ZoneOffset;
-import java.util.Iterator;
 
 /**
  * Test reading and writing correctly from file.
@@ -93,11 +95,147 @@
         mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L);
     }
 
+    private void setUseSplitFiles(boolean useSplitFiles) throws Exception {
+        mTaskStoreUnderTest.setUseSplitFiles(useSplitFiles);
+        waitForPendingIo();
+    }
+
     private void waitForPendingIo() throws Exception {
         assertTrue("Timed out waiting for persistence I/O to complete",
                 mTaskStoreUnderTest.waitForWriteToCompleteForTesting(5_000L));
     }
 
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runRemovingLastJob();
+    }
+
+    /** Test that we properly remove the last job of an app from the persisted file. */
+    @Test
+    public void testRemovingLastJob_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runRemovingLastJob();
+    }
+
+    private void runRemovingLastJob() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove 1 job
+        mTaskStoreUnderTest.remove(JobStatus1, true);
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+
+        assertJobsEqual(JobStatus2, loaded);
+        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(JobStatus2));
+
+        // Remove 2nd job
+        mTaskStoreUnderTest.remove(JobStatus2, true);
+        waitForPendingIo();
+        jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_singleFile() throws Exception {
+        setUseSplitFiles(false);
+        runClearJobs();
+    }
+
+    /** Test that we properly clear the persisted file when all jobs are dropped. */
+    @Test
+    public void testClearJobs_splitFiles() throws Exception {
+        setUseSplitFiles(true);
+        runClearJobs();
+    }
+
+    private void runClearJobs() throws Exception {
+        final JobInfo task1 = new Builder(8, mComponent)
+                .setRequiresDeviceIdle(true)
+                .setPeriodic(10000L)
+                .setRequiresCharging(true)
+                .setPersisted(true)
+                .build();
+        final JobInfo task2 = new Builder(12, mComponent)
+                .setMinimumLatency(5000L)
+                .setBackoffCriteria(15000L, JobInfo.BACKOFF_POLICY_LINEAR)
+                .setOverrideDeadline(30000L)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
+                .setPersisted(true)
+                .build();
+        final int uid1 = SOME_UID;
+        final int uid2 = uid1 + 1;
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
+        runWritingJobsToDisk(JobStatus1, JobStatus2);
+
+        // Remove all jobs
+        mTaskStoreUnderTest.clear();
+        waitForPendingIo();
+        JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
+    }
+
+    @Test
+    public void testExtractUidFromJobFileName() {
+        File file = new File(mTestContext.getFilesDir(), "randomName");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "jobs.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "1000.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), "10000");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX);
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "text.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + ".xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "-10123.xml");
+        assertEquals(JobStore.INVALID_UID, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "1.xml");
+        assertEquals(1, JobStore.extractUidFromJobFileName(file));
+
+        file = new File(mTestContext.getFilesDir(), JobStore.JOB_FILE_SPLIT_PREFIX + "101023.xml");
+        assertEquals(101023, JobStore.extractUidFromJobFileName(file));
+    }
+
     @Test
     public void testStringToIntArrayAndIntArrayToString() {
         final int[] netCapabilitiesIntArray = { 1, 3, 5, 7, 9 };
@@ -144,13 +282,13 @@
 
     @Test
     public void testWritingTwoJobsToDisk_singleFile() throws Exception {
-        mTaskStoreUnderTest.setUseSplitFiles(false);
+        setUseSplitFiles(false);
         runWritingTwoJobsToDisk();
     }
 
     @Test
     public void testWritingTwoJobsToDisk_splitFiles() throws Exception {
-        mTaskStoreUnderTest.setUseSplitFiles(true);
+        setUseSplitFiles(true);
         runWritingTwoJobsToDisk();
     }
 
@@ -172,28 +310,44 @@
         final int uid2 = uid1 + 1;
         final JobStatus taskStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null);
         final JobStatus taskStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null);
-        mTaskStoreUnderTest.add(taskStatus1);
-        mTaskStoreUnderTest.add(taskStatus2);
+
+        runWritingJobsToDisk(taskStatus1, taskStatus2);
+    }
+
+    private void runWritingJobsToDisk(JobStatus... jobStatuses) throws Exception {
+        ArraySet<JobStatus> expectedJobs = new ArraySet<>();
+        for (JobStatus jobStatus : jobStatuses) {
+            mTaskStoreUnderTest.add(jobStatus);
+            expectedJobs.add(jobStatus);
+        }
         waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
-        assertEquals("Incorrect # of persisted tasks.", 2, jobStatusSet.size());
-        Iterator<JobStatus> it = jobStatusSet.getAllJobs().iterator();
-        JobStatus loaded1 = it.next();
-        JobStatus loaded2 = it.next();
+        assertEquals("Incorrect # of persisted tasks.", expectedJobs.size(), jobStatusSet.size());
+        int count = 0;
+        final int expectedCount = expectedJobs.size();
+        for (JobStatus loaded : jobStatusSet.getAllJobs()) {
+            count++;
+            for (int i = 0; i < expectedJobs.size(); ++i) {
+                JobStatus expected = expectedJobs.valueAt(i);
 
-        // Reverse them so we know which comparison to make.
-        if (loaded1.getJobId() != 8) {
-            JobStatus tmp = loaded1;
-            loaded1 = loaded2;
-            loaded2 = tmp;
+                try {
+                    assertJobsEqual(expected, loaded);
+                    expectedJobs.remove(expected);
+                    break;
+                } catch (AssertionError e) {
+                    // Not equal. Move along.
+                }
+            }
         }
-
-        assertJobsEqual(taskStatus1, loaded1);
-        assertJobsEqual(taskStatus2, loaded2);
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus1));
-        assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(taskStatus2));
+        assertEquals("Loaded more jobs than expected", expectedCount, count);
+        if (expectedJobs.size() > 0) {
+            fail("Not all expected jobs were restored");
+        }
+        for (JobStatus jobStatus : jobStatuses) {
+            assertTrue("JobStore#contains invalid.", mTaskStoreUnderTest.containsJob(jobStatus));
+        }
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
index 55ab4a0..4668e5d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/BaseLockSettingsServiceTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.locksettings;
 
+import static android.app.admin.DevicePolicyManager.DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG;
+import static android.provider.DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -36,7 +39,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
 import android.hardware.face.FaceManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.FileUtils;
@@ -47,6 +50,7 @@
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
 import android.provider.Settings;
+import android.provider.DeviceConfig;
 import android.security.KeyStore;
 
 import androidx.test.InstrumentationRegistry;
@@ -188,7 +192,11 @@
         // Adding a fake Device Owner app which will enable escrow token support in LSS.
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(
                 new ComponentName("com.dummy.package", ".FakeDeviceOwner"));
+        // TODO(b/258213147): Remove
+        DeviceConfig.setProperty(NAMESPACE_DEVICE_POLICY_MANAGER,
+                DEPRECATE_USERMANAGERINTERNAL_DEVICEPOLICY_FLAG, "true", /* makeDefault= */ false);
         when(mUserManagerInternal.isDeviceManaged()).thenReturn(true);
+        when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(true);
         when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
         mockBiometricsHardwareFingerprintsAndTemplates(PRIMARY_USER_ID);
         mockBiometricsHardwareFingerprintsAndTemplates(MANAGED_PROFILE_USER_ID);
@@ -221,7 +229,10 @@
         when(mUserManager.getProfileParent(eq(profileId))).thenReturn(PRIMARY_USER_INFO);
         when(mUserManager.isUserRunning(eq(profileId))).thenReturn(true);
         when(mUserManager.isUserUnlocked(eq(profileId))).thenReturn(true);
+        // TODO(b/258213147): Remove
         when(mUserManagerInternal.isUserManaged(eq(profileId))).thenReturn(true);
+        when(mDeviceStateCache.isUserOrganizationManaged(eq(profileId)))
+                .thenReturn(true);
         return userInfo;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index eccfa06..d3b647d 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -22,7 +22,7 @@
 import android.app.admin.DeviceStateCache;
 import android.content.Context;
 import android.content.pm.UserInfo;
-import android.hardware.authsecret.V1_0.IAuthSecret;
+import android.hardware.authsecret.IAuthSecret;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Process;
@@ -154,7 +154,7 @@
                 storageManager, spManager, gsiService, recoverableKeyStoreManager,
                 userManagerInternal, deviceStateCache));
         mGateKeeperService = gatekeeper;
-        mAuthSecretService = authSecretService;
+        mAuthSecretServiceAidl = authSecretService;
     }
 
     @Override
@@ -199,4 +199,4 @@
         UserInfo userInfo = mUserManager.getUserInfo(userId);
         return userInfo.isCloneProfile() || userInfo.isManagedProfile();
     }
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
index b9cafa4..b00467c 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/SyntheticPasswordTests.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.atLeastOnce;
@@ -36,8 +37,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.admin.PasswordMetrics;
 import android.app.PropertyInvalidatedCache;
+import android.app.admin.PasswordMetrics;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
@@ -59,6 +60,7 @@
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 
 /**
@@ -229,9 +231,11 @@
                 badPassword, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
 
         // Check the same secret was passed each time
-        ArgumentCaptor<ArrayList<Byte>> secret = ArgumentCaptor.forClass(ArrayList.class);
-        verify(mAuthSecretService, atLeastOnce()).primaryUserCredential(secret.capture());
-        assertEquals(1, secret.getAllValues().stream().distinct().count());
+        ArgumentCaptor<byte[]> secret = ArgumentCaptor.forClass(byte[].class);
+        verify(mAuthSecretService, atLeastOnce()).setPrimaryUserCredential(secret.capture());
+        for (byte[] val : secret.getAllValues()) {
+          assertArrayEquals(val, secret.getAllValues().get(0));
+        }
     }
 
     @Test
@@ -242,7 +246,7 @@
         reset(mAuthSecretService);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -252,7 +256,7 @@
         initializeCredential(password, SECONDARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, SECONDARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -263,7 +267,7 @@
 
         reset(mAuthSecretService);
         mLocalService.unlockUserKeyIfUnsecured(PRIMARY_USER_ID);
-        verify(mAuthSecretService).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
@@ -397,6 +401,8 @@
     @Test
     public void testEscrowTokenCannotBeActivatedOnUnmanagedUser() {
         byte[] token = "some-high-entropy-secure-token".getBytes();
+        when(mDeviceStateCache.isUserOrganizationManaged(anyInt())).thenReturn(false);
+        // TODO(b/258213147): Remove
         when(mUserManagerInternal.isDeviceManaged()).thenReturn(false);
         when(mUserManagerInternal.isUserManaged(PRIMARY_USER_ID)).thenReturn(false);
         when(mDeviceStateCache.isDeviceProvisioned()).thenReturn(true);
@@ -591,7 +597,7 @@
         initializeCredential(password, PRIMARY_USER_ID);
         assertEquals(VerifyCredentialResponse.RESPONSE_OK, mService.verifyCredential(
                 password, PRIMARY_USER_ID, 0 /* flags */).getResponseCode());
-        verify(mAuthSecretService, never()).primaryUserCredential(any(ArrayList.class));
+        verify(mAuthSecretService, never()).setPrimaryUserCredential(any(byte[].class));
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/OWNERS b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
new file mode 100644
index 0000000..832bcd9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/OWNERS
@@ -0,0 +1 @@
+include /media/java/android/media/projection/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
new file mode 100644
index 0000000..c81fbb4
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/CrossProfileAppsServiceImplTest.java
@@ -0,0 +1,781 @@
+package com.android.server.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+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 static org.testng.Assert.assertThrows;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ActivityOptions;
+import android.app.AppOpsManager;
+import android.app.IApplicationThread;
+import android.app.admin.DevicePolicyManagerInternal;
+import android.content.AttributionSourceState;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.PermissionChecker;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.CrossProfileAppsInternal;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.permission.PermissionCheckerManager;
+import android.permission.PermissionManager;
+import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:com.android.server.pm.CrossProfileAppsServiceImplTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public class CrossProfileAppsServiceImplTest {
+    private static final String PACKAGE_ONE = "com.one";
+    private static final String FEATURE_ID = "feature.one";
+    private static final int PACKAGE_ONE_UID = 1111;
+    private static final ComponentName ACTIVITY_COMPONENT =
+            new ComponentName("com.one", "test");
+
+    private static final String PACKAGE_TWO = "com.two";
+    private static final int PACKAGE_TWO_UID = 2222;
+
+    private static final int PRIMARY_USER = 0;
+    private static final int PROFILE_OF_PRIMARY_USER = 10;
+    private static final int SECONDARY_USER = 11;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private UserManager mUserManager;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private ActivityManagerInternal mActivityManagerInternal;
+    @Mock
+    private ActivityTaskManagerInternal mActivityTaskManagerInternal;
+    @Mock
+    private IPackageManager mIPackageManager;
+    @Mock
+    private DevicePolicyManagerInternal mDevicePolicyManagerInternal;
+
+    private TestInjector mTestInjector;
+    private ActivityInfo mActivityInfo;
+    private CrossProfileAppsServiceImpl mCrossProfileAppsServiceImpl;
+    private IApplicationThread mIApplicationThread;
+
+    private SparseArray<Boolean> mUserEnabled = new SparseArray<>();
+
+    @Before
+    public void initCrossProfileAppsServiceImpl() {
+        mTestInjector = new TestInjector();
+        LocalServices.removeServiceForTest(CrossProfileAppsInternal.class);
+        mCrossProfileAppsServiceImpl = new CrossProfileAppsServiceImpl(mContext, mTestInjector);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Before
+    public void setupEnabledProfiles() {
+        mUserEnabled.put(PRIMARY_USER, true);
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, true);
+        mUserEnabled.put(SECONDARY_USER, true);
+
+        when(mUserManager.getEnabledProfileIds(anyInt())).thenAnswer(
+                invocation -> {
+                    List<Integer> users = new ArrayList<>();
+                    final int targetUser = invocation.getArgument(0);
+                    users.add(targetUser);
+
+                    int profileUserId = -1;
+                    if (targetUser == PRIMARY_USER) {
+                        profileUserId = PROFILE_OF_PRIMARY_USER;
+                    } else if (targetUser == PROFILE_OF_PRIMARY_USER) {
+                        profileUserId = PRIMARY_USER;
+                    }
+
+                    if (profileUserId != -1 && mUserEnabled.get(profileUserId)) {
+                        users.add(profileUserId);
+                    }
+                    return users.stream().mapToInt(i -> i).toArray();
+                });
+    }
+
+    @Before
+    public void setupCaller() {
+        mTestInjector.setCallingUid(PACKAGE_ONE_UID);
+        mTestInjector.setCallingUserId(PRIMARY_USER);
+    }
+
+    @Before
+    public void setupPackage() throws Exception {
+        // PACKAGE_ONE are installed in all users.
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, true);
+        mockAppsInstalled(PACKAGE_ONE, SECONDARY_USER, true);
+
+        // Packages are resolved to their corresponding UID.
+        doAnswer(invocation -> {
+            final int uid = invocation.getArgument(0);
+            final String packageName = invocation.getArgument(1);
+            if (uid == PACKAGE_ONE_UID && PACKAGE_ONE.equals(packageName)) {
+                return null;
+            } else if (uid ==PACKAGE_TWO_UID && PACKAGE_TWO.equals(packageName)) {
+                return null;
+            }
+            throw new SecurityException("Not matching");
+        }).when(mAppOpsManager).checkPackage(anyInt(), anyString());
+
+        // The intent is resolved to the ACTIVITY_COMPONENT.
+        mockActivityLaunchIntentResolvedTo(ACTIVITY_COMPONENT);
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_installed() throws Exception {
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PROFILE_OF_PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromPrimaryUser_userNotEnabled() throws Exception {
+        mUserEnabled.put(PROFILE_OF_PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromSecondaryUser() throws Exception {
+        mTestInjector.setCallingUserId(SECONDARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_installed() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).containsExactly(UserHandle.of(PRIMARY_USER));
+    }
+
+    @Test
+    public void getTargetUserProfiles_fromProfile_notInstalled() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+        mockAppsInstalled(PACKAGE_ONE, PRIMARY_USER, false);
+
+        List<UserHandle> targetProfiles =
+                mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_ONE);
+        assertThat(targetProfiles).isEmpty();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void getTargetUserProfiles_fakeCaller() throws Exception {
+        mCrossProfileAppsServiceImpl.getTargetUserProfiles(PACKAGE_TWO);
+    }
+
+    @Test
+    public void startActivityAsUser_currentUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_currentUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notInstalled() throws Exception {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notInstalled() {
+        mockAppsInstalled(PACKAGE_ONE, PROFILE_OF_PRIMARY_USER, false);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_fakeCaller() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_fakeCaller() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_TWO,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_notExported() throws Exception {
+        mActivityInfo.exported = false;
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_notExported() {
+        try {
+            when(mPackageManager.getPermissionInfo(anyString(), anyInt()))
+                    .thenReturn(new PermissionInfo());
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+        mActivityInfo.exported = false;
+
+
+        // There's a bug in static mocking if the APK is large - so here is the next best thing...
+        doReturn(Context.PERMISSION_CHECKER_SERVICE).when(mContext)
+                .getSystemServiceName(PermissionCheckerManager.class);
+        PermissionCheckerManager permissionCheckerManager = mock(PermissionCheckerManager.class);
+        doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(permissionCheckerManager)
+                .checkPermission(eq(Manifest.permission.INTERACT_ACROSS_PROFILES), any(
+                        AttributionSourceState.class), anyString(), anyBoolean(), anyBoolean(),
+                        anyBoolean(), anyInt());
+        doReturn(permissionCheckerManager).when(mContext).getSystemService(
+                Context.PERMISSION_CHECKER_SERVICE);
+
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_profile_anotherPackage() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_profile_anotherPackage() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                new ComponentName(PACKAGE_TWO, "test"),
+                                UserHandle.of(PROFILE_OF_PRIMARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_secondaryUser() throws Exception {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                true,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startAnyActivityAsUser_secondaryUser() {
+        assertThrows(
+                SecurityException.class,
+                () ->
+                        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                                mIApplicationThread,
+                                PACKAGE_ONE,
+                                FEATURE_ID,
+                                ACTIVITY_COMPONENT,
+                                UserHandle.of(SECONDARY_USER).getIdentifier(),
+                                false,
+                                /* targetTask */ null,
+                                /* options */ null));
+
+        verify(mActivityTaskManagerInternal, never())
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        anyString(),
+                        nullable(String.class),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        anyInt());
+    }
+
+    @Test
+    public void startActivityAsUser_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                /* targetTask */ null,
+                /* options */ null);
+
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        nullable(IBinder.class),
+                        anyInt(),
+                        nullable(Bundle.class),
+                        eq(PRIMARY_USER));
+    }
+
+    @Test
+    public void startActivityAsUser_sameTask_fromProfile_success() throws Exception {
+        mTestInjector.setCallingUserId(PROFILE_OF_PRIMARY_USER);
+
+        Bundle options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle();
+        Binder targetTask = new Binder();
+        mCrossProfileAppsServiceImpl.startActivityAsUser(
+                mIApplicationThread,
+                PACKAGE_ONE,
+                FEATURE_ID,
+                ACTIVITY_COMPONENT,
+                UserHandle.of(PRIMARY_USER).getIdentifier(),
+                true,
+                targetTask,
+                options);
+        verify(mActivityTaskManagerInternal)
+                .startActivityAsUser(
+                        nullable(IApplicationThread.class),
+                        eq(PACKAGE_ONE),
+                        eq(FEATURE_ID),
+                        any(Intent.class),
+                        eq(targetTask),
+                        anyInt(),
+                        eq(options),
+                        eq(PRIMARY_USER));
+    }
+
+    private void mockAppsInstalled(String packageName, int user, boolean installed) {
+        when(mPackageManagerInternal.getPackageInfo(
+                eq(packageName),
+                anyLong(),
+                anyInt(),
+                eq(user)))
+                .thenReturn(installed ? createInstalledPackageInfo() : null);
+    }
+
+    private PackageInfo createInstalledPackageInfo() {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.enabled = true;
+        return packageInfo;
+    }
+
+    private void mockActivityLaunchIntentResolvedTo(ComponentName componentName) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.packageName = componentName.getPackageName();
+        activityInfo.name = componentName.getClassName();
+        activityInfo.exported = true;
+        resolveInfo.activityInfo = activityInfo;
+        mActivityInfo = activityInfo;
+
+        when(mPackageManagerInternal.queryIntentActivities(
+                any(Intent.class), nullable(String.class), anyLong(), anyInt(), anyInt()))
+                .thenReturn(Collections.singletonList(resolveInfo));
+    }
+
+    private class TestInjector implements CrossProfileAppsServiceImpl.Injector {
+        private int mCallingUid;
+        private int mCallingUserId;
+        private int mCallingPid;
+
+        public void setCallingUid(int uid) {
+            mCallingUid = uid;
+        }
+
+        public void setCallingPid(int pid) {
+            mCallingPid = pid;
+        }
+
+        public void setCallingUserId(int userId) {
+            mCallingUserId = userId;
+        }
+
+        @Override
+        public int getCallingUid() {
+            return mCallingUid;
+        }
+
+        @Override
+        public int getCallingPid() {
+            return mCallingPid;
+        }
+
+        @Override
+        public int getCallingUserId() {
+            return mCallingUserId;
+        }
+
+        @Override
+        public UserHandle getCallingUserHandle() {
+            return UserHandle.of(mCallingUserId);
+        }
+
+        @Override
+        public long clearCallingIdentity() {
+            return 0;
+        }
+
+        @Override
+        public void restoreCallingIdentity(long token) {
+        }
+
+        @Override
+        public void withCleanCallingIdentity(ThrowingRunnable action) {
+            action.run();
+        }
+
+        @Override
+        public <T> T withCleanCallingIdentity(ThrowingSupplier<T> action) {
+            return action.get();
+        }
+
+        @Override
+        public UserManager getUserManager() {
+            return mUserManager;
+        }
+
+        @Override
+        public PackageManagerInternal getPackageManagerInternal() {
+            return mPackageManagerInternal;
+        }
+
+        @Override
+        public PackageManager getPackageManager() {
+            return mPackageManager;
+        }
+
+        @Override
+        public AppOpsManager getAppOpsManager() {
+            return mAppOpsManager;
+        }
+
+        @Override
+        public ActivityManagerInternal getActivityManagerInternal() {
+            return mActivityManagerInternal;
+        }
+
+        @Override
+        public ActivityTaskManagerInternal getActivityTaskManagerInternal() {
+            return mActivityTaskManagerInternal;
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return mIPackageManager;
+        }
+
+        @Override
+        public DevicePolicyManagerInternal getDevicePolicyManagerInternal() {
+            return mDevicePolicyManagerInternal;
+        }
+
+        @Override
+        public void sendBroadcastAsUser(Intent intent, UserHandle user) {
+            mContext.sendBroadcastAsUser(intent, user);
+        }
+
+        @Override
+        public int checkComponentPermission(
+                String permission, int uid, int owningUid, boolean exported) {
+            return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported);
+        }
+
+        @Override
+        public void killUid(int uid) {
+            PermissionManagerService.killUid(
+                    UserHandle.getAppId(uid),
+                    UserHandle.getUserId(uid),
+                    PermissionManager.KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
index 3848bab..57f9f18 100644
--- a/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/ThermalManagerServiceTest.java
@@ -30,8 +30,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.hardware.thermal.V2_0.TemperatureThreshold;
-import android.hardware.thermal.V2_0.ThrottlingSeverity;
+import android.hardware.thermal.TemperatureThreshold;
+import android.hardware.thermal.ThrottlingSeverity;
 import android.os.CoolingDevice;
 import android.os.IBinder;
 import android.os.IPowerManager;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
index e9c5b01..5b51868 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
@@ -359,6 +359,7 @@
         // map of ActivityManager process states and how long to simulate run time in each state
         Map<Integer, Integer> stateRuntimeMap = new HashMap<Integer, Integer>();
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP, 1111);
+        stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_TOP, 7382);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 1234);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 2468);
         stateRuntimeMap.put(ActivityManager.PROCESS_STATE_TOP_SLEEPING, 7531);
@@ -395,7 +396,8 @@
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
-        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE)
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING,
@@ -405,8 +407,7 @@
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_FOREGROUND,
                 elapsedTimeUs, STATS_SINCE_CHARGED);
-        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_BACKGROUND,
@@ -414,7 +415,8 @@
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER)
+                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BOUND_TOP);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
 
         actualRunTimeUs = uid.getProcessStateTime(BatteryStats.Uid.PROCESS_STATE_CACHED,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
index b313fa4..9d46e11 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
@@ -78,9 +78,9 @@
                 batteryUsageStats.getUidBatteryConsumers();
         final UidBatteryConsumer uidBatteryConsumer = uidBatteryConsumers.get(0);
         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_FOREGROUND))
-                .isEqualTo(60 * MINUTE_IN_MS);
+                .isEqualTo(20 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getTimeInStateMs(UidBatteryConsumer.STATE_BACKGROUND))
-                .isEqualTo(10 * MINUTE_IN_MS);
+                .isEqualTo(40 * MINUTE_IN_MS);
         assertThat(uidBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AUDIO))
                 .isWithin(PRECISION).of(2.0);
         assertThat(
@@ -121,40 +121,44 @@
     private BatteryStatsImpl prepareBatteryStats() {
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
+        mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
         synchronized (batteryStats) {
-            batteryStats.noteActivityResumedLocked(APP_UID,
-                    10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteUidProcessStateLocked(APP_UID,
-                    ActivityManager.PROCESS_STATE_TOP,
-                    10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteActivityPausedLocked(APP_UID,
-                    30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteUidProcessStateLocked(APP_UID,
-                    ActivityManager.PROCESS_STATE_SERVICE,
-                    30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteUidProcessStateLocked(APP_UID,
-                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
-                    40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteUidProcessStateLocked(APP_UID,
-                    ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE,
-                    50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
-        }
-        synchronized (batteryStats) {
-            batteryStats.noteUidProcessStateLocked(APP_UID,
-                    ActivityManager.PROCESS_STATE_CACHED_EMPTY,
-                    80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
+            batteryStats.noteActivityResumedLocked(APP_UID);
         }
 
+        mStatsRule.setTime(10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID, ActivityManager.PROCESS_STATE_TOP);
+        }
+        mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteActivityPausedLocked(APP_UID);
+        }
+        mStatsRule.setTime(30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_SERVICE);
+        }
+        mStatsRule.setTime(40 * MINUTE_IN_MS, 40 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        }
+        mStatsRule.setTime(50 * MINUTE_IN_MS, 50 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+        }
+        mStatsRule.setTime(60 * MINUTE_IN_MS, 60 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_BOUND_TOP);
+        }
+        mStatsRule.setTime(70 * MINUTE_IN_MS, 70 * MINUTE_IN_MS);
+        synchronized (batteryStats) {
+            batteryStats.noteUidProcessStateLocked(APP_UID,
+                    ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+        }
         synchronized (batteryStats) {
             batteryStats.noteFlashlightOnLocked(APP_UID, 1000, 1000);
         }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
index 1815e2a..7c1962c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.annotation.XmlRes;
 import android.content.Context;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -50,6 +51,7 @@
                     .powerProfileModeledOnly()
                     .includePowerModels()
                     .build();
+    private final Context mContext;
 
     private final PowerProfile mPowerProfile;
     private final MockClock mMockClock = new MockClock();
@@ -67,14 +69,19 @@
     }
 
     public BatteryUsageStatsRule(long currentTime, File historyDir) {
-        Context context = InstrumentationRegistry.getContext();
-        mPowerProfile = spy(new PowerProfile(context, true /* forTest */));
+        mContext = InstrumentationRegistry.getContext();
+        mPowerProfile = spy(new PowerProfile(mContext, true /* forTest */));
         mMockClock.currentTime = currentTime;
         mBatteryStats = new MockBatteryStatsImpl(mMockClock, historyDir);
         mBatteryStats.setPowerProfile(mPowerProfile);
         mBatteryStats.onSystemReady();
     }
 
+    public BatteryUsageStatsRule setTestPowerProfile(@XmlRes int xmlId) {
+        mPowerProfile.forceInitForTesting(mContext, xmlId);
+        return this;
+    }
+
     public BatteryUsageStatsRule setAveragePower(String key, double value) {
         when(mPowerProfile.getAveragePower(key)).thenReturn(value);
         when(mPowerProfile.getAveragePowerOrDefault(eq(key), anyDouble())).thenReturn(value);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index 8262fca..5bc73bd 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.usage.NetworkStatsManager;
+import android.hardware.radio.V1_5.AccessNetwork;
 import android.net.NetworkCapabilities;
 import android.net.NetworkStats;
 import android.os.BatteryConsumer;
@@ -34,6 +35,8 @@
 import android.os.BatteryUsageStatsQuery;
 import android.os.Process;
 import android.os.UidBatteryConsumer;
+import android.telephony.ActivityStatsTechSpecificInfo;
+import android.telephony.CellSignalStrength;
 import android.telephony.DataConnectionRealTimeInfo;
 import android.telephony.ModemActivityInfo;
 import android.telephony.ServiceState;
@@ -43,7 +46,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.os.PowerProfile;
+import com.android.frameworks.servicestests.R;
 
 import com.google.common.collect.Range;
 
@@ -52,28 +55,25 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 
+import java.util.ArrayList;
+
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 @SuppressWarnings("GuardedBy")
 public class MobileRadioPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
+    private static final int APP_UID2 = Process.FIRST_APPLICATION_UID + 101;
     @Mock
     NetworkStatsManager mNetworkStatsManager;
 
     @Rule
-    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule()
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ACTIVE)
-            .setAveragePowerUnspecified(PowerProfile.POWER_RADIO_ON)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_IDLE, 360.0)
-            .setAveragePower(PowerProfile.POWER_RADIO_SCANNING, 720.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_RX, 1440.0)
-            .setAveragePower(PowerProfile.POWER_MODEM_CONTROLLER_TX,
-                    new double[]{720.0, 1080.0, 1440.0, 1800.0, 2160.0})
-            .initMeasuredEnergyStatsLocked();
+    public final BatteryUsageStatsRule mStatsRule = new BatteryUsageStatsRule();
 
     @Test
     public void testCounterBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -85,9 +85,15 @@
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -97,10 +103,31 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         NetworkStats networkStats = new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
         mStatsRule.setNetworkStats(networkStats);
 
         ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
@@ -108,34 +135,331 @@
         stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
 
-        mStatsRule.setTime(12_000, 12_000);
+        mStatsRule.setTime(10_000, 10_000);
 
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(2.2444);
+                .isWithin(PRECISION).of(1.27888);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
 
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(0.8);
+                .isWithin(PRECISION).of(0.94);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_multipleDefinedRat() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_modem_calculator_multiactive)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ActivityStatsTechSpecificInfo cdmaInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.CDMA2000, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{10, 11, 12, 13, 14}, 15);
+        ActivityStatsTechSpecificInfo lteInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.EUTRAN, ServiceState.FREQUENCY_RANGE_UNKNOWN,
+                new int[]{20, 21, 22, 23, 24}, 25);
+        ActivityStatsTechSpecificInfo nrLowFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_LOW,
+                new int[]{30, 31, 32, 33, 34}, 35);
+        ActivityStatsTechSpecificInfo nrMidFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MID,
+                new int[]{40, 41, 42, 43, 44}, 45);
+        ActivityStatsTechSpecificInfo nrHighFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_HIGH,
+                new int[]{50, 51, 52, 53, 54}, 55);
+        ActivityStatsTechSpecificInfo nrMmwaveFreqInfo = new ActivityStatsTechSpecificInfo(
+                AccessNetwork.NGRAN, ServiceState.FREQUENCY_RANGE_MMWAVE,
+                new int[]{60, 61, 62, 63, 64}, 65);
+
+        ActivityStatsTechSpecificInfo[] ratInfos =
+                new ActivityStatsTechSpecificInfo[]{cdmaInfo, lteInfo, nrLowFreqInfo, nrMidFreqInfo,
+                        nrHighFreqInfo, nrMmwaveFreqInfo};
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000, ratInfos);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        // CDMA2000 [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [720, 1080, 1440, 1800, 2160, 1440] mA . [10, 11, 12, 13, 14, 15] ms = 111600 mA-ms
+        // LTE [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [800, 1200, 1600, 2000, 2400, 2000] mA . [20, 21, 22, 23, 24, 25] ms = 230000 mA-ms
+        // 5G Low Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="LOW" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [30, 31, 32, 33, 34, 35] ms = 373449 mA-ms
+        // 5G Mid Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        // (nrFrequency="MID" values was not defined so fall back to nrFrequency="DEFAULT")
+        //   [999, 1333, 1888, 2222, 2666, 2222] mA . [40, 41, 42, 43, 44, 45] ms = 486749 mA-ms
+        // 5G High Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [1818, 2727, 3636, 4545, 5454, 2727] mA . [50, 51, 52, 53, 54, 55] ms = 1104435 mA-ms
+        // 5G Mmwave Frequency [Tx0, Tx1, Tx2, Tx3, Tx4, Rx] drain * duration
+        //   [2345, 3456, 4567, 5678, 6789, 3456] mA . [60, 61, 62, 63, 64, 65] ms = 1651520 mA-ms
+        // _________________
+        // =    3957753 mA-ms or 1.09938 mA-h active consumption
+        //
+        // Idle drain rate * idle duration
+        //   360 mA * 3000 ms = 1080000 mA-ms
+        // Sleep drain rate * sleep duration
+        //   70 mA * 2000 ms = 140000 mA-ms
+        // _________________
+        // =    5177753 mA-ms or 1.43826 mA-h total consumption
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.43826);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.09938);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.82453);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.27484);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+    }
+
+    @Test
+    public void testCounterBasedModel_legacyPowerProfile() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
+        BatteryStatsImpl stats = mStatsRule.getBatteryStats();
+
+        // Scan for a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_OUT_OF_SERVICE,
+                TelephonyManager.SIM_STATE_READY,
+                2000, 2000);
+
+        // Found a cell
+        stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
+                5000, 5000);
+
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
+        // Note cell signal strength
+        SignalStrength signalStrength = mock(SignalStrength.class);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                8_000_000_000L, APP_UID, 8000, 8000);
+
+        // Note established network
+        stats.noteNetworkInterfaceForTransports("cellular",
+                new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
+
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+
+        stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
+                9_500_000_000L, APP_UID2, 9500, 9500);
+
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
+        // Note application network activity
+        NetworkStats networkStats = new NetworkStats(10000, 1)
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 150, 2000, 30, 100))
+                .addEntry(new NetworkStats.Entry("cellular", APP_UID2, 0, 0,
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 500, 50, 300, 10, 111));
+        mStatsRule.setNetworkStats(networkStats);
+
+        ModemActivityInfo mai = new ModemActivityInfo(10000, 2000, 3000,
+                new int[]{100, 200, 300, 400, 500}, 600);
+        stats.noteModemControllerActivity(mai, POWER_DATA_UNAVAILABLE, 10000, 10000,
+                mNetworkStatsManager);
+
+        mStatsRule.setTime(10_000, 10_000);
+
+        MobileRadioPowerCalculator calculator =
+                new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
+
+        mStatsRule.apply(BatteryUsageStatsRule.POWER_PROFILE_MODEL_ONLY, calculator);
+
+        BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // +  360 mA * 3000 ms (idle drain rate * idle duration)
+        // +   70 mA * 2000 ms (sleep drain rate * sleep duration)
+        // _________________
+        // =    4604000 mA-ms or 1.27888 mA-h
+        assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.27888);
+        assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        //    720 mA * 100 ms  (level 0 TX drain rate * level 0 TX duration)
+        // + 1080 mA * 200 ms  (level 1 TX drain rate * level 1 TX duration)
+        // + 1440 mA * 300 ms  (level 2 TX drain rate * level 2 TX duration)
+        // + 1800 mA * 400 ms  (level 3 TX drain rate * level 3 TX duration)
+        // + 2160 mA * 500 ms  (level 4 TX drain rate * level 4 TX duration)
+        // + 1440 mA * 600 ms  (RX drain rate * RX duration)
+        // _________________
+        // =    3384000 mA-ms or 0.94 mA-h
+        BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
+        assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.94);
+        assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // 3/4 of total packets were sent by APP_UID so 75% of total
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.705);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
+
+        // Rest should go to the other app
+        UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
+        assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(0.235);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_POWER_PROFILE);
     }
 
     @Test
     public void testTimerBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -148,13 +472,19 @@
                 TelephonyManager.SIM_STATE_READY,
                 2000, 2000);
 
+        ArrayList<CellSignalStrength> perRatCellStrength = new ArrayList();
+        CellSignalStrength gsmSignalStrength = mock(CellSignalStrength.class);
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        perRatCellStrength.add(gsmSignalStrength);
+
         // Found a cell
         stats.notePhoneStateLocked(ServiceState.STATE_IN_SERVICE, TelephonyManager.SIM_STATE_READY,
                 5000, 5000);
 
         // Note cell signal strength
         SignalStrength signalStrength = mock(SignalStrength.class);
-        when(signalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        when(signalStrength.getCellSignalStrengths()).thenReturn(perRatCellStrength);
         stats.notePhoneSignalStrengthLocked(signalStrength, 5000, 5000);
 
         stats.noteMobileRadioPowerStateLocked(DataConnectionRealTimeInfo.DC_POWER_STATE_HIGH,
@@ -164,10 +494,25 @@
         stats.noteNetworkInterfaceForTransports("cellular",
                 new int[]{NetworkCapabilities.TRANSPORT_CELLULAR});
 
+        // Spend some time in each signal strength level. It doesn't matter how long.
+        // The ModemActivityInfo reported level residency should be trusted over the BatteryStats
+        // timers.
+        when(gsmSignalStrength.getLevel()).thenReturn(
+                SignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8111, 8111);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_POOR);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8333, 8333);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_MODERATE);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 8666, 8666);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GOOD);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9110, 9110);
+        when(gsmSignalStrength.getLevel()).thenReturn(SignalStrength.SIGNAL_STRENGTH_GREAT);
+        stats.notePhoneSignalStrengthLocked(signalStrength, 9665, 9665);
+
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 10000, 10000,
                 mNetworkStatsManager);
@@ -177,7 +522,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
                 mNetworkStatsManager);
@@ -199,6 +544,8 @@
         MobileRadioPowerCalculator calculator =
                 new MobileRadioPowerCalculator(mStatsRule.getPowerProfile());
 
+        mStatsRule.setTime(12_000, 12_000);
+
         mStatsRule.apply(new BatteryUsageStatsQuery.Builder()
                 .powerProfileModeledOnly()
                 .includePowerModels()
@@ -217,6 +564,10 @@
                 BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE);
 
+
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.6);
+
         assertThat(uidConsumer.getConsumedPower(foreground)).isWithin(PRECISION).of(1.2);
         assertThat(uidConsumer.getConsumedPower(background)).isWithin(PRECISION).of(0.4);
         assertThat(uidConsumer.getConsumedPower(fgs)).isWithin(PRECISION).of(0);
@@ -224,6 +575,8 @@
 
     @Test
     public void testMeasuredEnergyBasedModel() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
         // Scan for a cell
@@ -264,12 +617,6 @@
 
         mStatsRule.apply(calculator);
 
-        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
-        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isWithin(PRECISION).of(1.53934);
-        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
-
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         // 10_000_000 micro-Coulomb * 1/1000 milli/micro * 1/3600 hour/second = 2.77778 mAh
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
@@ -282,10 +629,18 @@
                 .isWithin(PRECISION).of(1.53934);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+
+        UidBatteryConsumer uidConsumer = mStatsRule.getUidBatteryConsumer(APP_UID);
+        assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isWithin(PRECISION).of(1.53934);
+        assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
+                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
     }
 
     @Test
     public void testMeasuredEnergyBasedModel_byProcessState() {
+        mStatsRule.setTestPowerProfile(R.xml.power_profile_test_legacy_modem)
+                .initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
         BatteryStatsImpl.Uid uid = stats.getUidStatsLocked(APP_UID);
 
@@ -317,7 +672,7 @@
         // Note application network activity
         mStatsRule.setNetworkStats(new NetworkStats(10000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 100, 2000, 20, 100)));
 
         stats.noteModemControllerActivity(null, 10_000_000, 10000, 10000, mNetworkStatsManager);
 
@@ -326,7 +681,7 @@
 
         mStatsRule.setNetworkStats(new NetworkStats(12000, 1)
                 .addEntry(new NetworkStats.Entry("cellular", APP_UID, 0, 0,
-                    METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
+                        METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, 1000, 250, 2000, 80, 200)));
 
         stats.noteModemControllerActivity(null, 15_000_000, 12000, 12000, mNetworkStatsManager);
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index a03a1b4..ebcb498 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -234,6 +234,37 @@
     }
 
     @Test
+    public void testScheduleRepostsForLongTagPersistedNotification() throws Exception {
+        String longTag = "A".repeat(66000);
+        NotificationRecord r = getNotificationRecord("pkg", 1, longTag, UserHandle.SYSTEM);
+        mSnoozeHelper.snooze(r, 0);
+
+        // We store the full key in temp storage.
+        ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
+        verify(mAm).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(66010, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+        mSnoozeHelper.writeXml(serializer);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        parser.setInput(new BufferedInputStream(
+                new ByteArrayInputStream(baos.toByteArray())), "utf-8");
+        mSnoozeHelper.readXml(parser, 4);
+
+        mSnoozeHelper.scheduleRepostsForPersistedNotifications(5);
+
+        // We trim the key in persistent storage.
+        verify(mAm, times(2)).setExactAndAllowWhileIdle(anyInt(), anyLong(), captor.capture());
+        assertEquals(1000, captor.getValue().getIntent().getStringExtra(EXTRA_KEY).length());
+    }
+
+    @Test
     public void testSnoozeForTime() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r, 1000);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 85c4975..4e001fe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -106,7 +106,7 @@
         mTransition.mParticipants.add(mTopActivity);
         mTopActivity.mTransitionController.moveToCollecting(mTransition);
         // becomes invisible when covered by mTopActivity
-        mTrampolineActivity.mVisibleRequested = false;
+        mTrampolineActivity.setVisibleRequested(false);
     }
 
     private <T> T verifyAsync(T mock) {
@@ -235,7 +235,7 @@
     public void testOnActivityLaunchCancelled_hasDrawn() {
         onActivityLaunched(mTopActivity);
 
-        mTopActivity.mVisibleRequested = true;
+        mTopActivity.setVisibleRequested(true);
         doReturn(true).when(mTopActivity).isReportedDrawn();
 
         // Cannot time already-visible activities.
@@ -258,7 +258,7 @@
         notifyActivityLaunching(noDrawnActivity.intent);
         notifyAndVerifyActivityLaunched(noDrawnActivity);
 
-        noDrawnActivity.mVisibleRequested = false;
+        noDrawnActivity.setVisibleRequested(false);
         mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
 
         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
@@ -286,7 +286,7 @@
 
         clearInvocations(mLaunchObserver);
         mLaunchTopByTrampoline = true;
-        mTopActivity.mVisibleRequested = false;
+        mTopActivity.setVisibleRequested(false);
         notifyActivityLaunching(mTopActivity.intent);
         // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
         // the launch event is still valid.
@@ -314,7 +314,7 @@
         // Create an invisible event that should be cancelled after the next event starts.
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
         onActivityLaunched(prev);
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
 
         mActivityOptions = ActivityOptions.makeBasic();
         mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
@@ -561,7 +561,7 @@
     @Test
     public void testConsecutiveLaunchWithDifferentWindowingMode() {
         mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
-        mTrampolineActivity.mVisibleRequested = true;
+        mTrampolineActivity.setVisibleRequested(true);
         onActivityLaunched(mTrampolineActivity);
         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index a410eed..f983fa9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -917,7 +917,7 @@
         // Prepare the activity record to be ready for immediate removal. It should be invisible and
         // have no process. Otherwise, request to finish it will send a message to client first.
         activity.setState(STOPPED, "test");
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         // Set process to 'null' to allow immediate removal, but don't call mActivity.setProcess() -
         // this will cause NPE when updating task's process.
@@ -927,7 +927,7 @@
         // next activity reports idle to destroy it.
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "test");
 
@@ -1082,7 +1082,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1099,7 +1099,7 @@
         final ActivityRecord activity = createActivityWithTask();
         clearInvocations(activity.mDisplayContent);
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(PAUSED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1118,7 +1118,7 @@
         // Put an activity on top of test activity to make it invisible and prevent us from
         // accidentally resuming the topmost one again.
         new ActivityBuilder(mAtm).build();
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         activity.finishIfPossible("test", false /* oomAdj */);
@@ -1136,7 +1136,7 @@
         final TestTransitionPlayer testPlayer = registerTestTransitionPlayer();
         final ActivityRecord activity = createActivityWithTask();
         activity.finishing = false;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(RESUMED, "test");
         activity.finishIfPossible("test", false /* oomAdj */);
 
@@ -1273,7 +1273,7 @@
         final ActivityRecord currentTop = createActivityWithTask();
         final Task task = currentTop.getTask();
 
-        currentTop.mVisibleRequested = currentTop.nowVisible = true;
+        currentTop.setVisibleRequested(currentTop.nowVisible = true);
 
         // Simulates that {@code currentTop} starts an existing activity from background (so its
         // state is stopped) and the starting flow just goes to place it at top.
@@ -1300,7 +1300,7 @@
         final ActivityRecord bottomActivity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(bottomActivity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         // simulating bottomActivity as a trampoline activity.
         bottomActivity.setState(RESUMED, "test");
         bottomActivity.finishIfPossible("test", false);
@@ -1316,13 +1316,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as not visible, so that we will wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1346,13 +1346,13 @@
         final ActivityRecord topActivity = createActivityWithTask();
         mDisplayContent.setIsSleeping(true);
         doReturn(true).when(activity).shouldBeVisible();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
 
         // Mark the activity behind (on a separate task) as not visible
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1370,13 +1370,13 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as not visible, so that we would wait for it before removing
         // the top one.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.nowVisible = false;
         activity.setState(STOPPED, "test");
 
@@ -1394,12 +1394,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1417,12 +1417,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = false;
+        topActivity.setVisibleRequested(false);
         topActivity.nowVisible = false;
         topActivity.finishing = true;
         topActivity.setState(STOPPED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1440,12 +1440,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final ActivityRecord topActivity = new ActivityBuilder(mAtm)
                 .setTask(activity.getTask()).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.finishing = true;
         topActivity.setState(PAUSED, "true");
         // Mark the bottom activity as already visible, so that there is no need to wait for it.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.nowVisible = true;
         activity.setState(RESUMED, "test");
 
@@ -1454,7 +1454,7 @@
         final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
         final ActivityRecord focusedActivity = stack.getTopMostActivity();
         focusedActivity.nowVisible = true;
-        focusedActivity.mVisibleRequested = true;
+        focusedActivity.setVisibleRequested(true);
         focusedActivity.setState(RESUMED, "test");
         stack.setResumedActivity(focusedActivity, "test");
 
@@ -1476,7 +1476,7 @@
         int displayId = activity.getDisplayId();
         doReturn(true).when(keyguardController).isKeyguardLocked(eq(displayId));
         final ActivityRecord topActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        topActivity.mVisibleRequested = true;
+        topActivity.setVisibleRequested(true);
         topActivity.nowVisible = true;
         topActivity.setState(RESUMED, "true");
         doCallRealMethod().when(mRootWindowContainer).ensureActivitiesVisible(
@@ -1515,12 +1515,12 @@
         final ActivityRecord activity = createActivityWithTask();
         final Task task = activity.getTask();
         final ActivityRecord firstActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        firstActivity.mVisibleRequested = false;
+        firstActivity.setVisibleRequested(false);
         firstActivity.nowVisible = false;
         firstActivity.setState(STOPPED, "test");
 
         final ActivityRecord secondActivity = new ActivityBuilder(mAtm).setTask(task).build();
-        secondActivity.mVisibleRequested = true;
+        secondActivity.setVisibleRequested(true);
         secondActivity.nowVisible = true;
         secondActivity.setState(secondActivityState, "test");
 
@@ -1530,7 +1530,7 @@
         } else {
             translucentActivity = new ActivityBuilder(mAtm).setTask(task).build();
         }
-        translucentActivity.mVisibleRequested = true;
+        translucentActivity.setVisibleRequested(true);
         translucentActivity.nowVisible = true;
         translucentActivity.setState(RESUMED, "test");
 
@@ -1546,7 +1546,7 @@
 
         // Finish the first activity
         firstActivity.finishing = true;
-        firstActivity.mVisibleRequested = true;
+        firstActivity.setVisibleRequested(true);
         firstActivity.completeFinishing("test");
         verify(firstActivity.mDisplayContent, times(2)).ensureActivitiesVisible(null /* starting */,
                 0 /* configChanges */ , false /* preserveWindows */,
@@ -1614,7 +1614,7 @@
         }, true /* traverseTopToBottom */);
         activity.setState(STARTED, "test");
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         // Try to finish the last activity above the home stack.
         activity.completeFinishing("test");
@@ -1909,7 +1909,7 @@
         // Simulate that the activity requests the same orientation as display.
         activity.setOrientation(display.getConfiguration().orientation);
         // Skip the real freezing.
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         clearInvocations(activity);
         activity.onCancelFixedRotationTransform(originalRotation);
         // The implementation of cancellation must be executed.
@@ -2535,7 +2535,7 @@
 
         activity.setOccludesParent(true);
         activity.setVisible(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         // Can not specify orientation if app isn't visible even though it occludes parent.
         assertEquals(SCREEN_ORIENTATION_UNSET, activity.getOrientation());
         // Can specify orientation if the current orientation candidate is orientation behind.
@@ -2910,7 +2910,7 @@
         task.addChild(taskFragment2, POSITION_TOP);
         final ActivityRecord activity2 = new ActivityBuilder(mAtm)
                 .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         taskFragment2.addChild(activity2);
         assertTrue(activity2.isResizeable());
         activity1.reparent(taskFragment1, POSITION_TOP);
@@ -3055,7 +3055,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3064,7 +3064,7 @@
         // until we verify no logic relies on this behavior, we'll keep this as is.
         activity.setVisibility(true);
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3075,7 +3075,7 @@
                 .setCreateTask(true).build();
         // By default, activity is visible.
         assertTrue(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3083,7 +3083,7 @@
         // animation should be applied on this activity.
         activity.setVisibility(false);
         assertTrue(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertTrue(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3095,7 +3095,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3103,7 +3103,7 @@
         // animation should be applied on this activity.
         activity.setVisibility(true);
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3126,7 +3126,7 @@
         // Activiby is invisible. However ATMS requests it to become visible, since this is a top
         // activity.
         assertFalse(activity.isVisible());
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
         assertTrue(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
 
@@ -3134,7 +3134,7 @@
         // transition should be applied on this activity.
         activity.setVisibility(false);
         assertFalse(activity.isVisible());
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
         assertFalse(activity.mDisplayContent.mOpeningApps.contains(activity));
         assertFalse(activity.mDisplayContent.mClosingApps.contains(activity));
     }
@@ -3549,12 +3549,12 @@
         activity.reparent(taskFragment, POSITION_TOP);
 
         // Ensure the activity visibility is updated even it is not shown to current user.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(false).when(activity).showToCurrentUser();
         spyOn(taskFragment);
         doReturn(false).when(taskFragment).shouldBeVisible(any());
         display.ensureActivitiesVisible(null, 0, false, false);
-        assertFalse(activity.mVisibleRequested);
+        assertFalse(activity.isVisibleRequested());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 0743ef4..d16e11b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -62,11 +62,13 @@
 import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
+import static com.android.server.wm.WindowTestsBase.ActivityBuilder.DEFAULT_FAKE_UID;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -514,7 +516,9 @@
                 .setCreateActivity(true)
                 .build()
                 .getTopMostActivity();
-        splitPrimaryActivity.mVisibleRequested = splitSecondActivity.mVisibleRequested = true;
+
+        splitPrimaryActivity.setVisibleRequested(true);
+        splitSecondActivity.setVisibleRequested(true);
 
         assertEquals(splitOrg.mPrimary, splitPrimaryActivity.getRootTask());
         assertEquals(splitOrg.mSecondary, splitSecondActivity.getRootTask());
@@ -527,7 +531,7 @@
                 .setCreateActivity(true).build().getTopMostActivity();
         final ActivityRecord translucentActivity = new TaskBuilder(mSupervisor)
                 .setCreateActivity(true).build().getTopMostActivity();
-        assertTrue(activity.mVisibleRequested);
+        assertTrue(activity.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK,
                 false /* mockGetRootTask */);
@@ -1050,7 +1054,7 @@
                         ACTIVITY_TYPE_STANDARD, false /* onTop */));
         // Activity should start invisible since we are bringing it to front.
         singleTaskActivity.setVisible(false);
-        singleTaskActivity.mVisibleRequested = false;
+        singleTaskActivity.setVisibleRequested(false);
 
         // Create another activity on top of the secondary display.
         final Task topStack = secondaryTaskContainer.createRootTask(WINDOWING_MODE_FULLSCREEN,
@@ -1268,7 +1272,7 @@
         final ActivityStarter starter = prepareStarter(0 /* flags */);
         final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build();
         starter.mStartActivity = target;
-        target.mVisibleRequested = false;
+        target.setVisibleRequested(false);
         target.setTurnScreenOn(true);
         // Assume the flag was consumed by relayout.
         target.setCurrentLaunchCanTurnScreenOn(false);
@@ -1589,10 +1593,10 @@
         final ActivityRecord activityTop = new ActivityBuilder(mAtm).setTask(task).build();
 
         activityBot.setVisible(false);
-        activityBot.mVisibleRequested = false;
+        activityBot.setVisibleRequested(false);
 
         assertTrue(activityTop.isVisible());
-        assertTrue(activityTop.mVisibleRequested);
+        assertTrue(activityTop.isVisibleRequested());
 
         final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_REORDER_TO_FRONT
                         | FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */);
@@ -1615,6 +1619,119 @@
         assertNull(starter2.mMovedToTopActivity);
     }
 
+    /**
+     * Tests a task with specific display category exist in system and then launching another
+     * activity with the same affinity but without define the display category. Make sure the
+     * lunching activity is placed on the different task.
+     */
+    @Test
+    public void testLaunchActivityWithoutDisplayCategory() {
+        final ActivityInfo info = new ActivityInfo();
+        info.applicationInfo = new ApplicationInfo();
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
+                0 /* launchMode */);
+        info.requiredDisplayCategory = "automotive";
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
+                .build();
+
+        final ActivityRecord target = new ActivityBuilder(mAtm).setAffinity(info.taskAffinity)
+                .build();
+        final ActivityStarter starter = prepareStarter(FLAG_ACTIVITY_NEW_TASK, false);
+        startActivityInner(starter, target, null /* source */, null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertNotEquals(task, target.getTask());
+    }
+
+    /**
+     * Tests a task with a specific display category exist in the system and then launches another
+     * activity with the different display category. Make sure the launching activity is not placed
+     * on the sourceRecord's task.
+     */
+    @Test
+    public void testLaunchActivityWithDifferentDisplayCategory() {
+        final ActivityInfo info = new ActivityInfo();
+        info.applicationInfo = new ApplicationInfo();
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
+                0 /* launchMode */);
+        info.requiredDisplayCategory = "automotive";
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
+                .build();
+
+        final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
+                .setAffinity(info.taskAffinity).build();
+        final ActivityStarter starter = prepareStarter(0, false);
+        startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertNotEquals(task, target.getTask());
+    }
+
+    /**
+     * Tests a task with specific display category exist in system and then launching another
+     * activity with the same display category. Make sure the launching activity is placed on the
+     * same task.
+     */
+    @Test
+    public void testLaunchActivityWithSameDisplayCategory() {
+        final ActivityInfo info = new ActivityInfo();
+        info.applicationInfo = new ApplicationInfo();
+        info.taskAffinity = ActivityRecord.computeTaskAffinity("test", DEFAULT_FAKE_UID,
+                0 /* launchMode */);
+        info.requiredDisplayCategory = "automotive";
+        final Task task = new TaskBuilder(mSupervisor).setCreateActivity(true).setActivityInfo(info)
+                .build();
+
+        final ActivityRecord target = new ActivityBuilder(mAtm)
+                .setRequiredDisplayCategory(info.requiredDisplayCategory)
+                .setAffinity(info.taskAffinity).build();
+        final ActivityStarter starter = prepareStarter(0, false);
+        startActivityInner(starter, target,  task.getBottomMostActivity(), null /* options */,
+                null /* inTask */, null /* inTaskFragment */);
+
+        assertEquals(task, target.getTask());
+    }
+
+    /**
+     * Tests a task with specific display category exist in system and launching activity into the
+     * specific task within inTask attribute. Make sure the activity is not placed on the task since
+     * the display category is different.
+     */
+    @Test
+    public void testLaunchActivityInTaskWithDisplayCategory() {
+        final ActivityInfo info = new ActivityInfo();
+        info.applicationInfo = new ApplicationInfo();
+        info.requiredDisplayCategory = "automotive";
+        final Task inTask = new TaskBuilder(mSupervisor).setActivityInfo(info).build();
+        inTask.inRecents = true;
+
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord target = new ActivityBuilder(mAtm).build();
+        startActivityInner(starter, target, null /* source */, null /* options */, inTask,
+                null /* inTaskFragment */);
+
+        assertNotEquals(inTask, target.getTask());
+    }
+
+    /**
+     * Tests a task without a specific display category exist in the system and launches activity
+     * with display category into the task within the inTask attribute. Make sure the activity is
+     * not placed on the task since the display category is different.
+     */
+    @Test
+    public void testLaunchDisplayCategoryActivityInTask() {
+        final Task inTask = new TaskBuilder(mSupervisor).build();
+        inTask.inRecents = true;
+
+        final ActivityStarter starter = prepareStarter(0, false);
+        final ActivityRecord target = new ActivityBuilder(mAtm).setRequiredDisplayCategory("auto")
+                .build();
+        startActivityInner(starter, target, null /* source */, null /* options */, inTask,
+                null /* inTaskFragment */);
+
+        assertNotEquals(inTask, target.getTask());
+    }
+
     private static void startActivityInner(ActivityStarter starter, ActivityRecord target,
             ActivityRecord source, ActivityOptions options, Task inTask,
             TaskFragment inTaskFragment) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 2fccd64..368b750 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -344,7 +344,7 @@
 
         // Assume the activity is finishing and hidden because it was crashed.
         activity.finishing = true;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setVisible(false);
         activity.getTask().setPausingActivity(activity);
         homeActivity.setState(PAUSED, "test");
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index fb94147c..c73237e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -122,7 +122,7 @@
         final ActivityRecord top = createActivityRecord(task);
         top.setState(ActivityRecord.State.RESUMED, "test");
         behind.setState(ActivityRecord.State.STARTED, "test");
-        behind.mVisibleRequested = true;
+        behind.setVisibleRequested(true);
 
         task.removeActivities("test", false /* excludingTaskOverlay */);
         assertFalse(mDisplayContent.mAppTransition.isReady());
@@ -294,7 +294,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -319,12 +319,12 @@
         //                   +- [Task2] - [ActivityRecord2] (opening, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.mRequestForceTransition = true;
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         activity2.mRequestForceTransition = true;
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -391,7 +391,7 @@
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
         attrs.setTitle("AppWindow2");
         final TestWindowState appWindow2 = createWindowState(attrs, activity2);
         appWindow2.mWillReplaceWindow = true;
@@ -424,17 +424,17 @@
         //                               +- [ActivityRecord4] (invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         final ActivityRecord activity4 = createActivityRecord(mDisplayContent,
                 activity3.getTask());
         activity4.setVisible(false);
-        activity4.mVisibleRequested = false;
+        activity4.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -459,7 +459,7 @@
         //                            +- [ActivityRecord2] (closing, visible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
 
@@ -490,7 +490,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
@@ -528,13 +528,13 @@
 
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setOccludesParent(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent,
                 activity1.getTask());
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ActivityRecord activity3 = createActivityRecord(mDisplayContent);
         activity3.setOccludesParent(false);
@@ -567,7 +567,7 @@
         final Task parentTask = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(parentTask);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(parentTask);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -600,10 +600,10 @@
         splitRoot1.setAdjacentTaskFragment(splitRoot2);
         final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecordWithParentTask(splitRoot2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -625,12 +625,12 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final TaskFragment taskFragment2 = createTaskFragmentWithActivity(parentTask);
         final ActivityRecord activity2 = taskFragment2.getTopMostActivity();
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -654,11 +654,11 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -683,11 +683,11 @@
         final TaskFragment taskFragment1 = createTaskFragmentWithActivity(task1);
         final ActivityRecord activity1 = taskFragment1.getTopMostActivity();
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
 
         final ActivityRecord activity2 = createActivityRecord(mDisplayContent);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity2);
@@ -710,13 +710,13 @@
         //                   +- [Task2] (embedded) - [ActivityRecord2] (opening, invisible)
         final ActivityRecord activity1 = createActivityRecord(mDisplayContent);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
 
         final Task task2 = createTask(mDisplayContent);
         task2.mRemoveWithTaskOrganizer = true;
         final ActivityRecord activity2 = createActivityRecord(task2);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
         opening.add(activity1);
@@ -744,7 +744,7 @@
 
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(false);
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         final ActivityRecord activity2 = createActivityRecord(task);
 
         final ArraySet<ActivityRecord> opening = new ArraySet<>();
@@ -1260,6 +1260,8 @@
     @Test
     public void testTransitionGoodToGoForTaskFragments_detachedApp() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer iOrganizer = getITaskFragmentOrganizer(organizer);
+        mAtm.mTaskFragmentOrganizerController.registerOrganizer(iOrganizer);
         final Task task = createTask(mDisplayContent);
         final TaskFragment changeTaskFragment =
                 createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 437eeb1..6b814e6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -385,11 +385,11 @@
         // Simulate activity1 launches activity2.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.allDrawn = true;
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.allDrawn = true;
 
         dc.mClosingApps.add(activity1);
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 3bce860..1b77c95 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -20,24 +20,23 @@
 import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
 import static android.window.BackNavigationInfo.typeToString;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 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.Mockito.doAnswer;
-import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.HardwareBuffer;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
@@ -48,7 +47,6 @@
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedCallbackInfo;
 import android.window.OnBackInvokedDispatcher;
-import android.window.TaskSnapshot;
 import android.window.WindowOnBackInvokedDispatcher;
 
 import com.android.server.LocalServices;
@@ -67,6 +65,7 @@
     private BackNavigationController mBackNavigationController;
     private WindowManagerInternal mWindowManagerInternal;
     private BackAnimationAdapter mBackAnimationAdapter;
+    private Task mRootHomeTask;
 
     @Before
     public void setUp() throws Exception {
@@ -76,6 +75,7 @@
         LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
         mBackNavigationController.setWindowManager(mWm);
         mBackAnimationAdapter = mock(BackAnimationAdapter.class);
+        mRootHomeTask = initHomeActivity();
     }
 
     @Test
@@ -101,7 +101,8 @@
         ActivityRecord recordA = createActivityRecord(taskA);
         Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
 
-        withSystemCallback(createTopTaskWithActivity());
+        final Task topTask = createTopTaskWithActivity();
+        withSystemCallback(topTask);
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
         assertThat(typeToString(backNavigationInfo.getType()))
@@ -111,6 +112,20 @@
         verify(mBackNavigationController).scheduleAnimationLocked(
                 eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
                 any());
+
+        // reset drawning status
+        topTask.forAllWindows(w -> {
+            makeWindowVisibleAndDrawn(w);
+        }, true);
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        doReturn(true).when(recordA).canShowWhenLocked();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
     }
 
     @Test
@@ -137,6 +152,20 @@
         assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+
+        // reset drawing status
+        testCase.recordFront.forAllWindows(w -> {
+            makeWindowVisibleAndDrawn(w);
+        }, true);
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        doReturn(true).when(testCase.recordBack).canShowWhenLocked();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
     }
 
     @Test
@@ -164,12 +193,17 @@
 
     @Test
     public void preparesForBackToHome() {
-        Task task = createTopTaskWithActivity();
-        withSystemCallback(task);
+        final Task topTask = createTopTaskWithActivity();
+        withSystemCallback(topTask);
 
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
+
+        setupKeyguardOccluded();
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
     }
 
     @Test
@@ -302,14 +336,22 @@
         };
     }
 
-    @NonNull
-    private TaskSnapshotController createMockTaskSnapshotController() {
-        TaskSnapshotController taskSnapshotController = mock(TaskSnapshotController.class);
-        TaskSnapshot taskSnapshot = mock(TaskSnapshot.class);
-        when(taskSnapshot.getHardwareBuffer()).thenReturn(mock(HardwareBuffer.class));
-        when(taskSnapshotController.getSnapshot(anyInt(), anyInt(), anyBoolean(), anyBoolean()))
-                .thenReturn(taskSnapshot);
-        return taskSnapshotController;
+    private Task initHomeActivity() {
+        final Task task = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask();
+        task.forAllLeafTasks((t) -> {
+            if (t.getTopMostActivity() == null) {
+                final ActivityRecord r = createActivityRecord(t);
+                Mockito.doNothing().when(t).reparentSurfaceControl(any(), any());
+                Mockito.doNothing().when(r).reparentSurfaceControl(any(), any());
+            }
+        }, true);
+        return task;
+    }
+
+    private void setupKeyguardOccluded() {
+        final KeyguardController kc = mRootHomeTask.mTaskSupervisor.getKeyguardController();
+        doReturn(true).when(kc).isKeyguardLocked(anyInt());
+        doReturn(true).when(kc).isDisplayOccluded(anyInt());
     }
 
     @NonNull
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 d151188..bc23fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -590,7 +590,7 @@
         assertEquals(window1, mWm.mRoot.getTopFocusedDisplayContent().mCurrentFocus);
 
         // Make sure top focused display not changed if there is a focused app.
-        window1.mActivityRecord.mVisibleRequested = false;
+        window1.mActivityRecord.setVisibleRequested(false);
         window1.getDisplayContent().setFocusedApp(window1.mActivityRecord);
         updateFocusedWindow();
         assertTrue(!window1.isFocused());
@@ -1106,7 +1106,7 @@
     public void testOrientationBehind() {
         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
-        prev.mVisibleRequested = false;
+        prev.setVisibleRequested(false);
         final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
                 .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
         assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
index db1d15a..ba68a25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayWindowPolicyControllerTests.java
@@ -243,7 +243,7 @@
         }
 
         @Override
-        public boolean canShowTasksInRecents() {
+        public boolean canShowTasksInHostDeviceRecents() {
             return true;
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index ac2df62..6f2e3f2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -819,7 +819,7 @@
     @Test
     public void testVisibleTask_displayCanNotShowTaskFromRecents_expectNotVisible() {
         final DisplayContent displayContent = addNewDisplayContentAt(DisplayContent.POSITION_TOP);
-        doReturn(false).when(displayContent).canShowTasksInRecents();
+        doReturn(false).when(displayContent).canShowTasksInHostDeviceRecents();
         final Task task = displayContent.getDefaultTaskDisplayArea().createRootTask(
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */);
         mRecentTasks.add(task);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
index 88e58ea..08635ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java
@@ -172,12 +172,12 @@
         // executed.
         final ActivityRecord activity1 = createActivityRecord(task);
         activity1.setVisible(true);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.addWindow(createWindowState(new LayoutParams(TYPE_BASE_APPLICATION), activity1));
 
         final ActivityRecord activity2 = createActivityRecord(task);
         activity2.setVisible(false);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
 
         mDefaultDisplay.getConfiguration().windowConfiguration.setRotation(
                 mDefaultDisplay.getRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index a2b4cb8..de3a526 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -111,7 +111,7 @@
         RecentsAnimationCallbacks recentsAnimation = startRecentsActivity(
                 mRecentsComponent, true /* getRecentsAnimation */);
         // The launch-behind state should make the recents activity visible.
-        assertTrue(recentActivity.mVisibleRequested);
+        assertTrue(recentActivity.isVisibleRequested());
         assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
                 mAtm.mDemoteTopAppReasons);
         assertFalse(mAtm.mInternal.useTopSchedGroupForTopProcess());
@@ -119,7 +119,7 @@
         // Simulate the animation is cancelled without changing the stack order.
         recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
         // The non-top recents activity should be invisible by the restored launch-behind state.
-        assertFalse(recentActivity.mVisibleRequested);
+        assertFalse(recentActivity.isVisibleRequested());
         assertEquals(0, mAtm.mDemoteTopAppReasons);
     }
 
@@ -164,7 +164,7 @@
         // The activity is started in background so it should be invisible and will be stopped.
         assertThat(recentsActivity).isNotNull();
         assertThat(mSupervisor.mStoppingActivities).contains(recentsActivity);
-        assertFalse(recentsActivity.mVisibleRequested);
+        assertFalse(recentsActivity.isVisibleRequested());
 
         // Assume it is stopped to test next use case.
         recentsActivity.activityStopped(null /* newIcicle */, null /* newPersistentState */,
@@ -360,7 +360,7 @@
                 true);
 
         // Ensure we find the task for the right user and it is made visible
-        assertTrue(otherUserHomeActivity.mVisibleRequested);
+        assertTrue(otherUserHomeActivity.isVisibleRequested());
     }
 
     private void startRecentsActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
index 881cc5f..fb29d3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootTaskTests.java
@@ -1068,7 +1068,7 @@
         activity.app = null;
         overlayActivity.app = null;
         // Simulate the process is dead
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(DESTROYED, "Test");
 
         assertEquals(2, task.getChildCount());
@@ -1205,7 +1205,7 @@
 
         // There is still an activity1 in rootTask1 so the activity2 should be added to finishing
         // list that will be destroyed until idle.
-        rootTask2.getTopNonFinishingActivity().mVisibleRequested = true;
+        rootTask2.getTopNonFinishingActivity().setVisibleRequested(true);
         final ActivityRecord activity2 = finishTopActivity(rootTask2);
         assertEquals(STOPPING, activity2.getState());
         assertThat(mSupervisor.mStoppingActivities).contains(activity2);
@@ -1410,7 +1410,7 @@
         new ActivityBuilder(mAtm).setTask(task).build();
         // The scenario we are testing is when the app isn't visible yet.
         nonTopVisibleActivity.setVisible(false);
-        nonTopVisibleActivity.mVisibleRequested = false;
+        nonTopVisibleActivity.setVisibleRequested(false);
         doReturn(false).when(nonTopVisibleActivity).attachedToProcess();
         doReturn(true).when(nonTopVisibleActivity).shouldBeVisibleUnchecked();
         doNothing().when(mSupervisor).startSpecificActivity(any(), anyBoolean(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index f84865b..a17e124 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -174,7 +174,7 @@
         final Task rootTask = new TaskBuilder(mSupervisor).build();
         final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
         final ActivityRecord activity1 = new ActivityBuilder(mAtm).setTask(task1).build();
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         assertEquals(1, task1.mLayerRank);
@@ -183,7 +183,7 @@
 
         final Task task2 = new TaskBuilder(mSupervisor).build();
         final ActivityRecord activity2 = new ActivityBuilder(mAtm).setTask(task2).build();
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         mWm.mRoot.rankTaskLayers();
 
         // Note that ensureActivitiesVisible is disabled in SystemServicesTestRule, so both the
@@ -200,8 +200,8 @@
         assertEquals(2, task2.mLayerRank);
 
         // The rank should be updated to invisible when device went to sleep.
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(false);
         doReturn(true).when(mAtm).isSleepingOrShuttingDownLocked();
         doReturn(true).when(mRootWindowContainer).putTasksToSleep(anyBoolean(), anyBoolean());
         mSupervisor.mGoingToSleepWakeLock = mock(PowerManager.WakeLock.class);
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 e65610f..13ea99a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -169,7 +169,7 @@
     public void testRestartProcessIfVisible() {
         setUpDisplaySizeWithApp(1000, 2500);
         doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.setSavedState(null /* savedState */);
         mActivity.setState(RESUMED, "testRestart");
         prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
@@ -553,7 +553,7 @@
         resizeDisplay(display, 900, 1800);
 
         mActivity.setState(STOPPED, "testSizeCompatMode");
-        mActivity.mVisibleRequested = false;
+        mActivity.setVisibleRequested(false);
         mActivity.visibleIgnoringKeyguard = false;
         mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
         mActivity.app.computeProcessActivityState();
@@ -605,7 +605,7 @@
         // Make the activity resizable again by restarting it
         clearInvocations(mTask);
         mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
-        mActivity.mVisibleRequested = true;
+        mActivity.setVisibleRequested(true);
         mActivity.restartProcessIfVisible();
         // The full lifecycle isn't hooked up so manually set state to resumed
         mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
@@ -3188,7 +3188,7 @@
             task.mResizeMode = activity.info.resizeMode;
             task.getRootActivity().info.resizeMode = activity.info.resizeMode;
         }
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         if (maxAspect >= 0) {
             activity.info.setMaxAspectRatio(maxAspect);
         }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index d5fb1a8..91f8d8d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -398,7 +398,7 @@
                     .setParentTask(rootHomeTask).setCreateTask(true).build();
         }
         homeActivity.setVisible(false);
-        homeActivity.mVisibleRequested = true;
+        homeActivity.setVisibleRequested(true);
         assertFalse(rootHomeTask.isVisible());
 
         assertEquals(defaultTaskDisplayArea.getOrientation(), rootHomeTask.getOrientation());
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 2b49314..c333c93 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -358,7 +358,6 @@
         assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
 
         // Notify organizer if there is any embedded in the Task.
-        clearInvocations(mOrganizer);
         final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
                 .setParentTask(task)
                 .setOrganizer(mOrganizer)
@@ -367,10 +366,14 @@
                 DEFAULT_TASK_FRAGMENT_ORGANIZER_PROCESS_NAME);
         activity.reparent(taskFragment, POSITION_TOP);
         activity.mLastTaskFragmentOrganizerBeforePip = null;
+
+        // Clear invocations now because there will be another transaction for the TaskFragment
+        // change above, triggered by the reparent. We only want to test onActivityReparentedToTask
+        // here.
+        clearInvocations(mOrganizer);
         mController.onActivityReparentedToTask(activity);
         mController.dispatchPendingEvents();
 
-        assertTaskFragmentParentInfoChangedTransaction(task);
         assertActivityReparentedToTaskTransaction(task.mTaskId, activity.intent, activity.token);
     }
 
@@ -552,10 +555,9 @@
     @Test
     public void testApplyTransaction_enforceHierarchyChange_createTaskFragment() {
         final ActivityRecord ownerActivity = createActivityRecord(mDisplayContent);
-        final IBinder fragmentToken = new Binder();
 
         // Allow organizer to create TaskFragment and start/reparent activity to TaskFragment.
-        createTaskFragmentFromOrganizer(mTransaction, ownerActivity, fragmentToken);
+        createTaskFragmentFromOrganizer(mTransaction, ownerActivity, mFragmentToken);
         mTransaction.startActivityInTaskFragment(
                 mFragmentToken, null /* callerToken */, new Intent(), null /* activityOptions */);
         mTransaction.reparentActivityToTaskFragment(mFragmentToken, mock(IBinder.class));
@@ -564,7 +566,8 @@
         assertApplyTransactionAllowed(mTransaction);
 
         // Successfully created a TaskFragment.
-        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(fragmentToken);
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+                mFragmentToken);
         assertNotNull(taskFragment);
         assertEquals(ownerActivity.getTask(), taskFragment.getTask());
     }
@@ -703,6 +706,40 @@
     }
 
     @Test
+    public void testApplyTransaction_createTaskFragment_withPairedPrimaryFragmentToken() {
+        final Task task = createTask(mDisplayContent);
+        mTaskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(mFragmentToken)
+                .createActivityCount(1)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
+        final ActivityRecord activityOnTop = createActivityRecord(task);
+        final int uid = Binder.getCallingUid();
+        activityOnTop.info.applicationInfo.uid = uid;
+        activityOnTop.getTask().effectiveUid = uid;
+        final IBinder fragmentToken1 = new Binder();
+        final TaskFragmentCreationParams params = new TaskFragmentCreationParams.Builder(
+                mOrganizerToken, fragmentToken1, activityOnTop.token)
+                .setPairedPrimaryFragmentToken(mFragmentToken)
+                .build();
+        mTransaction.setTaskFragmentOrganizer(mIOrganizer);
+        mTransaction.createTaskFragment(params);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Successfully created a TaskFragment.
+        final TaskFragment taskFragment = mWindowOrganizerController.getTaskFragment(
+                fragmentToken1);
+        assertNotNull(taskFragment);
+        // The new TaskFragment should be positioned right above the paired TaskFragment.
+        assertEquals(task.mChildren.indexOf(mTaskFragment) + 1,
+                task.mChildren.indexOf(taskFragment));
+        // The top activity should remain on top.
+        assertEquals(task.mChildren.indexOf(taskFragment) + 1,
+                task.mChildren.indexOf(activityOnTop));
+    }
+
+    @Test
     public void testApplyTransaction_enforceHierarchyChange_reparentChildren() {
         doReturn(true).when(mTaskFragment).isAttached();
 
@@ -1159,6 +1196,7 @@
         doReturn(false).when(task).shouldBeVisible(any());
 
         // Dispatch the initial event in the Task to update the Task visibility to the organizer.
+        clearInvocations(mOrganizer);
         mController.onTaskFragmentAppeared(mIOrganizer, taskFragment);
         mController.dispatchPendingEvents();
         verify(mOrganizer).onTransactionReady(any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 8fda191..8ac7ceb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -107,18 +107,49 @@
     }
 
     @Test
+    public void testShouldStartChangeTransition_relativePositionChange() {
+        mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
+        final Rect startBounds = new Rect(0, 0, 500, 1000);
+        final Rect endBounds = new Rect(500, 0, 1000, 1000);
+        mTaskFragment.setBounds(startBounds);
+        mTaskFragment.updateRelativeEmbeddedBounds();
+        doReturn(true).when(mTaskFragment).isVisible();
+        doReturn(true).when(mTaskFragment).isVisibleRequested();
+
+        // Do not resize, just change the relative position.
+        final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
+        mTaskFragment.setBounds(endBounds);
+        mTaskFragment.updateRelativeEmbeddedBounds();
+        spyOn(mDisplayContent.mTransitionController);
+
+        // For Shell transition, we don't want to take snapshot when the bounds are not resized
+        doReturn(true).when(mDisplayContent.mTransitionController)
+                .isShellTransitionsEnabled();
+        assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
+
+        // For legacy transition, we want to request a change transition even if it is just relative
+        // bounds change.
+        doReturn(false).when(mDisplayContent.mTransitionController)
+                .isShellTransitionsEnabled();
+        assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
+    }
+
+    @Test
     public void testStartChangeTransition_resetSurface() {
         mockSurfaceFreezerSnapshot(mTaskFragment.mSurfaceFreezer);
         final Rect startBounds = new Rect(0, 0, 1000, 1000);
         final Rect endBounds = new Rect(500, 500, 1000, 1000);
         mTaskFragment.setBounds(startBounds);
+        mTaskFragment.updateRelativeEmbeddedBounds();
         doReturn(true).when(mTaskFragment).isVisible();
         doReturn(true).when(mTaskFragment).isVisibleRequested();
 
         clearInvocations(mTransaction);
+        final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
         mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
         mTaskFragment.setBounds(endBounds);
-        assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
+        mTaskFragment.updateRelativeEmbeddedBounds();
+        assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
         mTaskFragment.initializeChangeTransition(startBounds);
         mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
 
@@ -157,17 +188,20 @@
         final Rect startBounds = new Rect(0, 0, 1000, 1000);
         final Rect endBounds = new Rect(500, 500, 1000, 1000);
         mTaskFragment.setBounds(startBounds);
+        mTaskFragment.updateRelativeEmbeddedBounds();
         doReturn(true).when(mTaskFragment).isVisible();
         doReturn(true).when(mTaskFragment).isVisibleRequested();
 
+        final Rect relStartBounds = new Rect(mTaskFragment.getRelativeEmbeddedBounds());
         final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
         displayPolicy.screenTurnedOff();
 
         assertFalse(mTaskFragment.okToAnimate());
 
         mTaskFragment.setBounds(endBounds);
+        mTaskFragment.updateRelativeEmbeddedBounds();
 
-        assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
+        assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds, relStartBounds));
     }
 
     /**
@@ -540,8 +574,8 @@
         tf1.setBounds(600, 0, 1200, 1000);
         final ActivityRecord activity0 = tf0.getTopMostActivity();
         final ActivityRecord activity1 = tf1.getTopMostActivity();
-        doReturn(true).when(activity0).isVisibleRequested();
-        doReturn(true).when(activity1).isVisibleRequested();
+        activity0.setVisibleRequested(true);
+        activity1.setVisibleRequested(true);
 
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf0.getOrientation(SCREEN_ORIENTATION_UNSET));
         assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, tf1.getOrientation(SCREEN_ORIENTATION_UNSET));
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 74d7884..19e3246 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -1493,7 +1493,6 @@
     public void testReorderActivityToFront() {
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
         final Task task =  new TaskBuilder(mSupervisor).setCreateActivity(true).build();
-        doNothing().when(task).onActivityVisibleRequestedChanged();
         final ActivityRecord activity = task.getTopMostActivity();
 
         final TaskFragment fragment = createTaskFragmentWithEmbeddedActivity(task, organizer);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 59a31b1..aaf07b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -135,8 +135,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -199,9 +199,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
-        opening2.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
+        opening2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -250,8 +250,8 @@
         fillChangeMap(changes, tda);
 
         // End states.
-        showing.mVisibleRequested = true;
-        showing2.mVisibleRequested = true;
+        showing.setVisibleRequested(true);
+        showing2.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -286,16 +286,16 @@
 
         final Task openTask = createTask(mDisplayContent);
         final ActivityRecord opening = createActivityRecord(openTask);
-        opening.mVisibleRequested = false; // starts invisible
+        opening.setVisibleRequested(false); // starts invisible
         final Task closeTask = createTask(mDisplayContent);
         final ActivityRecord closing = createActivityRecord(closeTask);
-        closing.mVisibleRequested = true; // starts visible
+        closing.setVisibleRequested(true); // starts visible
 
         transition.collectExistenceChange(openTask);
         transition.collect(opening);
         transition.collect(closing);
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
                 transition.mParticipants, transition.mChanges);
@@ -323,7 +323,7 @@
                     WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
         }
 
         // doesn't matter which order collected since participants is a set
@@ -331,7 +331,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -360,7 +360,7 @@
                     WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
             final ActivityRecord act = createActivityRecord(tasks[i]);
             // alternate so that the transition doesn't get promoted to the display area
-            act.mVisibleRequested = (i % 2) == 0; // starts invisible
+            act.setVisibleRequested((i % 2) == 0); // starts invisible
             act.visibleIgnoringKeyguard = (i % 2) == 0;
             if (i == showWallpaperTask) {
                 doReturn(true).when(act).showWallpaper();
@@ -381,7 +381,7 @@
             transition.collectExistenceChange(tasks[i]);
             final ActivityRecord act = tasks[i].getTopMostActivity();
             transition.collect(act);
-            tasks[i].getTopMostActivity().mVisibleRequested = (i % 2) != 0;
+            tasks[i].getTopMostActivity().setVisibleRequested((i % 2) != 0);
         }
 
         ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -417,9 +417,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, true /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
-        hiding.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
+        hiding.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(hiding);
@@ -449,9 +449,9 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, topTask);
         // End states.
-        showing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
-        closing.mVisibleRequested = false;
+        showing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
+        closing.setVisibleRequested(false);
 
         participants.add(belowTask);
         participants.add(showing);
@@ -531,19 +531,19 @@
     @Test
     public void testOpenActivityInTheSameTaskWithDisplayChange() {
         final ActivityRecord closing = createActivityRecord(mDisplayContent);
-        closing.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
         final Task task = closing.getTask();
         makeTaskOrganized(task);
         final ActivityRecord opening = createActivityRecord(task);
-        opening.mVisibleRequested = false;
+        opening.setVisibleRequested(false);
         makeDisplayAreaOrganized(mDisplayContent.getDefaultTaskDisplayArea(), mDisplayContent);
         final WindowContainer<?>[] wcs = { closing, opening, task, mDisplayContent };
         final Transition transition = createTestTransition(TRANSIT_OPEN);
         for (WindowContainer<?> wc : wcs) {
             transition.collect(wc);
         }
-        closing.mVisibleRequested = false;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(false);
+        opening.setVisibleRequested(true);
         final int newRotation = mDisplayContent.getWindowConfiguration().getRotation() + 1;
         for (WindowContainer<?> wc : wcs) {
             wc.getWindowConfiguration().setRotation(newRotation);
@@ -586,9 +586,9 @@
         changes.put(changeInChange, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, openTask);
         // End states.
-        changeInChange.mVisibleRequested = true;
-        openInOpen.mVisibleRequested = true;
-        openInChange.mVisibleRequested = true;
+        changeInChange.setVisibleRequested(true);
+        openInOpen.setVisibleRequested(true);
+        openInChange.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -644,8 +644,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -685,8 +685,8 @@
         changes.put(closing, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         fillChangeMap(changes, newTask);
         // End states.
-        closing.mVisibleRequested = true;
-        opening.mVisibleRequested = true;
+        closing.setVisibleRequested(true);
+        opening.setVisibleRequested(true);
 
         final int transit = transition.mType;
         int flags = 0;
@@ -962,7 +962,7 @@
         home.mTransitionController.requestStartTransition(transition, home.getTask(),
                 null /* remoteTransition */, null /* displayChange */);
         transition.collectExistenceChange(home);
-        home.mVisibleRequested = true;
+        home.setVisibleRequested(true);
         mDisplayContent.setFixedRotationLaunchingAppUnchecked(home);
         doReturn(true).when(home).hasFixedRotationTransform(any());
         player.startTransition();
@@ -998,12 +998,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task1);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1011,9 +1011,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1029,8 +1029,8 @@
         closeTransition.collectExistenceChange(task2);
         closeTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
 
         openTransition.finishTransition();
 
@@ -1072,12 +1072,12 @@
         // Start out with task2 visible and set up a transition that closes task2 and opens task1
         final Task task1 = createTask(mDisplayContent);
         final ActivityRecord activity1 = createActivityRecord(task1);
-        activity1.mVisibleRequested = false;
+        activity1.setVisibleRequested(false);
         activity1.setVisible(false);
         final Task task2 = createTask(mDisplayContent);
         makeTaskOrganized(task1, task2);
         final ActivityRecord activity2 = createActivityRecord(task2);
-        activity2.mVisibleRequested = true;
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         openTransition.collectExistenceChange(task1);
@@ -1085,9 +1085,9 @@
         openTransition.collectExistenceChange(task2);
         openTransition.collectExistenceChange(activity2);
 
-        activity1.mVisibleRequested = true;
+        activity1.setVisibleRequested(true);
         activity1.setVisible(true);
-        activity2.mVisibleRequested = false;
+        activity2.setVisibleRequested(false);
 
         // Using abort to force-finish the sync (since we can't wait for drawing in unit test).
         // We didn't call abort on the transition itself, so it will still run onTransactionReady
@@ -1107,8 +1107,8 @@
         closeTransition.collectExistenceChange(activity2);
         closeTransition.setTransientLaunch(activity2, null /* restoreBelow */);
 
-        activity1.mVisibleRequested = false;
-        activity2.mVisibleRequested = true;
+        activity1.setVisibleRequested(false);
+        activity2.setVisibleRequested(true);
         activity2.setVisible(true);
 
         // Using abort to force-finish the sync (since we obviously can't wait for drawing).
@@ -1166,8 +1166,8 @@
         changes.put(activity0, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
         changes.put(activity1, new Transition.ChangeInfo(false /* vis */, false /* exChg */));
         // End states.
-        activity0.mVisibleRequested = false;
-        activity1.mVisibleRequested = true;
+        activity0.setVisibleRequested(false);
+        activity1.setVisibleRequested(true);
 
         participants.add(activity0);
         participants.add(activity1);
@@ -1210,9 +1210,9 @@
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(true /* vis */,
                 false /* exChg */));
         // End states.
-        closingActivity.mVisibleRequested = false;
-        openingActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        closingActivity.setVisibleRequested(false);
+        openingActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
 
         participants.add(closingActivity);
         participants.add(openingActivity);
@@ -1255,8 +1255,8 @@
                 false /* exChg */));
         changes.put(embeddedTf, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
         // End states.
-        nonEmbeddedActivity.mVisibleRequested = false;
-        embeddedActivity.mVisibleRequested = true;
+        nonEmbeddedActivity.setVisibleRequested(false);
+        embeddedActivity.setVisibleRequested(true);
         embeddedTf.setBounds(new Rect(0, 0, 500, 500));
 
         participants.add(nonEmbeddedActivity);
@@ -1285,11 +1285,11 @@
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: reset bounds to fill Task.
         activity.getConfiguration().windowConfiguration.setBounds(taskBounds);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1313,11 +1313,11 @@
         task.getConfiguration().windowConfiguration.setBounds(taskBounds);
         final ActivityRecord activity = createActivityRecord(task);
         // Start states: fills Task without override.
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         changes.put(activity, new Transition.ChangeInfo(activity));
         // End states: set bounds to make sure the start bounds is ignored if it is not visible.
         activity.getConfiguration().windowConfiguration.setBounds(new Rect(0, 0, 250, 500));
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1340,12 +1340,12 @@
         final Task lastParent = createTask(mDisplayContent);
         final Task newParent = createTask(mDisplayContent);
         final ActivityRecord activity = createActivityRecord(lastParent);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         changes.put(activity, new Transition.ChangeInfo(activity));
         activity.reparent(newParent, POSITION_TOP);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
 
         participants.add(activity);
         final ArrayList<WindowContainer> targets = Transition.calculateTargets(
@@ -1365,7 +1365,7 @@
         final Task task = createTask(mDisplayContent);
         task.setBounds(new Rect(0, 0, 2000, 1000));
         final ActivityRecord activity = createActivityRecord(task);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
         final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
@@ -1413,13 +1413,13 @@
         task.setTaskDescription(taskDescription);
 
         // Start states:
-        embeddedActivity.mVisibleRequested = true;
-        nonEmbeddedActivity.mVisibleRequested = false;
+        embeddedActivity.setVisibleRequested(true);
+        nonEmbeddedActivity.setVisibleRequested(false);
         changes.put(embeddedTf, new Transition.ChangeInfo(embeddedTf));
         changes.put(nonEmbeddedActivity, new Transition.ChangeInfo(nonEmbeddedActivity));
         // End states:
-        embeddedActivity.mVisibleRequested = false;
-        nonEmbeddedActivity.mVisibleRequested = true;
+        embeddedActivity.setVisibleRequested(false);
+        nonEmbeddedActivity.setVisibleRequested(true);
 
         participants.add(embeddedTf);
         participants.add(nonEmbeddedActivity);
@@ -1532,7 +1532,7 @@
         final ActivityRecord activity = createActivityRecord(lastParent);
         doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
         doNothing().when(activity).setDropInputMode(anyInt());
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
 
         final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
                 activity.mTransitionController, mWm.mSyncEngine);
diff --git a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
index 45e1141..2fccb88a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/UnknownAppVisibilityControllerTest.java
@@ -95,7 +95,7 @@
         final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent);
         mDisplayContent.mUnknownAppVisibilityController.notifyLaunched(activity);
         activity.finishing = true;
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setVisibility(false, false);
         assertTrue(mDisplayContent.mUnknownAppVisibilityController.allResolved());
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 94b5b93..a100b9a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -313,12 +313,12 @@
         r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
                 mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
         // Invisible requested activity should not share its rotation transform.
-        r.mVisibleRequested = false;
+        r.setVisibleRequested(false);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertFalse(wallpaperToken.hasFixedRotationTransform());
 
         // Wallpaper should link the transform of its target.
-        r.mVisibleRequested = true;
+        r.setVisibleRequested(true);
         mDisplayContent.mWallpaperController.adjustWallpaperWindows();
         assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
         assertTrue(r.hasFixedRotationTransform());
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 871030f..3d777f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -205,7 +205,7 @@
         win.mViewVisibility = View.VISIBLE;
         win.mHasSurface = true;
         win.mActivityRecord.mAppStopped = true;
-        win.mActivityRecord.mVisibleRequested = false;
+        win.mActivityRecord.setVisibleRequested(false);
         win.mActivityRecord.setVisible(false);
         mWm.mWindowMap.put(win.mClient.asBinder(), win);
         final int w = 100;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index c73e237..e5e9f54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -992,7 +992,7 @@
         final Task task = createTask(rootTaskController1);
         final WindowState w = createAppWindow(task, TYPE_APPLICATION, "Enlightened Window");
 
-        w.mActivityRecord.mVisibleRequested = true;
+        w.mActivityRecord.setVisibleRequested(true);
         w.mActivityRecord.setVisible(true);
 
         BLASTSyncEngine bse = new BLASTSyncEngine(mWm);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 3abf7ce..8bd4148 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -324,7 +324,7 @@
     @Test
     public void testComputeOomAdjFromActivities() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         final int[] callbackResult = { 0 };
         final int visible = 1;
         final int paused = 2;
@@ -359,7 +359,7 @@
         assertEquals(visible, callbackResult[0]);
 
         callbackResult[0] = 0;
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(PAUSED, "test");
         mWpc.computeOomAdjFromActivities(callback);
         assertEquals(paused, callbackResult[0]);
@@ -380,7 +380,7 @@
         final VisibleActivityProcessTracker tracker = mAtm.mVisibleActivityProcessTracker;
         spyOn(tracker);
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         activity.setState(STARTED, "test");
 
         verify(tracker).onAnyActivityVisible(mWpc);
@@ -398,7 +398,7 @@
         assertTrue(mWpc.hasForegroundActivities());
 
         activity.setVisibility(false);
-        activity.mVisibleRequested = false;
+        activity.setVisibleRequested(false);
         activity.setState(STOPPED, "test");
 
         verify(tracker).onAllActivitiesInvisible(mWpc);
@@ -413,7 +413,7 @@
     @Test
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
@@ -423,7 +423,7 @@
     @Test
     public void testTopActivityUiModeChangeForDifferentPackage_noScheduledConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
                 Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
         verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
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 1b79dd3..69e3244 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -264,7 +264,7 @@
 
         // Verify that app window can still be IME target as long as it is visible (even if
         // it is going to become invisible).
-        appWindow.mActivityRecord.mVisibleRequested = false;
+        appWindow.mActivityRecord.setVisibleRequested(false);
         assertTrue(appWindow.canBeImeTarget());
 
         // Make windows invisible
@@ -413,6 +413,16 @@
     }
 
     @Test
+    public void testCanAffectSystemUiFlags_starting() {
+        final WindowState app = createWindow(null, TYPE_APPLICATION_STARTING, "app");
+        app.mActivityRecord.setVisible(true);
+        app.mStartingData = new SnapshotStartingData(mWm, null, 0);
+        assertFalse(app.canAffectSystemUiFlags());
+        app.mStartingData = new SplashScreenStartingData(mWm, 0, 0);
+        assertTrue(app.canAffectSystemUiFlags());
+    }
+
+    @Test
     public void testCanAffectSystemUiFlags_disallow() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         app.mActivityRecord.setVisible(true);
@@ -720,7 +730,7 @@
 
         // No need to wait for a window of invisible activity even if the window has surface.
         final WindowState invisibleApp = mAppWindow;
-        invisibleApp.mActivityRecord.mVisibleRequested = false;
+        invisibleApp.mActivityRecord.setVisibleRequested(false);
         invisibleApp.mActivityRecord.allDrawn = false;
         outWaitingForDrawn.clear();
         invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
@@ -738,7 +748,7 @@
         assertFalse(startingApp.getOrientationChanging());
 
         // Even if the display is frozen, invisible requested window should not be affected.
-        startingApp.mActivityRecord.mVisibleRequested = false;
+        startingApp.mActivityRecord.setVisibleRequested(false);
         mWm.startFreezingDisplay(0, 0, mDisplayContent);
         doReturn(true).when(mWm.mPolicy).isScreenOn();
         startingApp.getWindowFrames().setInsetsChanged(true);
@@ -813,7 +823,7 @@
         final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
                 "App window");
         doReturn(true).when(embeddedActivity).isVisible();
-        embeddedActivity.mVisibleRequested = true;
+        embeddedActivity.setVisibleRequested(true);
         makeWindowVisible(win);
         win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
         // Set the bounds twice:
@@ -838,7 +848,7 @@
     @Test
     public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
         final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");
-        win0.mActivityRecord.mVisibleRequested = false;
+        win0.mActivityRecord.setVisibleRequested(false);
         assertFalse(win0.canReceiveTouchInput());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 5a261bc65..4d31414 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -738,7 +738,7 @@
         activity.onDisplayChanged(dc);
         activity.setOccludesParent(true);
         activity.setVisible(true);
-        activity.mVisibleRequested = true;
+        activity.setVisibleRequested(true);
     }
 
     /**
@@ -1017,6 +1017,7 @@
         private ActivityInfo.WindowLayout mWindowLayout;
         private boolean mVisible = true;
         private ActivityOptions mLaunchIntoPipOpts;
+        private String mRequiredDisplayCategory;
 
         ActivityBuilder(ActivityTaskManagerService service) {
             mService = service;
@@ -1157,6 +1158,11 @@
             return this;
         }
 
+        ActivityBuilder setRequiredDisplayCategory(String requiredDisplayCategory) {
+            mRequiredDisplayCategory = requiredDisplayCategory;
+            return this;
+        }
+
         ActivityRecord build() {
             SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
             try {
@@ -1201,6 +1207,9 @@
             aInfo.configChanges |= mConfigChanges;
             aInfo.taskAffinity = mAffinity;
             aInfo.windowLayout = mWindowLayout;
+            if (mRequiredDisplayCategory != null) {
+                aInfo.requiredDisplayCategory = mRequiredDisplayCategory;
+            }
 
             if (mCreateTask) {
                 mTask = new TaskBuilder(mService.mTaskSupervisor)
@@ -1240,7 +1249,7 @@
                     mTask.moveToFront("createActivity");
                 }
                 if (mVisible) {
-                    activity.mVisibleRequested = true;
+                    activity.setVisibleRequested(true);
                     activity.setVisible(true);
                 }
             }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 4fd2b78..b3a1f2b 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1864,6 +1864,21 @@
                         mResponseStatsTracker.dump(idpw);
                     }
                     return;
+                } else if ("app-component-usage".equals(arg)) {
+                    final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+                    synchronized (mLock) {
+                        if (!mLastTimeComponentUsedGlobal.isEmpty()) {
+                            ipw.println("App Component Usages:");
+                            ipw.increaseIndent();
+                            for (String pkg : mLastTimeComponentUsedGlobal.keySet()) {
+                                ipw.println("package=" + pkg
+                                            + " lastUsed=" + UserUsageStatsService.formatDateTime(
+                                                    mLastTimeComponentUsedGlobal.get(pkg), true));
+                            }
+                            ipw.decreaseIndent();
+                        }
+                    }
+                    return;
                 } else if (arg != null && !arg.startsWith("-")) {
                     // Anything else that doesn't start with '-' is a pkg to filter
                     pkgs.add(arg);
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
new file mode 100644
index 0000000..2812264
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DspTrustedHotwordDetectorSession.java
@@ -0,0 +1,254 @@
+/*
+ * 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.voiceinteraction;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.permission.Identity;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.voice.AlwaysOnHotwordDetector;
+import android.service.voice.HotwordDetectedResult;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetector;
+import android.service.voice.HotwordRejectedResult;
+import android.service.voice.IDspHotwordDetectionCallback;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Locale;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that provides Dsp trusted hotword detector to communicate with the {@link
+ * HotwordDetectionService}.
+ *
+ * This class can handle the hotword detection which detector is created by using
+ * {@link android.service.voice.VoiceInteractionService#createAlwaysOnHotwordDetector(String,
+ * Locale, PersistableBundle, SharedMemory, AlwaysOnHotwordDetector.Callback)}.
+ */
+final class DspTrustedHotwordDetectorSession extends HotwordDetectorSession {
+    private static final String TAG = "DspTrustedHotwordDetectorSession";
+
+    // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
+    private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
+    // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
+    private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
+
+    @GuardedBy("mLock")
+    private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
+
+    @GuardedBy("mLock")
+    private boolean mValidatingDspTrigger = false;
+
+    DspTrustedHotwordDetectorSession(
+            @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
+            @NonNull Object lock, @NonNull Context context,
+            @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
+            Identity voiceInteractorIdentity,
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+        super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
+                voiceInteractorIdentity, scheduledExecutorService, logging);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void detectFromDspSourceLocked(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+            IHotwordRecognitionStatusCallback externalCallback) {
+        if (DEBUG) {
+            Slog.d(TAG, "detectFromDspSourceLocked");
+        }
+
+        AtomicBoolean timeoutDetected = new AtomicBoolean(false);
+        // TODO: consider making this a non-anonymous class.
+        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
+            @Override
+            public void onDetected(HotwordDetectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onDetected");
+                }
+                synchronized (mLock) {
+                    if (mCancellationKeyPhraseDetectionFuture != null) {
+                        mCancellationKeyPhraseDetectionFuture.cancel(true);
+                    }
+                    if (timeoutDetected.get()) {
+                        return;
+                    }
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                    if (!mValidatingDspTrigger) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a process restart");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mValidatingDspTrigger = false;
+                    try {
+                        enforcePermissionsForDataDelivery();
+                        enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
+                    } catch (SecurityException e) {
+                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
+                        return;
+                    }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
+                        return;
+                    }
+                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
+                    }
+                }
+            }
+
+            @Override
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onRejected");
+                }
+                synchronized (mLock) {
+                    if (mCancellationKeyPhraseDetectionFuture != null) {
+                        mCancellationKeyPhraseDetectionFuture.cancel(true);
+                    }
+                    if (timeoutDetected.get()) {
+                        return;
+                    }
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                    if (!mValidatingDspTrigger) {
+                        Slog.i(TAG, "Ignoring #onRejected due to a process restart");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mValidatingDspTrigger = false;
+                    externalCallback.onRejected(result);
+                    if (mDebugHotwordLogging && result != null) {
+                        Slog.i(TAG, "Egressed rejected result: " + result);
+                    }
+                }
+            }
+        };
+
+        mValidatingDspTrigger = true;
+        mRemoteHotwordDetectionService.run(service -> {
+            // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
+            // the callback before timeout value. In order to reduce the latency impact between
+            // server side and client side, we need to use another timeout value
+            // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
+            mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
+                    () -> {
+                        // TODO: avoid allocate every time
+                        timeoutDetected.set(true);
+                        Slog.w(TAG, "Timed out on #detectFromDspSource");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
+                        try {
+                            externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Failed to report onError status: ", e);
+                            HotwordMetricsLogger.writeDetectorEvent(
+                                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                                    mVoiceInteractionServiceUid);
+                        }
+                    },
+                    MAX_VALIDATION_TIMEOUT_MILLIS,
+                    TimeUnit.MILLISECONDS);
+            service.detectFromDspSource(
+                    recognitionEvent,
+                    recognitionEvent.getCaptureFormat(),
+                    VALIDATION_TIMEOUT_MILLIS,
+                    internalCallback);
+        });
+    }
+
+    @Override
+    @SuppressWarnings("GuardedBy")
+    void informRestartProcessLocked() {
+        // TODO(b/244598068): Check HotwordAudioStreamManager first
+        Slog.v(TAG, "informRestartProcessLocked");
+        if (mValidatingDspTrigger) {
+            // We're restarting the process while it's processing a DSP trigger, so report a
+            // rejection. This also allows the Interactor to startRecognition again
+            try {
+                mCallback.onRejected(new HotwordRejectedResult.Builder().build());
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call #rejected");
+                HotwordMetricsLogger.writeDetectorEvent(
+                        HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
+                        mVoiceInteractionServiceUid);
+            }
+            mValidatingDspTrigger = false;
+        }
+        mUpdateStateAfterStartFinished.set(false);
+
+        try {
+            mCallback.onProcessRestarted();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+            HotwordMetricsLogger.writeDetectorEvent(
+                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP,
+                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
+                    mVoiceInteractionServiceUid);
+        }
+
+        mPerformingExternalSourceHotwordDetection = false;
+        closeExternalAudioStreamLocked("process restarted");
+    }
+
+    @SuppressWarnings("GuardedBy")
+    public void dumpLocked(String prefix, PrintWriter pw) {
+        super.dumpLocked(prefix, pw);
+        pw.print(prefix); pw.print("mValidatingDspTrigger="); pw.println(mValidatingDspTrigger);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 2ac25b6..2a4d4a1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -16,67 +16,27 @@
 
 package com.android.server.voiceinteraction;
 
-import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
-import static android.Manifest.permission.RECORD_AUDIO;
-import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
-import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
-import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
-import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
-import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
-import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
-
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
 import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
-import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.attention.AttentionManagerInternal;
 import android.content.ComponentName;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.Intent;
-import android.content.PermissionChecker;
 import android.hardware.soundtrigger.IRecognitionStatusCallback;
 import android.hardware.soundtrigger.SoundTrigger;
 import android.media.AudioFormat;
 import android.media.AudioManagerInternal;
 import android.media.permission.Identity;
-import android.media.permission.PermissionUtil;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -87,41 +47,27 @@
 import android.os.ServiceManager;
 import android.os.SharedMemory;
 import android.provider.DeviceConfig;
-import android.service.voice.HotwordDetectedResult;
 import android.service.voice.HotwordDetectionService;
 import android.service.voice.HotwordDetector;
-import android.service.voice.HotwordRejectedResult;
-import android.service.voice.IDspHotwordDetectionCallback;
 import android.service.voice.IHotwordDetectionService;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
 import android.speech.IRecognitionServiceManager;
-import android.text.TextUtils;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.contentcapture.IContentCaptureManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
-import com.android.internal.infra.AndroidFuture;
 import com.android.internal.infra.ServiceConnector;
 import com.android.server.LocalServices;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.time.Duration;
 import java.time.Instant;
-import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Function;
 
 /**
@@ -132,63 +78,15 @@
     static final boolean DEBUG = false;
 
     private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
-    // TODO: These constants need to be refined.
-    // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
-    private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
-    // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
-    private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
-    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
-    private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
-    private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
-            Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
     private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
     private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
 
-    // The error codes are used for onError callback
-    private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
-    private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
-    private static final int CALLBACK_DETECT_TIMEOUT = -3;
-    private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
-
-    // Hotword metrics
-    private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
-    private static final int METRICS_INIT_UNKNOWN_NO_VALUE =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
-    private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
-    private static final int METRICS_INIT_CALLBACK_STATE_ERROR =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
-    private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
-
-    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
-    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
-    private static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
-
-    private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
-    private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
-    private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
-    private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
-            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
-
-    private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
     // TODO: This may need to be a Handler(looper)
     private final ScheduledExecutorService mScheduledExecutorService =
             Executors.newSingleThreadScheduledExecutor();
-    private final AppOpsManager mAppOpsManager;
-    private final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
     @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
-    private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
     private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
-    private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
-    private final IHotwordRecognitionStatusCallback mCallback;
+    @NonNull private final ServiceConnectionFactory mServiceConnectionFactory;
     private final int mDetectorType;
     /**
      * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
@@ -201,35 +99,22 @@
     final ComponentName mDetectionComponentName;
     final int mUser;
     final Context mContext;
-
-    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
-
-    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
-            this::setProximityValue;
-
-
     volatile HotwordDetectionServiceIdentity mIdentity;
-    private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
     private Instant mLastRestartInstant;
 
-    private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
     private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
 
     /** Identity used for attributing app ops when delivering data to the Interactor. */
     @GuardedBy("mLock")
     @Nullable
     private final Identity mVoiceInteractorIdentity;
-    @GuardedBy("mLock")
-    private ParcelFileDescriptor mCurrentAudioSink;
-    @GuardedBy("mLock")
-    private boolean mValidatingDspTrigger = false;
-    @GuardedBy("mLock")
-    private boolean mPerformingSoftwareHotwordDetection;
-    private @NonNull ServiceConnection mRemoteHotwordDetectionService;
+    @NonNull private ServiceConnection mRemoteHotwordDetectionService;
     private IBinder mAudioFlinger;
-    private boolean mDebugHotwordLogging = false;
     @GuardedBy("mLock")
-    private double mProximityMeters = PROXIMITY_UNKNOWN;
+    private boolean mDebugHotwordLogging = false;
+
+    @GuardedBy("mLock")
+    private final HotwordDetectorSession mHotwordDetectorSession;
 
     HotwordDetectionConnection(Object lock, Context context, int voiceInteractionServiceUid,
             Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
@@ -244,13 +129,8 @@
         mContext = context;
         mVoiceInteractionServiceUid = voiceInteractionServiceUid;
         mVoiceInteractorIdentity = voiceInteractorIdentity;
-        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
-        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
-                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                mVoiceInteractorIdentity.attributionTag);
         mDetectionComponentName = serviceName;
         mUser = userId;
-        mCallback = callback;
         mDetectorType = detectorType;
         mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
                 KEY_RESTART_PERIOD_IN_SECONDS, 0);
@@ -259,17 +139,21 @@
         initAudioFlingerLocked();
 
         mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
-
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
-        if (ENABLE_PROXIMITY_RESULT) {
-            mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
-            if (mAttentionManagerInternal != null) {
-                mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
-            }
-        }
-
         mLastRestartInstant = Instant.now();
-        updateStateAfterProcessStart(options, sharedMemory);
+
+        if (detectorType == HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP) {
+            mHotwordDetectorSession = new DspTrustedHotwordDetectorSession(
+                    mRemoteHotwordDetectionService, mLock, mContext, callback,
+                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+                    mScheduledExecutorService, mDebugHotwordLogging);
+        } else {
+            mHotwordDetectorSession = new SoftwareTrustedHotwordDetectorSession(
+                    mRemoteHotwordDetectionService, mLock, mContext, callback,
+                    mVoiceInteractionServiceUid, mVoiceInteractorIdentity,
+                    mScheduledExecutorService, mDebugHotwordLogging);
+        }
+        mHotwordDetectorSession.initialize(options, sharedMemory);
 
         if (mReStartPeriodSeconds <= 0) {
             mCancellationTaskFuture = null;
@@ -320,106 +204,11 @@
         }
     }
 
-    private void updateStateAfterProcessStart(
-            PersistableBundle options, SharedMemory sharedMemory) {
-        if (DEBUG) {
-            Slog.d(TAG, "updateStateAfterProcessStart");
-        }
-        mRemoteHotwordDetectionService.postAsync(service -> {
-            AndroidFuture<Void> future = new AndroidFuture<>();
-            IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
-                @Override
-                public void sendResult(Bundle bundle) throws RemoteException {
-                    if (DEBUG) {
-                        Slog.d(TAG, "updateState finish");
-                    }
-                    future.complete(null);
-                    if (mUpdateStateAfterStartFinished.getAndSet(true)) {
-                        Slog.w(TAG, "call callback after timeout");
-                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
-                                mVoiceInteractionServiceUid);
-                        return;
-                    }
-                    Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
-                    int status = statusResultPair.first;
-                    int initResultMetricsResult = statusResultPair.second;
-                    try {
-                        mCallback.onStatusReported(status);
-                        HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                                initResultMetricsResult);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to report initialization status: " + e);
-                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                                mVoiceInteractionServiceUid);
-                    }
-                }
-            };
-            try {
-                service.updateState(options, sharedMemory, statusCallback);
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
-                        mVoiceInteractionServiceUid);
-            } catch (RemoteException e) {
-                // TODO: (b/181842909) Report an error to voice interactor
-                Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
-                        mVoiceInteractionServiceUid);
-            }
-            return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        }).whenComplete((res, err) -> {
-            if (err instanceof TimeoutException) {
-                Slog.w(TAG, "updateState timed out");
-                if (mUpdateStateAfterStartFinished.getAndSet(true)) {
-                    return;
-                }
-                try {
-                    mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
-                    HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                            METRICS_INIT_UNKNOWN_TIMEOUT);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
-            } else if (err != null) {
-                Slog.w(TAG, "Failed to update state: " + err);
-            } else {
-                // NOTE: so far we don't need to take any action.
-            }
-        });
-    }
-
-    private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) {
-        if (bundle == null) {
-            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE);
-        }
-        int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN);
-        if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) {
-            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
-                    status == INITIALIZATION_STATUS_UNKNOWN
-                    ? METRICS_INIT_UNKNOWN_NO_VALUE
-                    : METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
-        }
-        // TODO: should guard against negative here
-        int metricsResult = status == INITIALIZATION_STATUS_SUCCESS
-                ? METRICS_INIT_CALLBACK_STATE_SUCCESS
-                : METRICS_INIT_CALLBACK_STATE_ERROR;
-        return new Pair<>(status, metricsResult);
-    }
-
-    private boolean isBound() {
-        synchronized (mLock) {
-            return mRemoteHotwordDetectionService.isBound();
-        }
-    }
-
+    @SuppressWarnings("GuardedBy")
     void cancelLocked() {
         Slog.v(TAG, "cancelLocked");
         clearDebugHotwordLoggingTimeoutLocked();
+        mHotwordDetectorSession.destroyLocked();
         mDebugHotwordLogging = false;
         mRemoteHotwordDetectionService.unbind();
         LocalServices.getService(PermissionManagerServiceInternal.class)
@@ -434,118 +223,32 @@
         if (mAudioFlinger != null) {
             mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
         }
-        if (mAttentionManagerInternal != null) {
-            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
-        }
     }
 
+    @SuppressWarnings("GuardedBy")
     void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
-                mVoiceInteractionServiceUid);
-
-        // Prevent doing the init late, so restart is handled equally to a clean process start.
-        // TODO(b/191742511): this logic needs a test
-        if (!mUpdateStateAfterStartFinished.get()
-                && Instant.now().minus(MAX_UPDATE_TIMEOUT_DURATION).isBefore(mLastRestartInstant)) {
-            Slog.v(TAG, "call updateStateAfterProcessStart");
-            updateStateAfterProcessStart(options, sharedMemory);
-        } else {
-            mRemoteHotwordDetectionService.run(
-                    service -> service.updateState(options, sharedMemory, null /* callback */));
-        }
+        mHotwordDetectorSession.updateStateLocked(options, sharedMemory, mLastRestartInstant);
     }
 
+    /**
+     * This method is only used by SoftwareHotwordDetector.
+     */
     void startListeningFromMic(
             AudioFormat audioFormat,
             IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningFromMic");
         }
-        mSoftwareCallback = callback;
-
         synchronized (mLock) {
-            if (mPerformingSoftwareHotwordDetection) {
-                Slog.i(TAG, "Hotword validation is already in progress, ignoring.");
+            if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                Slog.d(TAG, "It is not a software detector");
                 return;
             }
-            mPerformingSoftwareHotwordDetection = true;
-
-            startListeningFromMicLocked();
+            ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession)
+                    .startListeningFromMicLocked(audioFormat, callback);
         }
     }
 
-    private void startListeningFromMicLocked() {
-        // TODO: consider making this a non-anonymous class.
-        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
-            @Override
-            public void onDetected(HotwordDetectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onDetected");
-                }
-                synchronized (mLock) {
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
-                    if (!mPerformingSoftwareHotwordDetection) {
-                        Slog.i(TAG, "Hotword detection has already completed");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mPerformingSoftwareHotwordDetection = false;
-                    try {
-                        enforcePermissionsForDataDelivery();
-                    } catch (SecurityException e) {
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
-                        mSoftwareCallback.onError();
-                        return;
-                    }
-                    saveProximityValueToBundle(result);
-                    HotwordDetectedResult newResult;
-                    try {
-                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
-                    } catch (IOException e) {
-                        // TODO: Write event
-                        mSoftwareCallback.onError();
-                        return;
-                    }
-                    mSoftwareCallback.onDetected(newResult, null, null);
-                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
-                            + " bits from hotword trusted process");
-                    if (mDebugHotwordLogging) {
-                        Slog.i(TAG, "Egressed detected result: " + newResult);
-                    }
-                }
-            }
-
-            @Override
-            public void onRejected(HotwordRejectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.wtf(TAG, "onRejected");
-                }
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        mDetectorType,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
-                // onRejected isn't allowed here, and we are not expecting it.
-            }
-        };
-
-        mRemoteHotwordDetectionService.run(
-                service -> service.detectFromMicrophoneSource(
-                        null,
-                        AUDIO_SOURCE_MICROPHONE,
-                        null,
-                        null,
-                        internalCallback));
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
-                mVoiceInteractionServiceUid);
-    }
-
     public void startListeningFromExternalSource(
             ParcelFileDescriptor audioStream,
             AudioFormat audioFormat,
@@ -554,39 +257,28 @@
         if (DEBUG) {
             Slog.d(TAG, "startListeningFromExternalSource");
         }
-
-        handleExternalSourceHotwordDetection(
-                audioStream,
-                audioFormat,
-                options,
-                callback);
+        synchronized (mLock) {
+            mHotwordDetectorSession.startListeningFromExternalSourceLocked(audioStream, audioFormat,
+                    options, callback);
+        }
     }
 
+    /**
+     * This method is only used by SoftwareHotwordDetector.
+     */
     void stopListening() {
         if (DEBUG) {
             Slog.d(TAG, "stopListening");
         }
         synchronized (mLock) {
-            stopListeningLocked();
+            if (!(mHotwordDetectorSession instanceof SoftwareTrustedHotwordDetectorSession)) {
+                Slog.d(TAG, "It is not a software detector");
+                return;
+            }
+            ((SoftwareTrustedHotwordDetectorSession) mHotwordDetectorSession).stopListeningLocked();
         }
     }
 
-    private void stopListeningLocked() {
-        if (!mPerformingSoftwareHotwordDetection) {
-            Slog.i(TAG, "Hotword detection is not running");
-            return;
-        }
-        mPerformingSoftwareHotwordDetection = false;
-
-        mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
-
-        if (mCurrentAudioSink != null) {
-            Slog.i(TAG, "Closing audio stream to hotword detector: stopping requested");
-            bestEffortClose(mCurrentAudioSink);
-        }
-        mCurrentAudioSink = null;
-    }
-
     void triggerHardwareRecognitionEventForTestLocked(
             SoundTrigger.KeyphraseRecognitionEvent event,
             IHotwordRecognitionStatusCallback callback) {
@@ -596,130 +288,18 @@
         detectFromDspSource(event, callback);
     }
 
-    void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
+    private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
             IHotwordRecognitionStatusCallback externalCallback) {
         if (DEBUG) {
             Slog.d(TAG, "detectFromDspSource");
         }
-
-        AtomicBoolean timeoutDetected = new AtomicBoolean(false);
-        // TODO: consider making this a non-anonymous class.
-        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
-            @Override
-            public void onDetected(HotwordDetectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onDetected");
-                }
-                synchronized (mLock) {
-                    if (mCancellationKeyPhraseDetectionFuture != null) {
-                        mCancellationKeyPhraseDetectionFuture.cancel(true);
-                    }
-                    if (timeoutDetected.get()) {
-                        return;
-                    }
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
-                    if (!mValidatingDspTrigger) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a process restart");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mValidatingDspTrigger = false;
-                    try {
-                        enforcePermissionsForDataDelivery();
-                        enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
-                    } catch (SecurityException e) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
-                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
-                        return;
-                    }
-                    saveProximityValueToBundle(result);
-                    HotwordDetectedResult newResult;
-                    try {
-                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
-                    } catch (IOException e) {
-                        // TODO: Write event
-                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
-                        return;
-                    }
-                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
-                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
-                            + " bits from hotword trusted process");
-                    if (mDebugHotwordLogging) {
-                        Slog.i(TAG, "Egressed detected result: " + newResult);
-                    }
-                }
-            }
-
-            @Override
-            public void onRejected(HotwordRejectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onRejected");
-                }
-                synchronized (mLock) {
-                    if (mCancellationKeyPhraseDetectionFuture != null) {
-                        mCancellationKeyPhraseDetectionFuture.cancel(true);
-                    }
-                    if (timeoutDetected.get()) {
-                        return;
-                    }
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
-                    if (!mValidatingDspTrigger) {
-                        Slog.i(TAG, "Ignoring #onRejected due to a process restart");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mValidatingDspTrigger = false;
-                    externalCallback.onRejected(result);
-                    if (mDebugHotwordLogging && result != null) {
-                        Slog.i(TAG, "Egressed rejected result: " + result);
-                    }
-                }
-            }
-        };
-
         synchronized (mLock) {
-            mValidatingDspTrigger = true;
-            mRemoteHotwordDetectionService.run(service -> {
-                // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
-                // the callback before timeout value. In order to reduce the latency impact between
-                // server side and client side, we need to use another timeout value
-                // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
-                mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
-                        () -> {
-                            // TODO: avoid allocate every time
-                            timeoutDetected.set(true);
-                            Slog.w(TAG, "Timed out on #detectFromDspSource");
-                            HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                    mDetectorType,
-                                    HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
-                            try {
-                                externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to report onError status: ", e);
-                                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                                        mVoiceInteractionServiceUid);
-                            }
-                        },
-                        MAX_VALIDATION_TIMEOUT_MILLIS,
-                        TimeUnit.MILLISECONDS);
-                service.detectFromDspSource(
-                        recognitionEvent,
-                        recognitionEvent.getCaptureFormat(),
-                        VALIDATION_TIMEOUT_MILLIS,
-                        internalCallback);
-            });
+            if (!(mHotwordDetectorSession instanceof DspTrustedHotwordDetectorSession)) {
+                Slog.d(TAG, "It is not a Dsp detector");
+                return;
+            }
+            ((DspTrustedHotwordDetectorSession) mHotwordDetectorSession).detectFromDspSourceLocked(
+                    recognitionEvent, externalCallback);
         }
     }
 
@@ -730,10 +310,12 @@
         }
     }
 
+    @SuppressWarnings("GuardedBy")
     void setDebugHotwordLoggingLocked(boolean logging) {
         Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
         clearDebugHotwordLoggingTimeoutLocked();
         mDebugHotwordLogging = logging;
+        mHotwordDetectorSession.setDebugHotwordLoggingLocked(logging);
 
         if (logging) {
             // Reset mDebugHotwordLogging to false after one hour
@@ -741,6 +323,7 @@
                 Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
                 synchronized (mLock) {
                     mDebugHotwordLogging = false;
+                    mHotwordDetectorSession.setDebugHotwordLoggingLocked(false);
                 }
             }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
         }
@@ -753,60 +336,23 @@
         }
     }
 
+    @SuppressWarnings("GuardedBy")
     private void restartProcessLocked() {
         // TODO(b/244598068): Check HotwordAudioStreamManager first
         Slog.v(TAG, "Restarting hotword detection process");
         ServiceConnection oldConnection = mRemoteHotwordDetectionService;
         HotwordDetectionServiceIdentity previousIdentity = mIdentity;
 
-        // TODO(volnov): this can be done after connect() has been successful.
-        if (mValidatingDspTrigger) {
-            // We're restarting the process while it's processing a DSP trigger, so report a
-            // rejection. This also allows the Interactor to startReco again
-            try {
-                mCallback.onRejected(new HotwordRejectedResult.Builder().build());
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        mDetectorType,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call #rejected");
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
-                        mVoiceInteractionServiceUid);
-            }
-            mValidatingDspTrigger = false;
-        }
-
-        mUpdateStateAfterStartFinished.set(false);
         mLastRestartInstant = Instant.now();
-
         // Recreate connection to reset the cache.
         mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
 
-        Slog.v(TAG, "Started the new process, issuing #onProcessRestarted");
-        try {
-            mCallback.onProcessRestarted();
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
-            HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
-                    mVoiceInteractionServiceUid);
-        }
-
-        // Restart listening from microphone if the hotword process has been restarted.
-        if (mPerformingSoftwareHotwordDetection) {
-            Slog.i(TAG, "Process restarted: calling startRecognition() again");
-            startListeningFromMicLocked();
-        }
-
-        if (mCurrentAudioSink != null) {
-            Slog.i(TAG, "Closing external audio stream to hotword detector: process restarted");
-            bestEffortClose(mCurrentAudioSink);
-            mCurrentAudioSink = null;
-        }
-
+        Slog.v(TAG, "Started the new process, dispatching processRestarted to detector");
+        mHotwordDetectorSession.updateRemoteHotwordDetectionServiceLocked(
+                mRemoteHotwordDetectionService);
+        mHotwordDetectorSession.informRestartProcessLocked();
         if (DEBUG) {
-            Slog.i(TAG, "#onProcessRestarted called, unbinding from the old process");
+            Slog.i(TAG, "processRestarted is dispatched done, unbinding from the old process");
         }
         oldConnection.ignoreConnectionStatusEvents();
         oldConnection.unbind();
@@ -816,7 +362,6 @@
     }
 
     static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
-        private SoundTrigger.KeyphraseRecognitionEvent mRecognitionEvent;
         private final HotwordDetectionConnection mHotwordDetectionConnection;
         private final IHotwordRecognitionStatusCallback mExternalCallback;
 
@@ -837,7 +382,6 @@
                 HotwordMetricsLogger.writeKeyphraseTriggerEvent(
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
                         HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
-                mRecognitionEvent = recognitionEvent;
                 mHotwordDetectionConnection.detectFromDspSource(
                         recognitionEvent, mExternalCallback);
             } else {
@@ -872,160 +416,18 @@
     }
 
     public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
-        pw.print(prefix);
-        pw.print("mBound=" + mRemoteHotwordDetectionService.isBound());
-        pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
-        pw.print(", mPerformingSoftwareHotwordDetection=" + mPerformingSoftwareHotwordDetection);
-        pw.print(", mRestartCount=" + mServiceConnectionFactory.mRestartCount);
-        pw.print(", mLastRestartInstant=" + mLastRestartInstant);
-        pw.println(", mDetectorType=" + HotwordDetector.detectorTypeToString(mDetectorType));
-    }
-
-    private void handleExternalSourceHotwordDetection(
-            ParcelFileDescriptor audioStream,
-            AudioFormat audioFormat,
-            @Nullable PersistableBundle options,
-            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "#handleExternalSourceHotwordDetection");
-        }
-        InputStream audioSource = new ParcelFileDescriptor.AutoCloseInputStream(audioStream);
-
-        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
-        if (clientPipe == null) {
-            // TODO: Need to propagate as unknown error or something?
-            return;
-        }
-        ParcelFileDescriptor serviceAudioSink = clientPipe.second;
-        ParcelFileDescriptor serviceAudioSource = clientPipe.first;
-
         synchronized (mLock) {
-            mCurrentAudioSink = serviceAudioSink;
+            pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
+            pw.print(prefix); pw.print("mBound=");
+            pw.println(mRemoteHotwordDetectionService.isBound());
+            pw.print(prefix); pw.print("mRestartCount=");
+            pw.println(mServiceConnectionFactory.mRestartCount);
+            pw.print(prefix); pw.print("mLastRestartInstant="); pw.println(mLastRestartInstant);
+            pw.print(prefix); pw.print("mDetectorType=");
+            pw.println(HotwordDetector.detectorTypeToString(mDetectorType));
+            pw.print(prefix); pw.println("HotwordDetectorSession");
+            mHotwordDetectorSession.dumpLocked(prefix, pw);
         }
-
-        mAudioCopyExecutor.execute(() -> {
-            try (InputStream source = audioSource;
-                 OutputStream fos =
-                         new ParcelFileDescriptor.AutoCloseOutputStream(serviceAudioSink)) {
-
-                byte[] buffer = new byte[1024];
-                while (true) {
-                    int bytesRead = source.read(buffer, 0, 1024);
-
-                    if (bytesRead < 0) {
-                        Slog.i(TAG, "Reached end of stream for external hotword");
-                        break;
-                    }
-
-                    // TODO: First write to ring buffer to make sure we don't lose data if the next
-                    // statement fails.
-                    // ringBuffer.append(buffer, bytesRead);
-                    fos.write(buffer, 0, bytesRead);
-                }
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed supplying audio data to validator", e);
-
-                try {
-                    callback.onError();
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to report onError status: " + ex);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
-            } finally {
-                synchronized (mLock) {
-                    mCurrentAudioSink = null;
-                }
-            }
-        });
-
-        // TODO: handle cancellations well
-        // TODO: what if we cancelled and started a new one?
-        mRemoteHotwordDetectionService.run(
-                service -> {
-                    service.detectFromMicrophoneSource(
-                            serviceAudioSource,
-                            // TODO: consider making a proxy callback + copy of audio format
-                            AUDIO_SOURCE_EXTERNAL,
-                            audioFormat,
-                            options,
-                            new IDspHotwordDetectionCallback.Stub() {
-                                @Override
-                                public void onRejected(HotwordRejectedResult result)
-                                        throws RemoteException {
-                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                            METRICS_EXTERNAL_SOURCE_REJECTED,
-                                            mVoiceInteractionServiceUid);
-                                    mScheduledExecutorService.schedule(
-                                            () -> {
-                                                bestEffortClose(serviceAudioSink, audioSource);
-                                            },
-                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
-                                            TimeUnit.MILLISECONDS);
-
-                                    callback.onRejected(result);
-
-                                    if (result != null) {
-                                        Slog.i(TAG, "Egressed 'hotword rejected result' "
-                                                + "from hotword trusted process");
-                                        if (mDebugHotwordLogging) {
-                                            Slog.i(TAG, "Egressed detected result: " + result);
-                                        }
-                                    }
-                                }
-
-                                @Override
-                                public void onDetected(HotwordDetectedResult triggerResult)
-                                        throws RemoteException {
-                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                            METRICS_EXTERNAL_SOURCE_DETECTED,
-                                            mVoiceInteractionServiceUid);
-                                    mScheduledExecutorService.schedule(
-                                            () -> {
-                                                bestEffortClose(serviceAudioSink, audioSource);
-                                            },
-                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
-                                            TimeUnit.MILLISECONDS);
-
-                                    try {
-                                        enforcePermissionsForDataDelivery();
-                                    } catch (SecurityException e) {
-                                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                                METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
-                                                mVoiceInteractionServiceUid);
-                                        callback.onError();
-                                        return;
-                                    }
-                                    HotwordDetectedResult newResult;
-                                    try {
-                                        newResult =
-                                                mHotwordAudioStreamCopier.startCopyingAudioStreams(
-                                                        triggerResult);
-                                    } catch (IOException e) {
-                                        // TODO: Write event
-                                        callback.onError();
-                                        return;
-                                    }
-                                    callback.onDetected(newResult, null /* audioFormat */,
-                                            null /* audioStream */);
-                                    Slog.i(TAG, "Egressed "
-                                            + HotwordDetectedResult.getUsageSize(newResult)
-                                            + " bits from hotword trusted process");
-                                    if (mDebugHotwordLogging) {
-                                        Slog.i(TAG,
-                                                "Egressed detected result: " + newResult);
-                                    }
-                                }
-                            });
-
-                    // A copy of this has been created and passed to the hotword validator
-                    bestEffortClose(serviceAudioSource);
-                });
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
-                mVoiceInteractionServiceUid);
     }
 
     private class ServiceConnectionFactory {
@@ -1054,7 +456,7 @@
         }
     }
 
-    private class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
+    class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
         private final Object mLock = new Object();
 
         private final Intent mIntent;
@@ -1109,21 +511,16 @@
         @Override
         public void binderDied() {
             super.binderDied();
+            Slog.w(TAG, "binderDied");
             synchronized (mLock) {
                 if (!mRespectServiceConnectionStatusChanged) {
                     Slog.v(TAG, "Ignored #binderDied event");
                     return;
                 }
-
-                Slog.w(TAG, "binderDied");
-                try {
-                    mCallback.onError(HOTWORD_DETECTION_SERVICE_DIED);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to report onError status: " + e);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
+            }
+            synchronized (HotwordDetectionConnection.this.mLock) {
+                mHotwordDetectorSession.reportErrorLocked(
+                        HotwordDetectorSession.HOTWORD_DETECTION_SERVICE_DIED);
             }
         }
 
@@ -1168,18 +565,6 @@
         }
     }
 
-    private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
-        ParcelFileDescriptor[] fileDescriptors;
-        try {
-            fileDescriptors = ParcelFileDescriptor.createPipe();
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to create audio stream pipe", e);
-            return null;
-        }
-
-        return Pair.create(fileDescriptors[0], fileDescriptors[1]);
-    }
-
     private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
         // TODO: Consider using a proxy that limits the exposed API surface.
         connection.run(service -> service.updateAudioFlinger(audioFlinger));
@@ -1241,85 +626,4 @@
             }
         });
     }
-
-    private void saveProximityValueToBundle(HotwordDetectedResult result) {
-        synchronized (mLock) {
-            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
-                result.setProximity(mProximityMeters);
-            }
-        }
-    }
-
-    private void setProximityValue(double proximityMeters) {
-        synchronized (mLock) {
-            mProximityMeters = proximityMeters;
-        }
-    }
-
-    private static void bestEffortClose(Closeable... closeables) {
-        for (Closeable closeable : closeables) {
-            bestEffortClose(closeable);
-        }
-    }
-
-    private static void bestEffortClose(Closeable closeable) {
-        try {
-            closeable.close();
-        } catch (IOException e) {
-            if (DEBUG) {
-                Slog.w(TAG, "Failed closing", e);
-            }
-        }
-    }
-
-    // TODO: Share this code with SoundTriggerMiddlewarePermission.
-    private void enforcePermissionsForDataDelivery() {
-        Binder.withCleanCallingIdentity(() -> {
-            enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
-            int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
-            mAppOpsManager.noteOpNoThrow(hotwordOp,
-                    mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                    mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
-            enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
-                    CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
-        });
-    }
-
-    /**
-     * Throws a {@link SecurityException} iff the given identity has given permission to receive
-     * data.
-     *
-     * @param context    A {@link Context}, used for permission checks.
-     * @param identity   The identity to check.
-     * @param permission The identifier of the permission we want to check.
-     * @param reason     The reason why we're requesting the permission, for auditing purposes.
-     */
-    private static void enforcePermissionForDataDelivery(@NonNull Context context,
-            @NonNull Identity identity,
-            @NonNull String permission, @NonNull String reason) {
-        final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity,
-                permission, reason);
-        if (status != PermissionChecker.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
-                            permission,
-                            SoundTriggerSessionPermissionsDecorator.toString(identity)));
-        }
-    }
-
-    private static void enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result,
-            SoundTrigger.KeyphraseRecognitionEvent recognitionEvent) {
-        // verify the phrase ID in HotwordDetectedResult is not exposing extra phrases
-        // the DSP did not detect
-        for (SoundTrigger.KeyphraseRecognitionExtra keyphrase : recognitionEvent.keyphraseExtras) {
-            if (keyphrase.getKeyphraseId() == result.getHotwordPhraseId()) {
-                return;
-            }
-        }
-        throw new SecurityException("Ignoring #onDetected due to trusted service "
-                + "sharing a keyphrase ID which the DSP did not detect");
-    }
-
-    private static final String OP_MESSAGE =
-            "Providing hotword detection result to VoiceInteractionService";
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
new file mode 100644
index 0000000..f9f43c9
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectorSession.java
@@ -0,0 +1,666 @@
+/*
+ * 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.voiceinteraction;
+
+import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
+import static android.Manifest.permission.RECORD_AUDIO;
+import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
+import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
+import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
+import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
+import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
+import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.attention.AttentionManagerInternal;
+import android.content.Context;
+import android.content.PermissionChecker;
+import android.hardware.soundtrigger.SoundTrigger;
+import android.media.AudioFormat;
+import android.media.permission.Identity;
+import android.media.permission.PermissionUtil;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IRemoteCallback;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.voice.HotwordDetectedResult;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetector;
+import android.service.voice.HotwordRejectedResult;
+import android.service.voice.IDspHotwordDetectionCallback;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+import com.android.internal.infra.AndroidFuture;
+import com.android.server.LocalServices;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that provides trusted hotword detector to communicate with the {@link
+ * HotwordDetectionService}.
+ *
+ * This class provides the methods to do initialization with the {@link HotwordDetectionService}
+ * and handle external source detection. It also provides the methods to check if we can egress
+ * the data from the {@link HotwordDetectionService}.
+ *
+ * The subclass should override the {@link #informRestartProcessLocked()} to handle the trusted
+ * process restart.
+ */
+abstract class HotwordDetectorSession {
+    private static final String TAG = "HotwordDetectorSession";
+    static final boolean DEBUG = false;
+
+    private static final String OP_MESSAGE =
+            "Providing hotword detection result to VoiceInteractionService";
+
+    // The error codes are used for onError callback
+    static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
+    static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
+    static final int CALLBACK_DETECT_TIMEOUT = -3;
+    static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
+
+    // TODO: These constants need to be refined.
+    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
+    private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
+    private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
+            Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
+
+    // Hotword metrics
+    private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
+    private static final int METRICS_INIT_UNKNOWN_NO_VALUE =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
+    private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
+    private static final int METRICS_INIT_CALLBACK_STATE_ERROR =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
+    private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS =
+            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
+
+    static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
+    static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
+    static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
+            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+
+    private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+    private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+    private static final int EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
+            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+    private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
+            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
+
+    private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
+    // TODO: This may need to be a Handler(looper)
+    final ScheduledExecutorService mScheduledExecutorService;
+    private final AppOpsManager mAppOpsManager;
+    final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
+    final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
+    final IHotwordRecognitionStatusCallback mCallback;
+
+    final Object mLock;
+    final int mVoiceInteractionServiceUid;
+    final Context mContext;
+
+    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
+
+    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
+            this::setProximityValue;
+
+    /** Identity used for attributing app ops when delivering data to the Interactor. */
+    @Nullable
+    private final Identity mVoiceInteractorIdentity;
+    @GuardedBy("mLock")
+    ParcelFileDescriptor mCurrentAudioSink;
+    @GuardedBy("mLock")
+    @NonNull HotwordDetectionConnection.ServiceConnection mRemoteHotwordDetectionService;
+    boolean mDebugHotwordLogging = false;
+    @GuardedBy("mLock")
+    private double mProximityMeters = PROXIMITY_UNKNOWN;
+    @GuardedBy("mLock")
+    private boolean mInitialized = false;
+    @GuardedBy("mLock")
+    private boolean mDestroyed = false;
+    @GuardedBy("mLock")
+    boolean mPerformingExternalSourceHotwordDetection;
+
+    HotwordDetectorSession(
+            @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
+            @NonNull Object lock, @NonNull Context context,
+            @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
+            Identity voiceInteractorIdentity,
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+        mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+        mLock = lock;
+        mContext = context;
+        mCallback = callback;
+        mVoiceInteractionServiceUid = voiceInteractionServiceUid;
+        mVoiceInteractorIdentity = voiceInteractorIdentity;
+        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
+        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, getDetectorType(),
+                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                mVoiceInteractorIdentity.attributionTag);
+        mScheduledExecutorService = scheduledExecutorService;
+        mDebugHotwordLogging = logging;
+
+        if (ENABLE_PROXIMITY_RESULT) {
+            mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
+            if (mAttentionManagerInternal != null) {
+                mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
+            }
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void updateStateAfterProcessStartLocked(PersistableBundle options,
+            SharedMemory sharedMemory) {
+        if (DEBUG) {
+            Slog.d(TAG, "updateStateAfterProcessStartLocked");
+        }
+        AndroidFuture<Void> voidFuture = mRemoteHotwordDetectionService.postAsync(service -> {
+            AndroidFuture<Void> future = new AndroidFuture<>();
+            IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle bundle) throws RemoteException {
+                    if (DEBUG) {
+                        Slog.d(TAG, "updateState finish");
+                    }
+                    future.complete(null);
+                    if (mUpdateStateAfterStartFinished.getAndSet(true)) {
+                        Slog.w(TAG, "call callback after timeout");
+                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
+                                mVoiceInteractionServiceUid);
+                        return;
+                    }
+                    Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
+                    int status = statusResultPair.first;
+                    int initResultMetricsResult = statusResultPair.second;
+                    try {
+                        mCallback.onStatusReported(status);
+                        HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+                                initResultMetricsResult);
+                    } catch (RemoteException e) {
+                        Slog.w(TAG, "Failed to report initialization status: " + e);
+                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                                mVoiceInteractionServiceUid);
+                    }
+                }
+            };
+            try {
+                service.updateState(options, sharedMemory, statusCallback);
+                HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
+                        mVoiceInteractionServiceUid);
+            } catch (RemoteException e) {
+                // TODO: (b/181842909) Report an error to voice interactor
+                Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
+                HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
+                        mVoiceInteractionServiceUid);
+            }
+            return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        }).whenComplete((res, err) -> {
+            if (err instanceof TimeoutException) {
+                Slog.w(TAG, "updateState timed out");
+                if (mUpdateStateAfterStartFinished.getAndSet(true)) {
+                    return;
+                }
+                try {
+                    mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
+                    HotwordMetricsLogger.writeServiceInitResultEvent(getDetectorType(),
+                            METRICS_INIT_UNKNOWN_TIMEOUT);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
+                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
+            } else if (err != null) {
+                Slog.w(TAG, "Failed to update state: " + err);
+            }
+        });
+        if (voidFuture == null) {
+            Slog.w(TAG, "Failed to create AndroidFuture");
+        }
+    }
+
+    private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) {
+        if (bundle == null) {
+            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE);
+        }
+        int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN);
+        if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) {
+            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
+                    status == INITIALIZATION_STATUS_UNKNOWN
+                            ? METRICS_INIT_UNKNOWN_NO_VALUE
+                            : METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
+        }
+        // TODO: should guard against negative here
+        int metricsResult = status == INITIALIZATION_STATUS_SUCCESS
+                ? METRICS_INIT_CALLBACK_STATE_SUCCESS
+                : METRICS_INIT_CALLBACK_STATE_ERROR;
+        return new Pair<>(status, metricsResult);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory,
+            Instant lastRestartInstant) {
+        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+                mVoiceInteractionServiceUid);
+        // Prevent doing the init late, so restart is handled equally to a clean process start.
+        // TODO(b/191742511): this logic needs a test
+        if (!mUpdateStateAfterStartFinished.get() && Instant.now().minus(
+                MAX_UPDATE_TIMEOUT_DURATION).isBefore(lastRestartInstant)) {
+            Slog.v(TAG, "call updateStateAfterProcessStartLocked");
+            updateStateAfterProcessStartLocked(options, sharedMemory);
+        } else {
+            mRemoteHotwordDetectionService.run(
+                    service -> service.updateState(options, sharedMemory, /* callback= */ null));
+        }
+    }
+
+    void startListeningFromExternalSourceLocked(
+            ParcelFileDescriptor audioStream,
+            AudioFormat audioFormat,
+            @Nullable PersistableBundle options,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startListeningFromExternalSourceLocked");
+        }
+
+        handleExternalSourceHotwordDetectionLocked(
+                audioStream,
+                audioFormat,
+                options,
+                callback);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void handleExternalSourceHotwordDetectionLocked(
+            ParcelFileDescriptor audioStream,
+            AudioFormat audioFormat,
+            @Nullable PersistableBundle options,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "#handleExternalSourceHotwordDetectionLocked");
+        }
+        if (mPerformingExternalSourceHotwordDetection) {
+            Slog.i(TAG, "Hotword validation is already in progress for external source.");
+            return;
+        }
+
+        InputStream audioSource = new ParcelFileDescriptor.AutoCloseInputStream(audioStream);
+
+        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
+        if (clientPipe == null) {
+            // TODO: Need to propagate as unknown error or something?
+            return;
+        }
+        ParcelFileDescriptor serviceAudioSink = clientPipe.second;
+        ParcelFileDescriptor serviceAudioSource = clientPipe.first;
+
+        mCurrentAudioSink = serviceAudioSink;
+        mPerformingExternalSourceHotwordDetection = true;
+
+        mAudioCopyExecutor.execute(() -> {
+            try (InputStream source = audioSource;
+                 OutputStream fos =
+                         new ParcelFileDescriptor.AutoCloseOutputStream(serviceAudioSink)) {
+
+                byte[] buffer = new byte[1024];
+                while (true) {
+                    int bytesRead = source.read(buffer, 0, 1024);
+
+                    if (bytesRead < 0) {
+                        Slog.i(TAG, "Reached end of stream for external hotword");
+                        break;
+                    }
+
+                    // TODO: First write to ring buffer to make sure we don't lose data if the next
+                    // statement fails.
+                    // ringBuffer.append(buffer, bytesRead);
+                    fos.write(buffer, 0, bytesRead);
+                }
+            } catch (IOException e) {
+                Slog.w(TAG, "Failed supplying audio data to validator", e);
+
+                try {
+                    callback.onError();
+                } catch (RemoteException ex) {
+                    Slog.w(TAG, "Failed to report onError status: " + ex);
+                    HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                            mVoiceInteractionServiceUid);
+                }
+            } finally {
+                synchronized (mLock) {
+                    mPerformingExternalSourceHotwordDetection = false;
+                    closeExternalAudioStreamLocked("start external source");
+                }
+            }
+        });
+
+        // TODO: handle cancellations well
+        // TODO: what if we cancelled and started a new one?
+        mRemoteHotwordDetectionService.run(
+                service -> {
+                    service.detectFromMicrophoneSource(
+                            serviceAudioSource,
+                            // TODO: consider making a proxy callback + copy of audio format
+                            AUDIO_SOURCE_EXTERNAL,
+                            audioFormat,
+                            options,
+                            new IDspHotwordDetectionCallback.Stub() {
+                                @Override
+                                public void onRejected(HotwordRejectedResult result)
+                                        throws RemoteException {
+                                    synchronized (mLock) {
+                                        mPerformingExternalSourceHotwordDetection = false;
+                                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                                METRICS_EXTERNAL_SOURCE_REJECTED,
+                                                mVoiceInteractionServiceUid);
+                                        mScheduledExecutorService.schedule(
+                                                () -> {
+                                                    bestEffortClose(serviceAudioSink, audioSource);
+                                                },
+                                                EXTERNAL_HOTWORD_CLEANUP_MILLIS,
+                                                TimeUnit.MILLISECONDS);
+
+                                        callback.onRejected(result);
+
+                                        if (result != null) {
+                                            Slog.i(TAG, "Egressed 'hotword rejected result' "
+                                                    + "from hotword trusted process");
+                                            if (mDebugHotwordLogging) {
+                                                Slog.i(TAG, "Egressed detected result: " + result);
+                                            }
+                                        }
+                                    }
+                                }
+
+                                @Override
+                                public void onDetected(HotwordDetectedResult triggerResult)
+                                        throws RemoteException {
+                                    synchronized (mLock) {
+                                        mPerformingExternalSourceHotwordDetection = false;
+                                        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                                                METRICS_EXTERNAL_SOURCE_DETECTED,
+                                                mVoiceInteractionServiceUid);
+                                        mScheduledExecutorService.schedule(
+                                                () -> {
+                                                    bestEffortClose(serviceAudioSink, audioSource);
+                                                },
+                                                EXTERNAL_HOTWORD_CLEANUP_MILLIS,
+                                                TimeUnit.MILLISECONDS);
+
+                                        try {
+                                            enforcePermissionsForDataDelivery();
+                                        } catch (SecurityException e) {
+                                            HotwordMetricsLogger.writeDetectorEvent(
+                                                    getDetectorType(),
+                                                    EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
+                                                    mVoiceInteractionServiceUid);
+                                            callback.onError();
+                                            return;
+                                        }
+                                        HotwordDetectedResult newResult;
+                                        try {
+                                            newResult = mHotwordAudioStreamCopier
+                                                    .startCopyingAudioStreams(triggerResult);
+                                        } catch (IOException e) {
+                                            // TODO: Write event
+                                            callback.onError();
+                                            return;
+                                        }
+                                        callback.onDetected(newResult, null /* audioFormat */,
+                                                null /* audioStream */);
+                                        Slog.i(TAG, "Egressed "
+                                                + HotwordDetectedResult.getUsageSize(newResult)
+                                                + " bits from hotword trusted process");
+                                        if (mDebugHotwordLogging) {
+                                            Slog.i(TAG,
+                                                    "Egressed detected result: " + newResult);
+                                        }
+                                    }
+                                }
+                            });
+
+                    // A copy of this has been created and passed to the hotword validator
+                    bestEffortClose(serviceAudioSource);
+                });
+        HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
+                mVoiceInteractionServiceUid);
+    }
+
+    void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {
+        synchronized (mLock) {
+            if (mInitialized || mDestroyed) {
+                return;
+            }
+            updateStateAfterProcessStartLocked(options, sharedMemory);
+            mInitialized = true;
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void destroyLocked() {
+        mDestroyed = true;
+        mDebugHotwordLogging = false;
+        mRemoteHotwordDetectionService = null;
+        if (mAttentionManagerInternal != null) {
+            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
+        }
+    }
+
+    void setDebugHotwordLoggingLocked(boolean logging) {
+        Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
+        mDebugHotwordLogging = logging;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void updateRemoteHotwordDetectionServiceLocked(
+            @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService) {
+        mRemoteHotwordDetectionService = remoteHotwordDetectionService;
+    }
+
+    void reportErrorLocked(int status) {
+        try {
+            mCallback.onError(status);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to report onError status: " + e);
+            HotwordMetricsLogger.writeDetectorEvent(getDetectorType(),
+                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
+                    mVoiceInteractionServiceUid);
+        }
+    }
+
+    /**
+     * Called when the trusted process is restarted.
+     */
+    abstract void informRestartProcessLocked();
+
+    private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
+        ParcelFileDescriptor[] fileDescriptors;
+        try {
+            fileDescriptors = ParcelFileDescriptor.createPipe();
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to create audio stream pipe", e);
+            return null;
+        }
+
+        return Pair.create(fileDescriptors[0], fileDescriptors[1]);
+    }
+
+    void saveProximityValueToBundle(HotwordDetectedResult result) {
+        synchronized (mLock) {
+            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
+                result.setProximity(mProximityMeters);
+            }
+        }
+    }
+
+    private void setProximityValue(double proximityMeters) {
+        synchronized (mLock) {
+            mProximityMeters = proximityMeters;
+        }
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void closeExternalAudioStreamLocked(String reason) {
+        if (mCurrentAudioSink != null) {
+            Slog.i(TAG, "Closing external audio stream to hotword detector: " + reason);
+            bestEffortClose(mCurrentAudioSink);
+            mCurrentAudioSink = null;
+        }
+    }
+
+    private static void bestEffortClose(Closeable... closeables) {
+        for (Closeable closeable : closeables) {
+            bestEffortClose(closeable);
+        }
+    }
+
+    private static void bestEffortClose(Closeable closeable) {
+        try {
+            closeable.close();
+        } catch (IOException e) {
+            if (DEBUG) {
+                Slog.w(TAG, "Failed closing", e);
+            }
+        }
+    }
+
+    // TODO: Share this code with SoundTriggerMiddlewarePermission.
+    void enforcePermissionsForDataDelivery() {
+        Binder.withCleanCallingIdentity(() -> {
+            synchronized (mLock) {
+                enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
+                int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
+                mAppOpsManager.noteOpNoThrow(hotwordOp,
+                        mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
+                        mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
+                enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
+                        CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
+            }
+        });
+    }
+
+    /**
+     * Throws a {@link SecurityException} if the given identity has no permission to receive data.
+     *
+     * @param context    A {@link Context}, used for permission checks.
+     * @param identity   The identity to check.
+     * @param permission The identifier of the permission we want to check.
+     * @param reason     The reason why we're requesting the permission, for auditing purposes.
+     */
+    private static void enforcePermissionForDataDelivery(@NonNull Context context,
+            @NonNull Identity identity, @NonNull String permission, @NonNull String reason) {
+        final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity,
+                permission, reason);
+        if (status != PermissionChecker.PERMISSION_GRANTED) {
+            throw new SecurityException(
+                    TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
+                            permission,
+                            SoundTriggerSessionPermissionsDecorator.toString(identity)));
+        }
+    }
+
+    static void enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result,
+            SoundTrigger.KeyphraseRecognitionEvent recognitionEvent) {
+        // verify the phrase ID in HotwordDetectedResult is not exposing extra phrases
+        // the DSP did not detect
+        for (SoundTrigger.KeyphraseRecognitionExtra keyphrase : recognitionEvent.keyphraseExtras) {
+            if (keyphrase.getKeyphraseId() == result.getHotwordPhraseId()) {
+                return;
+            }
+        }
+        throw new SecurityException("Ignoring #onDetected due to trusted service "
+                + "sharing a keyphrase ID which the DSP did not detect");
+    }
+
+    private int getDetectorType() {
+        if (this instanceof DspTrustedHotwordDetectorSession) {
+            return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_DSP;
+        } else if (this instanceof SoftwareTrustedHotwordDetectorSession) {
+            return HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE;
+        }
+        Slog.v(TAG, "Unexpected detector type");
+        return -1;
+    }
+
+    @SuppressWarnings("GuardedBy")
+    public void dumpLocked(String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("mCallback="); pw.println(mCallback);
+        pw.print(prefix); pw.print("mUpdateStateAfterStartFinished=");
+        pw.println(mUpdateStateAfterStartFinished);
+        pw.print(prefix); pw.print("mInitialized="); pw.println(mInitialized);
+        pw.print(prefix); pw.print("mDestroyed="); pw.println(mDestroyed);
+        pw.print(prefix); pw.print("DetectorType=");
+        pw.println(HotwordDetector.detectorTypeToString(getDetectorType()));
+        pw.print(prefix); pw.print("mPerformingExternalSourceHotwordDetection=");
+        pw.println(mPerformingExternalSourceHotwordDetection);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
new file mode 100644
index 0000000..6930689
--- /dev/null
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoftwareTrustedHotwordDetectorSession.java
@@ -0,0 +1,214 @@
+/*
+ * 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.voiceinteraction;
+
+import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
+
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.media.AudioFormat;
+import android.media.permission.Identity;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.service.voice.HotwordDetectedResult;
+import android.service.voice.HotwordDetectionService;
+import android.service.voice.HotwordDetector;
+import android.service.voice.HotwordRejectedResult;
+import android.service.voice.IDspHotwordDetectionCallback;
+import android.service.voice.IHotwordDetectionService;
+import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IHotwordRecognitionStatusCallback;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.concurrent.ScheduledExecutorService;
+
+/**
+ * A class that provides software trusted hotword detector to communicate with the {@link
+ * HotwordDetectionService}.
+ *
+ * This class can handle the hotword detection which detector is created by using
+ * {@link android.service.voice.VoiceInteractionService#createHotwordDetector(PersistableBundle,
+ * SharedMemory, HotwordDetector.Callback)}.
+ */
+final class SoftwareTrustedHotwordDetectorSession extends HotwordDetectorSession {
+    private static final String TAG = "SoftwareTrustedHotwordDetectorSession";
+
+    private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
+    @GuardedBy("mLock")
+    private boolean mPerformingSoftwareHotwordDetection;
+
+    SoftwareTrustedHotwordDetectorSession(
+            @NonNull HotwordDetectionConnection.ServiceConnection remoteHotwordDetectionService,
+            @NonNull Object lock, @NonNull Context context,
+            @NonNull IHotwordRecognitionStatusCallback callback, int voiceInteractionServiceUid,
+            Identity voiceInteractorIdentity,
+            @NonNull ScheduledExecutorService scheduledExecutorService, boolean logging) {
+        super(remoteHotwordDetectionService, lock, context, callback, voiceInteractionServiceUid,
+                voiceInteractorIdentity, scheduledExecutorService, logging);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void startListeningFromMicLocked(
+            AudioFormat audioFormat,
+            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
+        if (DEBUG) {
+            Slog.d(TAG, "startListeningFromMicLocked");
+        }
+        mSoftwareCallback = callback;
+
+        if (mPerformingSoftwareHotwordDetection) {
+            Slog.i(TAG, "Hotword validation is already in progress, ignoring.");
+            return;
+        }
+        mPerformingSoftwareHotwordDetection = true;
+
+        startListeningFromMicLocked();
+    }
+
+    @SuppressWarnings("GuardedBy")
+    private void startListeningFromMicLocked() {
+        // TODO: consider making this a non-anonymous class.
+        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
+            @Override
+            public void onDetected(HotwordDetectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.d(TAG, "onDetected");
+                }
+                synchronized (mLock) {
+                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                            HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
+                    if (!mPerformingSoftwareHotwordDetection) {
+                        Slog.i(TAG, "Hotword detection has already completed");
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
+                        return;
+                    }
+                    mPerformingSoftwareHotwordDetection = false;
+                    try {
+                        enforcePermissionsForDataDelivery();
+                    } catch (SecurityException e) {
+                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    saveProximityValueToBundle(result);
+                    HotwordDetectedResult newResult;
+                    try {
+                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
+                    } catch (IOException e) {
+                        // TODO: Write event
+                        mSoftwareCallback.onError();
+                        return;
+                    }
+                    mSoftwareCallback.onDetected(newResult, null, null);
+                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
+                            + " bits from hotword trusted process");
+                    if (mDebugHotwordLogging) {
+                        Slog.i(TAG, "Egressed detected result: " + newResult);
+                    }
+                }
+            }
+
+            @Override
+            public void onRejected(HotwordRejectedResult result) throws RemoteException {
+                if (DEBUG) {
+                    Slog.wtf(TAG, "onRejected");
+                }
+                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+                        HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
+                // onRejected isn't allowed here, and we are not expecting it.
+            }
+        };
+
+        mRemoteHotwordDetectionService.run(
+                service -> service.detectFromMicrophoneSource(
+                        null,
+                        AUDIO_SOURCE_MICROPHONE,
+                        null,
+                        null,
+                        internalCallback));
+        HotwordMetricsLogger.writeDetectorEvent(
+                HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
+                mVoiceInteractionServiceUid);
+    }
+
+    @SuppressWarnings("GuardedBy")
+    void stopListeningLocked() {
+        if (DEBUG) {
+            Slog.d(TAG, "stopListeningLocked");
+        }
+        if (!mPerformingSoftwareHotwordDetection) {
+            Slog.i(TAG, "Hotword detection is not running");
+            return;
+        }
+        mPerformingSoftwareHotwordDetection = false;
+
+        mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
+
+        closeExternalAudioStreamLocked("stopping requested");
+    }
+
+    @Override
+    @SuppressWarnings("GuardedBy")
+    void informRestartProcessLocked() {
+        // TODO(b/244598068): Check HotwordAudioStreamManager first
+        Slog.v(TAG, "informRestartProcessLocked");
+        mUpdateStateAfterStartFinished.set(false);
+
+        try {
+            mCallback.onProcessRestarted();
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
+            HotwordMetricsLogger.writeDetectorEvent(
+                    HotwordDetector.DETECTOR_TYPE_TRUSTED_HOTWORD_SOFTWARE,
+                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
+                    mVoiceInteractionServiceUid);
+        }
+
+        // Restart listening from microphone if the hotword process has been restarted.
+        if (mPerformingSoftwareHotwordDetection) {
+            Slog.i(TAG, "Process restarted: calling startRecognition() again");
+            startListeningFromMicLocked();
+        }
+
+        mPerformingExternalSourceHotwordDetection = false;
+        closeExternalAudioStreamLocked("process restarted");
+    }
+
+    @SuppressWarnings("GuardedBy")
+    public void dumpLocked(String prefix, PrintWriter pw) {
+        super.dumpLocked(prefix, pw);
+        pw.print(prefix); pw.print("mPerformingSoftwareHotwordDetection=");
+        pw.println(mPerformingSoftwareHotwordDetection);
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
deleted file mode 100644
index 02f5889..0000000
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/TrustedHotwordDetectorSession.java
+++ /dev/null
@@ -1,1325 +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.server.voiceinteraction;
-
-import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD;
-import static android.Manifest.permission.RECORD_AUDIO;
-import static android.service.attention.AttentionService.PROXIMITY_UNKNOWN;
-import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_EXTERNAL;
-import static android.service.voice.HotwordDetectionService.AUDIO_SOURCE_MICROPHONE;
-import static android.service.voice.HotwordDetectionService.ENABLE_PROXIMITY_RESULT;
-import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS;
-import static android.service.voice.HotwordDetectionService.INITIALIZATION_STATUS_UNKNOWN;
-import static android.service.voice.HotwordDetectionService.KEY_INITIALIZATION_STATUS;
-
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
-import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
-import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.attention.AttentionManagerInternal;
-import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.content.PermissionChecker;
-import android.hardware.soundtrigger.IRecognitionStatusCallback;
-import android.hardware.soundtrigger.SoundTrigger;
-import android.media.AudioFormat;
-import android.media.AudioManagerInternal;
-import android.media.permission.Identity;
-import android.media.permission.PermissionUtil;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IRemoteCallback;
-import android.os.ParcelFileDescriptor;
-import android.os.PersistableBundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.SharedMemory;
-import android.provider.DeviceConfig;
-import android.service.voice.HotwordDetectedResult;
-import android.service.voice.HotwordDetectionService;
-import android.service.voice.HotwordDetector;
-import android.service.voice.HotwordRejectedResult;
-import android.service.voice.IDspHotwordDetectionCallback;
-import android.service.voice.IHotwordDetectionService;
-import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
-import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity;
-import android.speech.IRecognitionServiceManager;
-import android.text.TextUtils;
-import android.util.Pair;
-import android.util.Slog;
-import android.view.contentcapture.IContentCaptureManager;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IHotwordRecognitionStatusCallback;
-import com.android.internal.infra.AndroidFuture;
-import com.android.internal.infra.ServiceConnector;
-import com.android.server.LocalServices;
-import com.android.server.pm.permission.PermissionManagerServiceInternal;
-
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
-
-/**
- * A class that provides the communication with the HotwordDetectionService.
- */
-final class TrustedHotwordDetectorSession {
-    private static final String TAG = "TrustedHotwordDetectorSession";
-    static final boolean DEBUG = false;
-
-    private static final String KEY_RESTART_PERIOD_IN_SECONDS = "restart_period_in_seconds";
-    // TODO: These constants need to be refined.
-    // The validation timeout value is 3 seconds for onDetect of DSP trigger event.
-    private static final long VALIDATION_TIMEOUT_MILLIS = 3000;
-    // Write the onDetect timeout metric when it takes more time than MAX_VALIDATION_TIMEOUT_MILLIS.
-    private static final long MAX_VALIDATION_TIMEOUT_MILLIS = 4000;
-    private static final long MAX_UPDATE_TIMEOUT_MILLIS = 30000;
-    private static final long EXTERNAL_HOTWORD_CLEANUP_MILLIS = 2000;
-    private static final Duration MAX_UPDATE_TIMEOUT_DURATION =
-            Duration.ofMillis(MAX_UPDATE_TIMEOUT_MILLIS);
-    private static final long RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS = 60 * 60 * 1000; // 1 hour
-    private static final int MAX_ISOLATED_PROCESS_NUMBER = 10;
-
-    // The error codes are used for onError callback
-    private static final int HOTWORD_DETECTION_SERVICE_DIED = -1;
-    private static final int CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION = -2;
-    private static final int CALLBACK_DETECT_TIMEOUT = -3;
-    private static final int CALLBACK_ONDETECTED_STREAM_COPY_ERROR = -4;
-
-    // Hotword metrics
-    private static final int METRICS_INIT_UNKNOWN_TIMEOUT =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
-    private static final int METRICS_INIT_UNKNOWN_NO_VALUE =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_NO_VALUE;
-    private static final int METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_OVER_MAX_CUSTOM_VALUE;
-    private static final int METRICS_INIT_CALLBACK_STATE_ERROR =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_ERROR;
-    private static final int METRICS_INIT_CALLBACK_STATE_SUCCESS =
-            HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_SUCCESS;
-
-    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_SECURITY_EXCEPTION;
-    private static final int METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_UNEXPECTED_CALLBACK;
-    private static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
-            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
-
-    private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
-    private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
-    private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
-            HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
-    private static final int METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION =
-            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_STATUS_REPORTED_EXCEPTION;
-
-    private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
-    // TODO: This may need to be a Handler(looper)
-    private final ScheduledExecutorService mScheduledExecutorService =
-            Executors.newSingleThreadScheduledExecutor();
-    private final AppOpsManager mAppOpsManager;
-    private final HotwordAudioStreamCopier mHotwordAudioStreamCopier;
-    @Nullable private final ScheduledFuture<?> mCancellationTaskFuture;
-    private final AtomicBoolean mUpdateStateAfterStartFinished = new AtomicBoolean(false);
-    private final IBinder.DeathRecipient mAudioServerDeathRecipient = this::audioServerDied;
-    private final @NonNull ServiceConnectionFactory mServiceConnectionFactory;
-    private final IHotwordRecognitionStatusCallback mCallback;
-    private final int mDetectorType;
-    /**
-     * Time after which each HotwordDetectionService process is stopped and replaced by a new one.
-     * 0 indicates no restarts.
-     */
-    private final int mReStartPeriodSeconds;
-
-    final Object mLock;
-    final int mVoiceInteractionServiceUid;
-    final ComponentName mDetectionComponentName;
-    final int mUser;
-    final Context mContext;
-
-    @Nullable AttentionManagerInternal mAttentionManagerInternal = null;
-
-    final AttentionManagerInternal.ProximityUpdateCallbackInternal mProximityCallbackInternal =
-            this::setProximityValue;
-
-
-    volatile HotwordDetectionServiceIdentity mIdentity;
-    private IMicrophoneHotwordDetectionVoiceInteractionCallback mSoftwareCallback;
-    private Instant mLastRestartInstant;
-
-    private ScheduledFuture<?> mCancellationKeyPhraseDetectionFuture;
-    private ScheduledFuture<?> mDebugHotwordLoggingTimeoutFuture = null;
-
-    /** Identity used for attributing app ops when delivering data to the Interactor. */
-    @GuardedBy("mLock")
-    @Nullable
-    private final Identity mVoiceInteractorIdentity;
-    @GuardedBy("mLock")
-    private ParcelFileDescriptor mCurrentAudioSink;
-    @GuardedBy("mLock")
-    private boolean mValidatingDspTrigger = false;
-    @GuardedBy("mLock")
-    private boolean mPerformingSoftwareHotwordDetection;
-    private @NonNull ServiceConnection mRemoteHotwordDetectionService;
-    private IBinder mAudioFlinger;
-    private boolean mDebugHotwordLogging = false;
-    @GuardedBy("mLock")
-    private double mProximityMeters = PROXIMITY_UNKNOWN;
-
-    TrustedHotwordDetectorSession(Object lock, Context context, int voiceInteractionServiceUid,
-            Identity voiceInteractorIdentity, ComponentName serviceName, int userId,
-            boolean bindInstantServiceAllowed, @Nullable PersistableBundle options,
-            @Nullable SharedMemory sharedMemory,
-            @NonNull IHotwordRecognitionStatusCallback callback, int detectorType) {
-        if (callback == null) {
-            Slog.w(TAG, "Callback is null while creating connection");
-            throw new IllegalArgumentException("Callback is null while creating connection");
-        }
-        mLock = lock;
-        mContext = context;
-        mVoiceInteractionServiceUid = voiceInteractionServiceUid;
-        mVoiceInteractorIdentity = voiceInteractorIdentity;
-        mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
-        mHotwordAudioStreamCopier = new HotwordAudioStreamCopier(mAppOpsManager, detectorType,
-                mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                mVoiceInteractorIdentity.attributionTag);
-        mDetectionComponentName = serviceName;
-        mUser = userId;
-        mCallback = callback;
-        mDetectorType = detectorType;
-        mReStartPeriodSeconds = DeviceConfig.getInt(DeviceConfig.NAMESPACE_VOICE_INTERACTION,
-                KEY_RESTART_PERIOD_IN_SECONDS, 0);
-        final Intent intent = new Intent(HotwordDetectionService.SERVICE_INTERFACE);
-        intent.setComponent(mDetectionComponentName);
-        initAudioFlingerLocked();
-
-        mServiceConnectionFactory = new ServiceConnectionFactory(intent, bindInstantServiceAllowed);
-
-        mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
-        if (ENABLE_PROXIMITY_RESULT) {
-            mAttentionManagerInternal = LocalServices.getService(AttentionManagerInternal.class);
-            if (mAttentionManagerInternal != null) {
-                mAttentionManagerInternal.onStartProximityUpdates(mProximityCallbackInternal);
-            }
-        }
-
-        mLastRestartInstant = Instant.now();
-        updateStateAfterProcessStart(options, sharedMemory);
-
-        if (mReStartPeriodSeconds <= 0) {
-            mCancellationTaskFuture = null;
-        } else {
-            // TODO: we need to be smarter here, e.g. schedule it a bit more often,
-            //  but wait until the current session is closed.
-            mCancellationTaskFuture = mScheduledExecutorService.scheduleAtFixedRate(() -> {
-                Slog.v(TAG, "Time to restart the process, TTL has passed");
-                synchronized (mLock) {
-                    restartProcessLocked();
-                    HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                            HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE);
-                }
-            }, mReStartPeriodSeconds, mReStartPeriodSeconds, TimeUnit.SECONDS);
-        }
-    }
-
-    private void initAudioFlingerLocked() {
-        if (DEBUG) {
-            Slog.d(TAG, "initAudioFlingerLocked");
-        }
-        mAudioFlinger = ServiceManager.waitForService("media.audio_flinger");
-        if (mAudioFlinger == null) {
-            throw new IllegalStateException("Service media.audio_flinger wasn't found.");
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "Obtained audio_flinger binder.");
-        }
-        try {
-            mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Audio server died before we registered a DeathRecipient; retrying init.",
-                    e);
-            initAudioFlingerLocked();
-        }
-    }
-
-    private void audioServerDied() {
-        Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService.");
-        synchronized (mLock) {
-            // TODO: Check if this needs to be scheduled on a different thread.
-            initAudioFlingerLocked();
-            // We restart the process instead of simply sending over the new binder, to avoid race
-            // conditions with audio reading in the service.
-            restartProcessLocked();
-            HotwordMetricsLogger.writeServiceRestartEvent(mDetectorType,
-                    HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED);
-        }
-    }
-
-    private void updateStateAfterProcessStart(
-            PersistableBundle options, SharedMemory sharedMemory) {
-        if (DEBUG) {
-            Slog.d(TAG, "updateStateAfterProcessStart");
-        }
-        mRemoteHotwordDetectionService.postAsync(service -> {
-            AndroidFuture<Void> future = new AndroidFuture<>();
-            IRemoteCallback statusCallback = new IRemoteCallback.Stub() {
-                @Override
-                public void sendResult(Bundle bundle) throws RemoteException {
-                    if (DEBUG) {
-                        Slog.d(TAG, "updateState finish");
-                    }
-                    future.complete(null);
-                    if (mUpdateStateAfterStartFinished.getAndSet(true)) {
-                        Slog.w(TAG, "call callback after timeout");
-                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT,
-                                mVoiceInteractionServiceUid);
-                        return;
-                    }
-                    Pair<Integer, Integer> statusResultPair = getInitStatusAndMetricsResult(bundle);
-                    int status = statusResultPair.first;
-                    int initResultMetricsResult = statusResultPair.second;
-                    try {
-                        mCallback.onStatusReported(status);
-                        HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                                initResultMetricsResult);
-                    } catch (RemoteException e) {
-                        Slog.w(TAG, "Failed to report initialization status: " + e);
-                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                                mVoiceInteractionServiceUid);
-                    }
-                }
-            };
-            try {
-                service.updateState(options, sharedMemory, statusCallback);
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE,
-                        mVoiceInteractionServiceUid);
-            } catch (RemoteException e) {
-                // TODO: (b/181842909) Report an error to voice interactor
-                Slog.w(TAG, "Failed to updateState for HotwordDetectionService", e);
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__CALL_UPDATE_STATE_EXCEPTION,
-                        mVoiceInteractionServiceUid);
-            }
-            return future.orTimeout(MAX_UPDATE_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        }).whenComplete((res, err) -> {
-            if (err instanceof TimeoutException) {
-                Slog.w(TAG, "updateState timed out");
-                if (mUpdateStateAfterStartFinished.getAndSet(true)) {
-                    return;
-                }
-                try {
-                    mCallback.onStatusReported(INITIALIZATION_STATUS_UNKNOWN);
-                    HotwordMetricsLogger.writeServiceInitResultEvent(mDetectorType,
-                            METRICS_INIT_UNKNOWN_TIMEOUT);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to report initialization status UNKNOWN", e);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            METRICS_CALLBACK_ON_STATUS_REPORTED_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
-            } else if (err != null) {
-                Slog.w(TAG, "Failed to update state: " + err);
-            } else {
-                // NOTE: so far we don't need to take any action.
-            }
-        });
-    }
-
-    private static Pair<Integer, Integer> getInitStatusAndMetricsResult(Bundle bundle) {
-        if (bundle == null) {
-            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN, METRICS_INIT_UNKNOWN_NO_VALUE);
-        }
-        int status = bundle.getInt(KEY_INITIALIZATION_STATUS, INITIALIZATION_STATUS_UNKNOWN);
-        if (status > HotwordDetectionService.getMaxCustomInitializationStatus()) {
-            return new Pair<>(INITIALIZATION_STATUS_UNKNOWN,
-                    status == INITIALIZATION_STATUS_UNKNOWN
-                    ? METRICS_INIT_UNKNOWN_NO_VALUE
-                    : METRICS_INIT_UNKNOWN_OVER_MAX_CUSTOM_VALUE);
-        }
-        // TODO: should guard against negative here
-        int metricsResult = status == INITIALIZATION_STATUS_SUCCESS
-                ? METRICS_INIT_CALLBACK_STATE_SUCCESS
-                : METRICS_INIT_CALLBACK_STATE_ERROR;
-        return new Pair<>(status, metricsResult);
-    }
-
-    private boolean isBound() {
-        synchronized (mLock) {
-            return mRemoteHotwordDetectionService.isBound();
-        }
-    }
-
-    void cancelLocked() {
-        Slog.v(TAG, "cancelLocked");
-        clearDebugHotwordLoggingTimeoutLocked();
-        mDebugHotwordLogging = false;
-        mRemoteHotwordDetectionService.unbind();
-        LocalServices.getService(PermissionManagerServiceInternal.class)
-                .setHotwordDetectionServiceProvider(null);
-        if (mIdentity != null) {
-            removeServiceUidForAudioPolicy(mIdentity.getIsolatedUid());
-        }
-        mIdentity = null;
-        if (mCancellationTaskFuture != null) {
-            mCancellationTaskFuture.cancel(/* may interrupt */ true);
-        }
-        if (mAudioFlinger != null) {
-            mAudioFlinger.unlinkToDeath(mAudioServerDeathRecipient, /* flags= */ 0);
-        }
-        if (mAttentionManagerInternal != null) {
-            mAttentionManagerInternal.onStopProximityUpdates(mProximityCallbackInternal);
-        }
-    }
-
-    void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
-                mVoiceInteractionServiceUid);
-
-        // Prevent doing the init late, so restart is handled equally to a clean process start.
-        // TODO(b/191742511): this logic needs a test
-        if (!mUpdateStateAfterStartFinished.get()
-                && Instant.now().minus(MAX_UPDATE_TIMEOUT_DURATION).isBefore(mLastRestartInstant)) {
-            Slog.v(TAG, "call updateStateAfterProcessStart");
-            updateStateAfterProcessStart(options, sharedMemory);
-        } else {
-            mRemoteHotwordDetectionService.run(
-                    service -> service.updateState(options, sharedMemory, null /* callback */));
-        }
-    }
-
-    void startListeningFromMic(
-            AudioFormat audioFormat,
-            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "startListeningFromMic");
-        }
-        mSoftwareCallback = callback;
-
-        synchronized (mLock) {
-            if (mPerformingSoftwareHotwordDetection) {
-                Slog.i(TAG, "Hotword validation is already in progress, ignoring.");
-                return;
-            }
-            mPerformingSoftwareHotwordDetection = true;
-
-            startListeningFromMicLocked();
-        }
-    }
-
-    private void startListeningFromMicLocked() {
-        // TODO: consider making this a non-anonymous class.
-        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
-            @Override
-            public void onDetected(HotwordDetectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onDetected");
-                }
-                synchronized (mLock) {
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
-                    if (!mPerformingSoftwareHotwordDetection) {
-                        Slog.i(TAG, "Hotword detection has already completed");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mPerformingSoftwareHotwordDetection = false;
-                    try {
-                        enforcePermissionsForDataDelivery();
-                    } catch (SecurityException e) {
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
-                        mSoftwareCallback.onError();
-                        return;
-                    }
-                    saveProximityValueToBundle(result);
-                    HotwordDetectedResult newResult;
-                    try {
-                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
-                    } catch (IOException e) {
-                        // TODO: Write event
-                        mSoftwareCallback.onError();
-                        return;
-                    }
-                    mSoftwareCallback.onDetected(newResult, null, null);
-                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
-                            + " bits from hotword trusted process");
-                    if (mDebugHotwordLogging) {
-                        Slog.i(TAG, "Egressed detected result: " + newResult);
-                    }
-                }
-            }
-
-            @Override
-            public void onRejected(HotwordRejectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.wtf(TAG, "onRejected");
-                }
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        mDetectorType,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
-                // onRejected isn't allowed here, and we are not expecting it.
-            }
-        };
-
-        mRemoteHotwordDetectionService.run(
-                service -> service.detectFromMicrophoneSource(
-                        null,
-                        AUDIO_SOURCE_MICROPHONE,
-                        null,
-                        null,
-                        internalCallback));
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
-                mVoiceInteractionServiceUid);
-    }
-
-    public void startListeningFromExternalSource(
-            ParcelFileDescriptor audioStream,
-            AudioFormat audioFormat,
-            @Nullable PersistableBundle options,
-            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "startListeningFromExternalSource");
-        }
-
-        handleExternalSourceHotwordDetection(
-                audioStream,
-                audioFormat,
-                options,
-                callback);
-    }
-
-    void stopListening() {
-        if (DEBUG) {
-            Slog.d(TAG, "stopListening");
-        }
-        synchronized (mLock) {
-            stopListeningLocked();
-        }
-    }
-
-    private void stopListeningLocked() {
-        if (!mPerformingSoftwareHotwordDetection) {
-            Slog.i(TAG, "Hotword detection is not running");
-            return;
-        }
-        mPerformingSoftwareHotwordDetection = false;
-
-        mRemoteHotwordDetectionService.run(IHotwordDetectionService::stopDetection);
-
-        if (mCurrentAudioSink != null) {
-            Slog.i(TAG, "Closing audio stream to hotword detector: stopping requested");
-            bestEffortClose(mCurrentAudioSink);
-        }
-        mCurrentAudioSink = null;
-    }
-
-    void triggerHardwareRecognitionEventForTestLocked(
-            SoundTrigger.KeyphraseRecognitionEvent event,
-            IHotwordRecognitionStatusCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "triggerHardwareRecognitionEventForTestLocked");
-        }
-        detectFromDspSource(event, callback);
-    }
-
-    private void detectFromDspSource(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent,
-            IHotwordRecognitionStatusCallback externalCallback) {
-        if (DEBUG) {
-            Slog.d(TAG, "detectFromDspSource");
-        }
-
-        AtomicBoolean timeoutDetected = new AtomicBoolean(false);
-        // TODO: consider making this a non-anonymous class.
-        IDspHotwordDetectionCallback internalCallback = new IDspHotwordDetectionCallback.Stub() {
-            @Override
-            public void onDetected(HotwordDetectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onDetected");
-                }
-                synchronized (mLock) {
-                    if (mCancellationKeyPhraseDetectionFuture != null) {
-                        mCancellationKeyPhraseDetectionFuture.cancel(true);
-                    }
-                    if (timeoutDetected.get()) {
-                        return;
-                    }
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
-                    if (!mValidatingDspTrigger) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a process restart");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mValidatingDspTrigger = false;
-                    try {
-                        enforcePermissionsForDataDelivery();
-                        enforceExtraKeyphraseIdNotLeaked(result, recognitionEvent);
-                    } catch (SecurityException e) {
-                        Slog.i(TAG, "Ignoring #onDetected due to a SecurityException", e);
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
-                        externalCallback.onError(CALLBACK_ONDETECTED_GOT_SECURITY_EXCEPTION);
-                        return;
-                    }
-                    saveProximityValueToBundle(result);
-                    HotwordDetectedResult newResult;
-                    try {
-                        newResult = mHotwordAudioStreamCopier.startCopyingAudioStreams(result);
-                    } catch (IOException e) {
-                        // TODO: Write event
-                        externalCallback.onError(CALLBACK_ONDETECTED_STREAM_COPY_ERROR);
-                        return;
-                    }
-                    externalCallback.onKeyphraseDetected(recognitionEvent, newResult);
-                    Slog.i(TAG, "Egressed " + HotwordDetectedResult.getUsageSize(newResult)
-                            + " bits from hotword trusted process");
-                    if (mDebugHotwordLogging) {
-                        Slog.i(TAG, "Egressed detected result: " + newResult);
-                    }
-                }
-            }
-
-            @Override
-            public void onRejected(HotwordRejectedResult result) throws RemoteException {
-                if (DEBUG) {
-                    Slog.d(TAG, "onRejected");
-                }
-                synchronized (mLock) {
-                    if (mCancellationKeyPhraseDetectionFuture != null) {
-                        mCancellationKeyPhraseDetectionFuture.cancel(true);
-                    }
-                    if (timeoutDetected.get()) {
-                        return;
-                    }
-                    HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                            mDetectorType,
-                            HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
-                    if (!mValidatingDspTrigger) {
-                        Slog.i(TAG, "Ignoring #onRejected due to a process restart");
-                        HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                mDetectorType,
-                                METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK);
-                        return;
-                    }
-                    mValidatingDspTrigger = false;
-                    externalCallback.onRejected(result);
-                    if (mDebugHotwordLogging && result != null) {
-                        Slog.i(TAG, "Egressed rejected result: " + result);
-                    }
-                }
-            }
-        };
-
-        synchronized (mLock) {
-            mValidatingDspTrigger = true;
-            mRemoteHotwordDetectionService.run(service -> {
-                // We use the VALIDATION_TIMEOUT_MILLIS to inform that the client needs to invoke
-                // the callback before timeout value. In order to reduce the latency impact between
-                // server side and client side, we need to use another timeout value
-                // MAX_VALIDATION_TIMEOUT_MILLIS to monitor it.
-                mCancellationKeyPhraseDetectionFuture = mScheduledExecutorService.schedule(
-                        () -> {
-                            // TODO: avoid allocate every time
-                            timeoutDetected.set(true);
-                            Slog.w(TAG, "Timed out on #detectFromDspSource");
-                            HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                                    mDetectorType,
-                                    HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECT_TIMEOUT);
-                            try {
-                                externalCallback.onError(CALLBACK_DETECT_TIMEOUT);
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "Failed to report onError status: ", e);
-                                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                                        mVoiceInteractionServiceUid);
-                            }
-                        },
-                        MAX_VALIDATION_TIMEOUT_MILLIS,
-                        TimeUnit.MILLISECONDS);
-                service.detectFromDspSource(
-                        recognitionEvent,
-                        recognitionEvent.getCaptureFormat(),
-                        VALIDATION_TIMEOUT_MILLIS,
-                        internalCallback);
-            });
-        }
-    }
-
-    void forceRestart() {
-        Slog.v(TAG, "Requested to restart the service internally. Performing the restart");
-        synchronized (mLock) {
-            restartProcessLocked();
-        }
-    }
-
-    void setDebugHotwordLoggingLocked(boolean logging) {
-        Slog.v(TAG, "setDebugHotwordLoggingLocked: " + logging);
-        clearDebugHotwordLoggingTimeoutLocked();
-        mDebugHotwordLogging = logging;
-
-        if (logging) {
-            // Reset mDebugHotwordLogging to false after one hour
-            mDebugHotwordLoggingTimeoutFuture = mScheduledExecutorService.schedule(() -> {
-                Slog.v(TAG, "Timeout to reset mDebugHotwordLogging to false");
-                synchronized (mLock) {
-                    mDebugHotwordLogging = false;
-                }
-            }, RESET_DEBUG_HOTWORD_LOGGING_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        }
-    }
-
-    private void clearDebugHotwordLoggingTimeoutLocked() {
-        if (mDebugHotwordLoggingTimeoutFuture != null) {
-            mDebugHotwordLoggingTimeoutFuture.cancel(/* mayInterruptIfRunning= */true);
-            mDebugHotwordLoggingTimeoutFuture = null;
-        }
-    }
-
-    private void restartProcessLocked() {
-        // TODO(b/244598068): Check HotwordAudioStreamManager first
-        Slog.v(TAG, "Restarting hotword detection process");
-        ServiceConnection oldConnection = mRemoteHotwordDetectionService;
-        HotwordDetectionServiceIdentity previousIdentity = mIdentity;
-
-        // TODO(volnov): this can be done after connect() has been successful.
-        if (mValidatingDspTrigger) {
-            // We're restarting the process while it's processing a DSP trigger, so report a
-            // rejection. This also allows the Interactor to startReco again
-            try {
-                mCallback.onRejected(new HotwordRejectedResult.Builder().build());
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        mDetectorType,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART);
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to call #rejected");
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_REJECTED_EXCEPTION,
-                        mVoiceInteractionServiceUid);
-            }
-            mValidatingDspTrigger = false;
-        }
-
-        mUpdateStateAfterStartFinished.set(false);
-        mLastRestartInstant = Instant.now();
-
-        // Recreate connection to reset the cache.
-        mRemoteHotwordDetectionService = mServiceConnectionFactory.createLocked();
-
-        Slog.v(TAG, "Started the new process, issuing #onProcessRestarted");
-        try {
-            mCallback.onProcessRestarted();
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to communicate #onProcessRestarted", e);
-            HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                    HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_PROCESS_RESTARTED_EXCEPTION,
-                    mVoiceInteractionServiceUid);
-        }
-
-        // Restart listening from microphone if the hotword process has been restarted.
-        if (mPerformingSoftwareHotwordDetection) {
-            Slog.i(TAG, "Process restarted: calling startRecognition() again");
-            startListeningFromMicLocked();
-        }
-
-        if (mCurrentAudioSink != null) {
-            Slog.i(TAG, "Closing external audio stream to hotword detector: process restarted");
-            bestEffortClose(mCurrentAudioSink);
-            mCurrentAudioSink = null;
-        }
-
-        if (DEBUG) {
-            Slog.i(TAG, "#onProcessRestarted called, unbinding from the old process");
-        }
-        oldConnection.ignoreConnectionStatusEvents();
-        oldConnection.unbind();
-        if (previousIdentity != null) {
-            removeServiceUidForAudioPolicy(previousIdentity.getIsolatedUid());
-        }
-    }
-
-    static final class SoundTriggerCallback extends IRecognitionStatusCallback.Stub {
-        private SoundTrigger.KeyphraseRecognitionEvent mRecognitionEvent;
-        private final HotwordDetectionConnection mHotwordDetectionConnection;
-        private final IHotwordRecognitionStatusCallback mExternalCallback;
-
-        SoundTriggerCallback(IHotwordRecognitionStatusCallback callback,
-                HotwordDetectionConnection connection) {
-            mHotwordDetectionConnection = connection;
-            mExternalCallback = callback;
-        }
-
-        @Override
-        public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent recognitionEvent)
-                throws RemoteException {
-            if (DEBUG) {
-                Slog.d(TAG, "onKeyphraseDetected recognitionEvent : " + recognitionEvent);
-            }
-            final boolean useHotwordDetectionService = mHotwordDetectionConnection != null;
-            if (useHotwordDetectionService) {
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
-                mRecognitionEvent = recognitionEvent;
-                mHotwordDetectionConnection.detectFromDspSource(
-                        recognitionEvent, mExternalCallback);
-            } else {
-                HotwordMetricsLogger.writeKeyphraseTriggerEvent(
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR,
-                        HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__KEYPHRASE_TRIGGER);
-                mExternalCallback.onKeyphraseDetected(recognitionEvent, null);
-            }
-        }
-
-        @Override
-        public void onGenericSoundTriggerDetected(
-                SoundTrigger.GenericRecognitionEvent recognitionEvent)
-                throws RemoteException {
-            mExternalCallback.onGenericSoundTriggerDetected(recognitionEvent);
-        }
-
-        @Override
-        public void onError(int status) throws RemoteException {
-            mExternalCallback.onError(status);
-        }
-
-        @Override
-        public void onRecognitionPaused() throws RemoteException {
-            mExternalCallback.onRecognitionPaused();
-        }
-
-        @Override
-        public void onRecognitionResumed() throws RemoteException {
-            mExternalCallback.onRecognitionResumed();
-        }
-    }
-
-    public void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mReStartPeriodSeconds="); pw.println(mReStartPeriodSeconds);
-        pw.print(prefix);
-        pw.print("mBound=" + mRemoteHotwordDetectionService.isBound());
-        pw.print(", mValidatingDspTrigger=" + mValidatingDspTrigger);
-        pw.print(", mPerformingSoftwareHotwordDetection=" + mPerformingSoftwareHotwordDetection);
-        pw.print(", mRestartCount=" + mServiceConnectionFactory.mRestartCount);
-        pw.print(", mLastRestartInstant=" + mLastRestartInstant);
-        pw.println(", mDetectorType=" + HotwordDetector.detectorTypeToString(mDetectorType));
-    }
-
-    private void handleExternalSourceHotwordDetection(
-            ParcelFileDescriptor audioStream,
-            AudioFormat audioFormat,
-            @Nullable PersistableBundle options,
-            IMicrophoneHotwordDetectionVoiceInteractionCallback callback) {
-        if (DEBUG) {
-            Slog.d(TAG, "#handleExternalSourceHotwordDetection");
-        }
-        InputStream audioSource = new ParcelFileDescriptor.AutoCloseInputStream(audioStream);
-
-        Pair<ParcelFileDescriptor, ParcelFileDescriptor> clientPipe = createPipe();
-        if (clientPipe == null) {
-            // TODO: Need to propagate as unknown error or something?
-            return;
-        }
-        ParcelFileDescriptor serviceAudioSink = clientPipe.second;
-        ParcelFileDescriptor serviceAudioSource = clientPipe.first;
-
-        synchronized (mLock) {
-            mCurrentAudioSink = serviceAudioSink;
-        }
-
-        mAudioCopyExecutor.execute(() -> {
-            try (InputStream source = audioSource;
-                 OutputStream fos =
-                         new ParcelFileDescriptor.AutoCloseOutputStream(serviceAudioSink)) {
-
-                byte[] buffer = new byte[1024];
-                while (true) {
-                    int bytesRead = source.read(buffer, 0, 1024);
-
-                    if (bytesRead < 0) {
-                        Slog.i(TAG, "Reached end of stream for external hotword");
-                        break;
-                    }
-
-                    // TODO: First write to ring buffer to make sure we don't lose data if the next
-                    // statement fails.
-                    // ringBuffer.append(buffer, bytesRead);
-                    fos.write(buffer, 0, bytesRead);
-                }
-            } catch (IOException e) {
-                Slog.w(TAG, "Failed supplying audio data to validator", e);
-
-                try {
-                    callback.onError();
-                } catch (RemoteException ex) {
-                    Slog.w(TAG, "Failed to report onError status: " + ex);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
-            } finally {
-                synchronized (mLock) {
-                    mCurrentAudioSink = null;
-                }
-            }
-        });
-
-        // TODO: handle cancellations well
-        // TODO: what if we cancelled and started a new one?
-        mRemoteHotwordDetectionService.run(
-                service -> {
-                    service.detectFromMicrophoneSource(
-                            serviceAudioSource,
-                            // TODO: consider making a proxy callback + copy of audio format
-                            AUDIO_SOURCE_EXTERNAL,
-                            audioFormat,
-                            options,
-                            new IDspHotwordDetectionCallback.Stub() {
-                                @Override
-                                public void onRejected(HotwordRejectedResult result)
-                                        throws RemoteException {
-                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                            METRICS_EXTERNAL_SOURCE_REJECTED,
-                                            mVoiceInteractionServiceUid);
-                                    mScheduledExecutorService.schedule(
-                                            () -> {
-                                                bestEffortClose(serviceAudioSink, audioSource);
-                                            },
-                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
-                                            TimeUnit.MILLISECONDS);
-
-                                    callback.onRejected(result);
-
-                                    if (result != null) {
-                                        Slog.i(TAG, "Egressed 'hotword rejected result' "
-                                                + "from hotword trusted process");
-                                        if (mDebugHotwordLogging) {
-                                            Slog.i(TAG, "Egressed detected result: " + result);
-                                        }
-                                    }
-                                }
-
-                                @Override
-                                public void onDetected(HotwordDetectedResult triggerResult)
-                                        throws RemoteException {
-                                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                            METRICS_EXTERNAL_SOURCE_DETECTED,
-                                            mVoiceInteractionServiceUid);
-                                    mScheduledExecutorService.schedule(
-                                            () -> {
-                                                bestEffortClose(serviceAudioSink, audioSource);
-                                            },
-                                            EXTERNAL_HOTWORD_CLEANUP_MILLIS,
-                                            TimeUnit.MILLISECONDS);
-
-                                    try {
-                                        enforcePermissionsForDataDelivery();
-                                    } catch (SecurityException e) {
-                                        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                                                METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
-                                                mVoiceInteractionServiceUid);
-                                        callback.onError();
-                                        return;
-                                    }
-                                    HotwordDetectedResult newResult;
-                                    try {
-                                        newResult =
-                                                mHotwordAudioStreamCopier.startCopyingAudioStreams(
-                                                        triggerResult);
-                                    } catch (IOException e) {
-                                        // TODO: Write event
-                                        callback.onError();
-                                        return;
-                                    }
-                                    callback.onDetected(newResult, null /* audioFormat */,
-                                            null /* audioStream */);
-                                    Slog.i(TAG, "Egressed "
-                                            + HotwordDetectedResult.getUsageSize(newResult)
-                                            + " bits from hotword trusted process");
-                                    if (mDebugHotwordLogging) {
-                                        Slog.i(TAG,
-                                                "Egressed detected result: " + newResult);
-                                    }
-                                }
-                            });
-
-                    // A copy of this has been created and passed to the hotword validator
-                    bestEffortClose(serviceAudioSource);
-                });
-        HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
-                mVoiceInteractionServiceUid);
-    }
-
-    private class ServiceConnectionFactory {
-        private final Intent mIntent;
-        private final int mBindingFlags;
-
-        private int mRestartCount = 0;
-
-        ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed) {
-            mIntent = intent;
-            mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0;
-        }
-
-        ServiceConnection createLocked() {
-            ServiceConnection connection =
-                    new ServiceConnection(mContext, mIntent, mBindingFlags, mUser,
-                            IHotwordDetectionService.Stub::asInterface,
-                            mRestartCount++ % MAX_ISOLATED_PROCESS_NUMBER);
-            connection.connect();
-
-            updateAudioFlinger(connection, mAudioFlinger);
-            updateContentCaptureManager(connection);
-            updateSpeechService(connection);
-            updateServiceIdentity(connection);
-            return connection;
-        }
-    }
-
-    private class ServiceConnection extends ServiceConnector.Impl<IHotwordDetectionService> {
-        private final Object mLock = new Object();
-
-        private final Intent mIntent;
-        private final int mBindingFlags;
-        private final int mInstanceNumber;
-
-        private boolean mRespectServiceConnectionStatusChanged = true;
-        private boolean mIsBound = false;
-        private boolean mIsLoggedFirstConnect = false;
-
-        ServiceConnection(@NonNull Context context,
-                @NonNull Intent intent, int bindingFlags, int userId,
-                @Nullable Function<IBinder, IHotwordDetectionService> binderAsInterface,
-                int instanceNumber) {
-            super(context, intent, bindingFlags, userId, binderAsInterface);
-            this.mIntent = intent;
-            this.mBindingFlags = bindingFlags;
-            this.mInstanceNumber = instanceNumber;
-        }
-
-        @Override // from ServiceConnector.Impl
-        protected void onServiceConnectionStatusChanged(IHotwordDetectionService service,
-                boolean connected) {
-            if (DEBUG) {
-                Slog.d(TAG, "onServiceConnectionStatusChanged connected = " + connected);
-            }
-            synchronized (mLock) {
-                if (!mRespectServiceConnectionStatusChanged) {
-                    Slog.v(TAG, "Ignored onServiceConnectionStatusChanged event");
-                    return;
-                }
-                mIsBound = connected;
-
-                if (!connected) {
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
-                            mVoiceInteractionServiceUid);
-                } else if (!mIsLoggedFirstConnect) {
-                    mIsLoggedFirstConnect = true;
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
-                            mVoiceInteractionServiceUid);
-                }
-            }
-        }
-
-        @Override
-        protected long getAutoDisconnectTimeoutMs() {
-            return -1;
-        }
-
-        @Override
-        public void binderDied() {
-            super.binderDied();
-            synchronized (mLock) {
-                if (!mRespectServiceConnectionStatusChanged) {
-                    Slog.v(TAG, "Ignored #binderDied event");
-                    return;
-                }
-
-                Slog.w(TAG, "binderDied");
-                try {
-                    mCallback.onError(HOTWORD_DETECTION_SERVICE_DIED);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to report onError status: " + e);
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_ON_ERROR_EXCEPTION,
-                            mVoiceInteractionServiceUid);
-                }
-            }
-        }
-
-        @Override
-        protected boolean bindService(
-                @NonNull android.content.ServiceConnection serviceConnection) {
-            try {
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE,
-                        mVoiceInteractionServiceUid);
-                boolean bindResult = mContext.bindIsolatedService(
-                        mIntent,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE | mBindingFlags,
-                        "hotword_detector_" + mInstanceNumber,
-                        mExecutor,
-                        serviceConnection);
-                if (!bindResult) {
-                    HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                            HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
-                            mVoiceInteractionServiceUid);
-                }
-                return bindResult;
-            } catch (IllegalArgumentException e) {
-                HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
-                        HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL,
-                        mVoiceInteractionServiceUid);
-                Slog.wtf(TAG, "Can't bind to the hotword detection service!", e);
-                return false;
-            }
-        }
-
-        boolean isBound() {
-            synchronized (mLock) {
-                return mIsBound;
-            }
-        }
-
-        void ignoreConnectionStatusEvents() {
-            synchronized (mLock) {
-                mRespectServiceConnectionStatusChanged = false;
-            }
-        }
-    }
-
-    private static Pair<ParcelFileDescriptor, ParcelFileDescriptor> createPipe() {
-        ParcelFileDescriptor[] fileDescriptors;
-        try {
-            fileDescriptors = ParcelFileDescriptor.createPipe();
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to create audio stream pipe", e);
-            return null;
-        }
-
-        return Pair.create(fileDescriptors[0], fileDescriptors[1]);
-    }
-
-    private static void updateAudioFlinger(ServiceConnection connection, IBinder audioFlinger) {
-        // TODO: Consider using a proxy that limits the exposed API surface.
-        connection.run(service -> service.updateAudioFlinger(audioFlinger));
-    }
-
-    private static void updateContentCaptureManager(ServiceConnection connection) {
-        IBinder b = ServiceManager
-                .getService(Context.CONTENT_CAPTURE_MANAGER_SERVICE);
-        IContentCaptureManager binderService = IContentCaptureManager.Stub.asInterface(b);
-        connection.run(
-                service -> service.updateContentCaptureManager(binderService,
-                        new ContentCaptureOptions(null)));
-    }
-
-    private static void updateSpeechService(ServiceConnection connection) {
-        IBinder b = ServiceManager.getService(Context.SPEECH_RECOGNITION_SERVICE);
-        IRecognitionServiceManager binderService = IRecognitionServiceManager.Stub.asInterface(b);
-        connection.run(service -> {
-            service.updateRecognitionServiceManager(binderService);
-        });
-    }
-
-    private void updateServiceIdentity(ServiceConnection connection) {
-        connection.run(service -> service.ping(new IRemoteCallback.Stub() {
-            @Override
-            public void sendResult(Bundle bundle) throws RemoteException {
-                // TODO: Exit if the service has been unbound already (though there's a very low
-                // chance this happens).
-                if (DEBUG) {
-                    Slog.d(TAG, "updating hotword UID " + Binder.getCallingUid());
-                }
-                // TODO: Have the provider point to the current state stored in
-                // VoiceInteractionManagerServiceImpl.
-                final int uid = Binder.getCallingUid();
-                LocalServices.getService(PermissionManagerServiceInternal.class)
-                        .setHotwordDetectionServiceProvider(() -> uid);
-                mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
-                addServiceUidForAudioPolicy(uid);
-            }
-        }));
-    }
-
-    private void addServiceUidForAudioPolicy(int uid) {
-        mScheduledExecutorService.execute(() -> {
-            AudioManagerInternal audioManager =
-                    LocalServices.getService(AudioManagerInternal.class);
-            if (audioManager != null) {
-                audioManager.addAssistantServiceUid(uid);
-            }
-        });
-    }
-
-    private void removeServiceUidForAudioPolicy(int uid) {
-        mScheduledExecutorService.execute(() -> {
-            AudioManagerInternal audioManager =
-                    LocalServices.getService(AudioManagerInternal.class);
-            if (audioManager != null) {
-                audioManager.removeAssistantServiceUid(uid);
-            }
-        });
-    }
-
-    private void saveProximityValueToBundle(HotwordDetectedResult result) {
-        synchronized (mLock) {
-            if (result != null && mProximityMeters != PROXIMITY_UNKNOWN) {
-                result.setProximity(mProximityMeters);
-            }
-        }
-    }
-
-    private void setProximityValue(double proximityMeters) {
-        synchronized (mLock) {
-            mProximityMeters = proximityMeters;
-        }
-    }
-
-    private static void bestEffortClose(Closeable... closeables) {
-        for (Closeable closeable : closeables) {
-            bestEffortClose(closeable);
-        }
-    }
-
-    private static void bestEffortClose(Closeable closeable) {
-        try {
-            closeable.close();
-        } catch (IOException e) {
-            if (DEBUG) {
-                Slog.w(TAG, "Failed closing", e);
-            }
-        }
-    }
-
-    // TODO: Share this code with SoundTriggerMiddlewarePermission.
-    private void enforcePermissionsForDataDelivery() {
-        Binder.withCleanCallingIdentity(() -> {
-            enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
-            int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
-            mAppOpsManager.noteOpNoThrow(hotwordOp,
-                    mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
-                    mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
-            enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
-                    CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
-        });
-    }
-
-    /**
-     * Throws a {@link SecurityException} iff the given identity has given permission to receive
-     * data.
-     *
-     * @param context    A {@link Context}, used for permission checks.
-     * @param identity   The identity to check.
-     * @param permission The identifier of the permission we want to check.
-     * @param reason     The reason why we're requesting the permission, for auditing purposes.
-     */
-    private static void enforcePermissionForDataDelivery(@NonNull Context context,
-            @NonNull Identity identity,
-            @NonNull String permission, @NonNull String reason) {
-        final int status = PermissionUtil.checkPermissionForDataDelivery(context, identity,
-                permission, reason);
-        if (status != PermissionChecker.PERMISSION_GRANTED) {
-            throw new SecurityException(
-                    TextUtils.formatSimple("Failed to obtain permission %s for identity %s",
-                            permission,
-                            SoundTriggerSessionPermissionsDecorator.toString(identity)));
-        }
-    }
-
-    private static void enforceExtraKeyphraseIdNotLeaked(HotwordDetectedResult result,
-            SoundTrigger.KeyphraseRecognitionEvent recognitionEvent) {
-        // verify the phrase ID in HotwordDetectedResult is not exposing extra phrases
-        // the DSP did not detect
-        for (SoundTrigger.KeyphraseRecognitionExtra keyphrase : recognitionEvent.keyphraseExtras) {
-            if (keyphrase.getKeyphraseId() == result.getHotwordPhraseId()) {
-                return;
-            }
-        }
-        throw new SecurityException("Ignoring #onDetected due to trusted service "
-                + "sharing a keyphrase ID which the DSP did not detect");
-    }
-
-    private static final String OP_MESSAGE =
-            "Providing hotword detection result to VoiceInteractionService";
-}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 1ba997f..fdf69430 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -743,11 +743,23 @@
 
     /**
      * Given a list of permissions, check to see if the caller has at least one of them granted. If
-     * not, check to see if the caller has carrier privileges. If the caller does not have any  of
+     * not, check to see if the caller has carrier privileges. If the caller does not have any of
      * these permissions, throw a SecurityException.
      */
     public static void enforceAnyPermissionGrantedOrCarrierPrivileges(Context context, int subId,
             int uid, String message, String... permissions) {
+        enforceAnyPermissionGrantedOrCarrierPrivileges(
+                context, subId, uid, false, message, permissions);
+    }
+
+    /**
+     * Given a list of permissions, check to see if the caller has at least one of them granted. If
+     * not, check to see if the caller has carrier privileges on the specified subscription (or any
+     * subscription if {@code allowCarrierPrivilegeOnAnySub} is {@code true}. If the caller does not
+     * have any of these permissions, throw a {@link SecurityException}.
+     */
+    public static void enforceAnyPermissionGrantedOrCarrierPrivileges(Context context, int subId,
+            int uid, boolean allowCarrierPrivilegeOnAnySub, String message, String... permissions) {
         if (permissions.length == 0) return;
         boolean isGranted = false;
         for (String perm : permissions) {
@@ -758,7 +770,12 @@
         }
 
         if (isGranted) return;
-        if (checkCarrierPrivilegeForSubId(context, subId)) return;
+
+        if (allowCarrierPrivilegeOnAnySub) {
+            if (checkCarrierPrivilegeForAnySubId(context, Binder.getCallingUid())) return;
+        } else {
+            if (checkCarrierPrivilegeForSubId(context, subId)) return;
+        }
 
         StringBuilder b = new StringBuilder(message);
         b.append(": Neither user ");
@@ -769,7 +786,8 @@
             b.append(" or ");
             b.append(permissions[i]);
         }
-        b.append(" or carrier privileges");
+        b.append(" or carrier privileges. subId=" + subId + ", allowCarrierPrivilegeOnAnySub="
+                + allowCarrierPrivilegeOnAnySub);
         throw new SecurityException(b.toString());
     }
 
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index 297940e..03519a3 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -155,6 +155,16 @@
      */
     private int mParametersUseForLevel;
 
+    /**
+     * Timing advance value for a one way trip from cell to device in microseconds.
+     * Approximate distance is calculated using 300m/us * timingAdvance.
+     *
+     * Reference: 3GPP TS 36.213 section 4.2.3.
+     *
+     * Range: [0, 1282]
+     */
+    private int mTimingAdvance;
+
     /** @hide */
     public CellSignalStrengthNr() {
         setDefaultValues();
@@ -169,10 +179,11 @@
      * @param ssRsrp SS reference signal received power.
      * @param ssRsrq SS reference signal received quality.
      * @param ssSinr SS signal-to-noise and interference ratio.
+     * @param timingAdvance Timing advance.
      * @hide
      */
     public CellSignalStrengthNr(int csiRsrp, int csiRsrq, int csiSinr, int csiCqiTableIndex,
-            List<Byte> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr) {
+            List<Byte> csiCqiReport, int ssRsrp, int ssRsrq, int ssSinr, int timingAdvance) {
         mCsiRsrp = inRangeOrUnavailable(csiRsrp, -156, -31);
         mCsiRsrq = inRangeOrUnavailable(csiRsrq, -20, -3);
         mCsiSinr = inRangeOrUnavailable(csiSinr, -23, 23);
@@ -183,6 +194,7 @@
         mSsRsrp = inRangeOrUnavailable(ssRsrp, -156, -31);
         mSsRsrq = inRangeOrUnavailable(ssRsrq, -43, 20);
         mSsSinr = inRangeOrUnavailable(ssSinr, -23, 40);
+        mTimingAdvance = inRangeOrUnavailable(timingAdvance, 0, 1282);
         updateLevel(null, null);
     }
 
@@ -198,7 +210,7 @@
     public CellSignalStrengthNr(
             int csiRsrp, int csiRsrq, int csiSinr, int ssRsrp, int ssRsrq, int ssSinr) {
         this(csiRsrp, csiRsrq, csiSinr, CellInfo.UNAVAILABLE, Collections.emptyList(),
-                ssRsrp, ssRsrq, ssSinr);
+                ssRsrp, ssRsrq, ssSinr, CellInfo.UNAVAILABLE);
     }
 
     /**
@@ -302,6 +314,22 @@
         return mCsiCqiReport;
     }
 
+    /**
+     * Get the timing advance value for a one way trip from cell to device for NR in microseconds.
+     * {@link android.telephony.CellInfo#UNAVAILABLE} is reported when there is no
+     * active RRC connection.
+     *
+     * Reference: 3GPP TS 36.213 section 4.2.3.
+     * Range: 0 us to 1282 us.
+     *
+     * @return the NR timing advance if available or
+     *         {@link android.telephony.CellInfo#UNAVAILABLE} if unavailable.
+     */
+    @IntRange(from = 0, to = 1282)
+    public int getTimingAdvanceMicros() {
+        return mTimingAdvance;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -319,6 +347,7 @@
         dest.writeInt(mSsRsrq);
         dest.writeInt(mSsSinr);
         dest.writeInt(mLevel);
+        dest.writeInt(mTimingAdvance);
     }
 
     private CellSignalStrengthNr(Parcel in) {
@@ -331,6 +360,7 @@
         mSsRsrq = in.readInt();
         mSsSinr = in.readInt();
         mLevel = in.readInt();
+        mTimingAdvance = in.readInt();
     }
 
     /** @hide */
@@ -346,6 +376,7 @@
         mSsSinr = CellInfo.UNAVAILABLE;
         mLevel = SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
         mParametersUseForLevel = USE_SSRSRP;
+        mTimingAdvance = CellInfo.UNAVAILABLE;
     }
 
     /** {@inheritDoc} */
@@ -495,6 +526,7 @@
         mSsSinr = s.mSsSinr;
         mLevel = s.mLevel;
         mParametersUseForLevel = s.mParametersUseForLevel;
+        mTimingAdvance = s.mTimingAdvance;
     }
 
     /** @hide */
@@ -506,7 +538,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mCsiRsrp, mCsiRsrq, mCsiSinr, mCsiCqiTableIndex,
-                mCsiCqiReport, mSsRsrp, mSsRsrq, mSsSinr, mLevel);
+                mCsiCqiReport, mSsRsrp, mSsRsrq, mSsSinr, mLevel, mTimingAdvance);
     }
 
     private static final CellSignalStrengthNr sInvalid = new CellSignalStrengthNr();
@@ -525,7 +557,7 @@
                     && mCsiCqiTableIndex == o.mCsiCqiTableIndex
                     && mCsiCqiReport.equals(o.mCsiCqiReport)
                     && mSsRsrp == o.mSsRsrp && mSsRsrq == o.mSsRsrq && mSsSinr == o.mSsSinr
-                    && mLevel == o.mLevel;
+                    && mLevel == o.mLevel && mTimingAdvance == o.mTimingAdvance;
         }
         return false;
     }
@@ -543,6 +575,7 @@
                 .append(" ssSinr = " + mSsSinr)
                 .append(" level = " + mLevel)
                 .append(" parametersUseForLevel = " + mParametersUseForLevel)
+                .append(" timingAdvance = " + mTimingAdvance)
                 .append(" }")
                 .toString();
     }
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 6be2f77..7c600b1 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -138,6 +138,12 @@
      */
     public static final int FREQUENCY_RANGE_MMWAVE = 4;
 
+    /**
+     * Number of frequency ranges.
+     * @hide
+     */
+    public static final int FREQUENCY_RANGE_COUNT = 5;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "DUPLEX_MODE_",
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index db9dfbb..3b84b65 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -649,6 +649,15 @@
     }
 
     /**
+     * @return {@code true} if the subscription is from the actively used SIM.
+     *
+     * @hide
+     */
+    public boolean isActive() {
+        return mSimSlotIndex >= 0 || mType == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM;
+    }
+
+    /**
      * Used in scenarios where different subscriptions are bundled as a group.
      * It's typically a primary and an opportunistic subscription. (see {@link #isOpportunistic()})
      * Such that those subscriptions will have some affiliated behaviors such as opportunistic
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 794fe72..9b566fb 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -754,6 +754,15 @@
     /** Indicates that data roaming is disabled for a subscription */
     public static final int DATA_ROAMING_DISABLE = SimInfo.DATA_ROAMING_DISABLE;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"DATA_ROAMING_"},
+            value = {
+                    DATA_ROAMING_ENABLE,
+                    DATA_ROAMING_DISABLE
+            })
+    public @interface DataRoamingMode {}
+
     /**
      * TelephonyProvider column name for subscription carrier id.
      * @see TelephonyManager#getSimCarrierId()
@@ -1766,8 +1775,7 @@
      *
      * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      * or that the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
-     * to the calling app are returned.
+     * {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @return Sorted list of the currently {@link SubscriptionInfo} records available on the device.
      * <ul>
@@ -1785,7 +1793,6 @@
      * </li>
      * </ul>
      */
-    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public List<SubscriptionInfo> getActiveSubscriptionInfoList() {
         return getActiveSubscriptionInfoList(/* userVisibleonly */true);
@@ -1989,17 +1996,12 @@
     }
 
     /**
+     * Get the active subscription count.
      *
-     * Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
-     * or that the calling app has carrier privileges (see
-     * {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, the count will include
-     * only those subscriptions accessible to the caller.
+     * @return The current number of active subscriptions.
      *
-     * @return the current number of active subscriptions. There is no guarantee the value
-     * returned by this method will be the same as the length of the list returned by
-     * {@link #getActiveSubscriptionInfoList}.
+     * @see #getActiveSubscriptionInfoList()
      */
-    @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public int getActiveSubscriptionInfoCount() {
         int result = 0;
@@ -2139,7 +2141,7 @@
     /**
      * Set SIM icon tint color for subscription ID
      * @param tint the RGB value of icon tint color of the SIM
-     * @param subId the unique Subscritpion ID in database
+     * @param subId the unique subscription ID in database
      * @return the number of records updated
      * @hide
      */
@@ -2147,7 +2149,7 @@
     public int setIconTint(@ColorInt int tint, int subId) {
         if (VDBG) logd("[setIconTint]+ tint:" + tint + " subId:" + subId);
         return setSubscriptionPropertyHelper(subId, "setIconTint",
-                (iSub)-> iSub.setIconTint(tint, subId)
+                (iSub)-> iSub.setIconTint(subId, tint)
         );
     }
 
@@ -2612,37 +2614,6 @@
     }
 
     /**
-     * Returns a constant indicating the state of sim for the slot index.
-     *
-     * @param slotIndex
-     *
-     * {@See TelephonyManager#SIM_STATE_UNKNOWN}
-     * {@See TelephonyManager#SIM_STATE_ABSENT}
-     * {@See TelephonyManager#SIM_STATE_PIN_REQUIRED}
-     * {@See TelephonyManager#SIM_STATE_PUK_REQUIRED}
-     * {@See TelephonyManager#SIM_STATE_NETWORK_LOCKED}
-     * {@See TelephonyManager#SIM_STATE_READY}
-     * {@See TelephonyManager#SIM_STATE_NOT_READY}
-     * {@See TelephonyManager#SIM_STATE_PERM_DISABLED}
-     * {@See TelephonyManager#SIM_STATE_CARD_IO_ERROR}
-     *
-     * {@hide}
-     */
-    public static int getSimStateForSlotIndex(int slotIndex) {
-        int simState = TelephonyManager.SIM_STATE_UNKNOWN;
-
-        try {
-            ISub iSub = TelephonyManager.getSubscriptionService();
-            if (iSub != null) {
-                simState = iSub.getSimStateForSlotIndex(slotIndex);
-            }
-        } catch (RemoteException ex) {
-        }
-
-        return simState;
-    }
-
-    /**
      * Store properties associated with SubscriptionInfo in database
      * @param subId Subscription Id of Subscription
      * @param propKey Column name in database associated with SubscriptionInfo
@@ -3733,10 +3704,15 @@
     }
 
     /**
-     * DO NOT USE.
-     * This API is designed for features that are not finished at this point. Do not call this API.
+     * Check if a subscription is active.
+     *
+     * @param subscriptionId The subscription id to check.
+     *
+     * @return {@code true} if the subscription is active.
+     *
+     * @throws IllegalArgumentException if the provided slot index is invalid.
+     *
      * @hide
-     * TODO b/135547512: further clean up
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -3757,8 +3733,12 @@
      * Set the device to device status sharing user preference for a subscription ID. The setting
      * app uses this method to indicate with whom they wish to share device to device status
      * information.
-     * @param sharing the status sharing preference
-     * @param subscriptionId the unique Subscription ID in database
+     *
+     * @param subscriptionId the unique Subscription ID in database.
+     * @param sharing the status sharing preference.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist, or the sharing
+     * preference is invalid.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingPreference(int subscriptionId,
@@ -3789,8 +3769,12 @@
      * Set the list of contacts that allow device to device status sharing for a subscription ID.
      * The setting app uses this method to indicate with whom they wish to share device to device
      * status information.
-     * @param contacts The list of contacts that allow device to device status sharing
-     * @param subscriptionId The unique Subscription ID in database
+     *
+     * @param subscriptionId The unique Subscription ID in database.
+     * @param contacts The list of contacts that allow device to device status sharing.
+     *
+     * @throws IllegalArgumentException if the subscription does not exist, or contacts is
+     * {@code null}.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingContacts(int subscriptionId,
@@ -3820,16 +3804,24 @@
     }
 
     /**
-     * DO NOT USE.
-     * This API is designed for features that are not finished at this point. Do not call this API.
+     * Get the active subscription id by logical SIM slot index.
+     *
+     * @param slotIndex The logical SIM slot index.
+     * @return The active subscription id.
+     *
+     * @throws IllegalArgumentException if the provided slot index is invalid.
+     *
      * @hide
-     * TODO b/135547512: further clean up
      */
     @SystemApi
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public int getEnabledSubscriptionId(int slotIndex) {
         int subId = INVALID_SUBSCRIPTION_ID;
 
+        if (!isValidSlotIndex(slotIndex)) {
+            throw new IllegalArgumentException("Invalid slot index " + slotIndex);
+        }
+
         try {
             ISub iSub = TelephonyManager.getSubscriptionService();
             if (iSub != null) {
@@ -3871,12 +3863,12 @@
     /**
      * Get active data subscription id. Active data subscription refers to the subscription
      * currently chosen to provide cellular internet connection to the user. This may be
-     * different from getDefaultDataSubscriptionId(). Eg. Opportunistics data
+     * different from getDefaultDataSubscriptionId().
      *
-     * See {@link PhoneStateListener#onActiveDataSubscriptionIdChanged(int)} for the details.
+     * @return Active data subscription id if any is chosen, or {@link #INVALID_SUBSCRIPTION_ID} if
+     * not.
      *
-     * @return Active data subscription id if any is chosen, or
-     * SubscriptionManager.INVALID_SUBSCRIPTION_ID if not.
+     * @see TelephonyCallback.ActiveDataSubscriptionIdListener
      */
     public static int getActiveDataSubscriptionId() {
         if (isSubscriptionManagerServiceEnabled()) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 3024b89..851697a 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -71,7 +71,6 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.SystemProperties;
-import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.carrier.CarrierIdentifier;
@@ -127,7 +126,6 @@
 import com.android.internal.telephony.OperatorInfo;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.RILConstants;
-import com.android.internal.telephony.SmsApplication;
 import com.android.telephony.Rlog;
 
 import java.io.IOException;
@@ -3558,7 +3556,7 @@
                     "state as absent");
             return SIM_STATE_ABSENT;
         }
-        return SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+        return getSimStateForSlotIndex(slotIndex);
     }
 
     /**
@@ -3705,9 +3703,7 @@
     @Deprecated
     public @SimState int getSimApplicationState(int physicalSlotIndex) {
         int activePort = getFirstActivePortIndex(physicalSlotIndex);
-        int simState =
-                SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex,
-                        activePort));
+        int simState = getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex, activePort));
         return getSimApplicationStateFromSimState(simState);
     }
 
@@ -3733,9 +3729,7 @@
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @SimState int getSimApplicationState(int physicalSlotIndex, int portIndex) {
-        int simState =
-                SubscriptionManager.getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex,
-                        portIndex));
+        int simState = getSimStateForSlotIndex(getLogicalSlotIndex(physicalSlotIndex, portIndex));
         return getSimApplicationStateFromSimState(simState);
     }
 
@@ -3804,7 +3798,7 @@
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
     public @SimState int getSimState(int slotIndex) {
-        int simState = SubscriptionManager.getSimStateForSlotIndex(slotIndex);
+        int simState = getSimStateForSlotIndex(slotIndex);
         if (simState == SIM_STATE_LOADED) {
             simState = SIM_STATE_READY;
         }
@@ -17747,4 +17741,30 @@
         }
         return null;
     }
+
+    /**
+     * Returns a constant indicating the state of sim for the slot index.
+     *
+     * @param slotIndex Logical SIM slot index.
+     *
+     * @see TelephonyManager.SimState
+     *
+     * @hide
+     */
+    @SimState
+    public static int getSimStateForSlotIndex(int slotIndex) {
+        try {
+            ITelephony telephony = ITelephony.Stub.asInterface(
+                    TelephonyFrameworkInitializer
+                            .getTelephonyServiceManager()
+                            .getTelephonyServiceRegisterer()
+                            .get());
+            if (telephony != null) {
+                return telephony.getSimStateForSlotIndex(slotIndex);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error in getSimStateForSlotIndex: " + e);
+        }
+        return TelephonyManager.SIM_STATE_UNKNOWN;
+    }
 }
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index fa3f15d..554beb9 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1286,7 +1286,7 @@
                 && xorEqualsInt(this.mMmsProxyPort, other.mMmsProxyPort))
                 && xorEqualsString(this.mUser, other.mUser)
                 && xorEqualsString(this.mPassword, other.mPassword)
-                && xorEqualsInt(this.mAuthType, other.mAuthType)
+                && Objects.equals(this.mAuthType, other.mAuthType)
                 && !typeSameAny(this, other)
                 && Objects.equals(this.mOperatorNumeric, other.mOperatorNumeric)
                 && Objects.equals(this.mProtocol, other.mProtocol)
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
index 4fd9040..219c9c8 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRegistration.aidl
@@ -31,4 +31,5 @@
    oneway void triggerFullNetworkRegistration(int sipCode, String sipReason);
    oneway void triggerUpdateSipDelegateRegistration();
    oneway void triggerSipDelegateDeregistration();
+   oneway void triggerDeregistration(int reason);
 }
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index d776928..8184424 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -700,6 +700,7 @@
             throw new IllegalStateException("Session is not available.");
         }
         try {
+            c.setDefaultExecutor(MmTelFeature.this.mExecutor);
             listener.onIncomingCall(c.getServiceImpl(), extras);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
diff --git a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
index 6fc1cc8..b69eb6f 100644
--- a/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsRegistrationImplBase.java
@@ -104,6 +104,73 @@
     // yet.
     private static final int REGISTRATION_STATE_UNKNOWN = -1;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(
+        prefix = {"REASON_"},
+        value = {
+            REASON_UNKNOWN,
+            REASON_SIM_REMOVED,
+            REASON_SIM_REFRESH,
+            REASON_ALLOWED_NETWORK_TYPES_CHANGED,
+            REASON_NON_IMS_CAPABLE_NETWORK,
+            REASON_RADIO_POWER_OFF,
+            REASON_HANDOVER_FAILED,
+            REASON_VOPS_NOT_SUPPORTED,
+        })
+    public @interface ImsDeregistrationReason{}
+
+    /**
+     * Unspecified reason.
+     * @hide
+     */
+    public static final int REASON_UNKNOWN = 0;
+
+    /**
+     * Since SIM is removed, the credentials for IMS service is also removed.
+     * @hide
+     */
+    public static final int REASON_SIM_REMOVED = 1;
+
+    /**
+     * Detach from the network shall be performed due to the SIM refresh. IMS service should be
+     * deregistered before that procedure.
+     * @hide
+     */
+    public static final int REASON_SIM_REFRESH = 2;
+
+    /**
+     * The allowed network types have changed, resulting in a network type
+     * that does not support IMS.
+     * @hide
+     */
+    public static final int REASON_ALLOWED_NETWORK_TYPES_CHANGED = 3;
+
+   /**
+     * The device camped on a network that does not support IMS.
+     * @hide
+     */
+    public static final int REASON_NON_IMS_CAPABLE_NETWORK = 4;
+
+    /**
+     * IMS service should be deregistered from the network before turning off the radio.
+     * @hide
+     */
+    public static final int REASON_RADIO_POWER_OFF = 5;
+
+    /**
+     * Since the handover is failed or not allowed, the data service for IMS shall be
+     * disconnected.
+     * @hide
+     */
+    public static final int REASON_HANDOVER_FAILED = 6;
+
+    /**
+     * The network is changed to a network that does not support voice over IMS.
+     * @hide
+     */
+    public static final int REASON_VOPS_NOT_SUPPORTED = 7;
+
     private Executor mExecutor;
 
     /**
@@ -182,6 +249,12 @@
                     .triggerSipDelegateDeregistration(), "triggerSipDelegateDeregistration");
         }
 
+        @Override
+        public void triggerDeregistration(@ImsDeregistrationReason int reason) {
+            executeMethodAsyncNoException(() -> ImsRegistrationImplBase.this
+                    .triggerDeregistration(reason), "triggerDeregistration");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -303,6 +376,19 @@
         // Stub implementation, ImsService should implement this
     }
 
+    /**
+     * Requests IMS stack to perform graceful IMS deregistration before radio performing
+     * network detach in the events of SIM remove, refresh or and so on. The radio waits for
+     * the IMS deregistration, which will be notified by telephony via
+     * {@link android.hardware.radio.ims.IRadioIms#updateImsRegistrationInfo()},
+     * or a certain timeout interval to start the network detach procedure.
+     *
+     * @param reason the reason why the deregistration is triggered.
+     * @hide
+     */
+    public void triggerDeregistration(@ImsDeregistrationReason int reason) {
+        // Stub Implementation, can be overridden by ImsService
+    }
 
     /**
      * Notify the framework that the device is connected to the IMS network.
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 5173405..280d259 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -135,11 +135,11 @@
 
     /**
      * Set SIM icon tint color by simInfo index
-     * @param tint the icon tint color of the SIM
      * @param subId the unique SubscriptionInfo index in database
+     * @param tint the icon tint color of the SIM
      * @return the number of records updated
      */
-    int setIconTint(int tint, int subId);
+    int setIconTint(int subId, int tint);
 
     /**
      * Set display name by simInfo index with name source
@@ -274,11 +274,6 @@
     boolean isSubscriptionEnabled(int subId);
 
     int getEnabledSubscriptionId(int slotIndex);
-    /**
-     * Get the SIM state for the slot index
-     * @return SIM state as the ordinal of IccCardConstants.State
-     */
-    int getSimStateForSlotIndex(int slotIndex);
 
     boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId);
 
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 616ea50..7ede4ab 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2628,5 +2628,12 @@
       * {@code null} if the functionality is not supported.
       * @hide
       */
-      ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
+    ComponentName getDefaultRespondViaMessageApplication(int subId, boolean updateIfNeeded);
+
+    /**
+     * Get the SIM state for the logical SIM slot index.
+     *
+     * @param slotIndex Logical SIM slot index.
+     */
+    int getSimStateForSlotIndex(int slotIndex);
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index c100a9c..a4609f7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -174,14 +174,18 @@
 
     open fun cujCompleted() {
         entireScreenCovered()
-        navBarLayerIsVisibleAtStartAndEnd()
-        navBarWindowIsAlwaysVisible()
-        taskBarLayerIsVisibleAtStartAndEnd()
-        taskBarWindowIsAlwaysVisible()
         statusBarLayerIsVisibleAtStartAndEnd()
         statusBarLayerPositionAtStartAndEnd()
         statusBarWindowIsAlwaysVisible()
         visibleLayersShownMoreThanOneConsecutiveEntry()
         visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+        if (flicker.scenario.isTablet) {
+            taskBarLayerIsVisibleAtStartAndEnd()
+            taskBarWindowIsAlwaysVisible()
+        } else {
+            navBarLayerIsVisibleAtStartAndEnd()
+            navBarWindowIsAlwaysVisible()
+        }
     }
 }
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index e6b60cf..167d560 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -88,46 +88,7 @@
     }
 
     private static String insetsTypesToString(int types) {
-        if (types == 0) {
-            return "none";
-        }
-        final StringBuilder sb = new StringBuilder();
-        if ((types & Type.statusBars()) != 0) {
-            types &= ~Type.statusBars();
-            sb.append("statusBars ");
-        }
-        if ((types & Type.navigationBars()) != 0) {
-            types &= ~Type.navigationBars();
-            sb.append("navigationBars ");
-        }
-        if ((types & Type.captionBar()) != 0) {
-            types &= ~Type.captionBar();
-            sb.append("captionBar ");
-        }
-        if ((types & Type.ime()) != 0) {
-            types &= ~Type.ime();
-            sb.append("ime ");
-        }
-        if ((types & Type.systemGestures()) != 0) {
-            types &= ~Type.systemGestures();
-            sb.append("systemGestures ");
-        }
-        if ((types & Type.mandatorySystemGestures()) != 0) {
-            types &= ~Type.mandatorySystemGestures();
-            sb.append("mandatorySystemGestures ");
-        }
-        if ((types & Type.tappableElement()) != 0) {
-            types &= ~Type.tappableElement();
-            sb.append("tappableElement ");
-        }
-        if ((types & Type.displayCutout()) != 0) {
-            types &= ~Type.displayCutout();
-            sb.append("displayCutout ");
-        }
-        if (types != 0) {
-            sb.append("unknownTypes:").append(types);
-        }
-        return sb.toString();
+        return types == 0 ? "none" : WindowInsets.Type.toString(types);
     }
 
     @Override
diff --git a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
index 133c176..cc3781a 100644
--- a/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
+++ b/tests/utils/testutils/java/com/android/internal/util/test/BroadcastInterceptingContext.java
@@ -246,6 +246,12 @@
     }
 
     @Override
+    public void sendBroadcastAsUser(Intent intent, UserHandle user,
+            String receiverPermission, Bundle options) {
+        sendBroadcast(intent);
+    }
+
+    @Override
     public void sendStickyBroadcast(Intent intent) {
         sendBroadcast(intent);
     }
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index bba819c..bcef917 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -114,6 +114,13 @@
         val overridingName = "${overridingClass.name}.${overridingMethod.name}"
         val overriddenName = "${overriddenClass.name}.${overriddenMethod.name}"
         if (overridingAnnotation == null) {
+            if (shouldIgnoreGeneratedMethod(
+                            context,
+                            overriddenClass = overriddenClass,
+                            overridingClass = overridingClass)
+            ) {
+                return
+            }
             val msg = "The method $overridingName overrides the method $overriddenName which " +
                 "is annotated with @EnforcePermission. The same annotation must be used " +
                 "on $overridingName"
@@ -215,6 +222,39 @@
         }
     }
 
+    /**
+     * since this lint runs globally, it will also run against generated
+     * test code e.g.
+     * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtected.java
+     * system/tools/aidl/tests/golden_output/aidl-test-interface-permission-java-source/gen/android/aidl/tests/permission/IProtectedInterface.java
+     * we do not want to report errors against generated `Stub` and `Proxy` classes in those files
+     */
+    private fun shouldIgnoreGeneratedMethod(
+            context: JavaContext,
+            overriddenClass: PsiClass,
+            overridingClass: PsiClass,
+
+    ): Boolean {
+        if (isInterfaceAndExtendsIInterface(overriddenClass) &&
+                context.evaluator.isStatic(overridingClass)) {
+            if (overridingClass.name == "Default") return true
+            if (overridingClass.name == "Proxy") {
+                val shouldBeStub = overridingClass.parent as? PsiClass ?: return false
+                return shouldBeStub.name == "Stub" &&
+                        context.evaluator.isAbstract(shouldBeStub) &&
+                        context.evaluator.isStatic(shouldBeStub) &&
+                        shouldBeStub.extendsList?.referenceElements
+                        ?.any { it.qualifiedName == BINDER_CLASS } == true
+            }
+        }
+        return false
+    }
+
+    private fun isInterfaceAndExtendsIInterface(overriddenClass: PsiClass): Boolean =
+            overriddenClass.isInterface &&
+                    overriddenClass.extendsList?.referenceElements
+                    ?.any { it.qualifiedName == IINTERFACE_INTERFACE } == true
+
     companion object {
         val EXPLANATION = """
             The @EnforcePermission annotation is used to indicate that the underlying binder code
diff --git a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
index 3c2ea1d..c1e47e9 100644
--- a/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
+++ b/tools/lint/global/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionHelperDetector.kt
@@ -28,8 +28,8 @@
 import com.intellij.psi.PsiElement
 import org.jetbrains.uast.UBlockExpression
 import org.jetbrains.uast.UDeclarationsExpression
-import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UExpression
 import org.jetbrains.uast.UMethod
 
 class EnforcePermissionHelperDetector : Detector(), SourceCodeScanner {
@@ -40,6 +40,7 @@
 
     private inner class AidlStubHandler(val context: JavaContext) : UElementHandler() {
         override fun visitMethod(node: UMethod) {
+            if (context.evaluator.isAbstract(node)) return
             if (!node.hasAnnotation(ANNOTATION_ENFORCE_PERMISSION)) return
 
             val targetExpression = "super.${node.name}$HELPER_SUFFIX()"
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt
new file mode 100644
index 0000000..f2930d9
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorCodegenTest.kt
@@ -0,0 +1,557 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionDetectorCodegenTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionDetector.ISSUE_MISSING_ENFORCE_PERMISSION,
+            EnforcePermissionDetector.ISSUE_MISMATCHING_ENFORCE_PERMISSION
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun test_generated_IProtected() {
+        lint().files(
+            java(
+                """
+                /*
+                 * This file is auto-generated.  DO NOT MODIFY.
+                 */
+                package android.aidl.tests.permission;
+                public interface IProtected extends android.os.IInterface
+                {
+                  /** Default implementation for IProtected. */
+                  public static class Default implements android.aidl.tests.permission.IProtected
+                  {
+                    @Override public void PermissionProtected() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void NonManifestPermission() throws android.os.RemoteException
+                    {
+                    }
+                    // Used by the integration tests to dynamically set permissions that are considered granted.
+                    @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                    {
+                    }
+                    @Override
+                    public android.os.IBinder asBinder() {
+                      return null;
+                    }
+                  }
+                  /** Local-side IPC implementation stub class. */
+                  public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected
+                  {
+                    private final android.os.PermissionEnforcer mEnforcer;
+                    /** Construct the stub using the Enforcer provided. */
+                    public Stub(android.os.PermissionEnforcer enforcer)
+                    {
+                      this.attachInterface(this, DESCRIPTOR);
+                      if (enforcer == null) {
+                        throw new IllegalArgumentException("enforcer cannot be null");
+                      }
+                      mEnforcer = enforcer;
+                    }
+                    @Deprecated
+                    /** Default constructor. */
+                    public Stub() {
+                      this(android.os.PermissionEnforcer.fromContext(
+                         android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                    }
+                    /**
+                     * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface,
+                     * generating a proxy if needed.
+                     */
+                    public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj)
+                    {
+                      if ((obj==null)) {
+                        return null;
+                      }
+                      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                      if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) {
+                        return ((android.aidl.tests.permission.IProtected)iin);
+                      }
+                      return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj);
+                    }
+                    @Override public android.os.IBinder asBinder()
+                    {
+                      return this;
+                    }
+                    /** @hide */
+                    public static java.lang.String getDefaultTransactionName(int transactionCode)
+                    {
+                      switch (transactionCode)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          return "PermissionProtected";
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          return "MultiplePermissionsAll";
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          return "MultiplePermissionsAny";
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          return "NonManifestPermission";
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          return "SetGranted";
+                        }
+                        default:
+                        {
+                          return null;
+                        }
+                      }
+                    }
+                    /** @hide */
+                    public java.lang.String getTransactionName(int transactionCode)
+                    {
+                      return this.getDefaultTransactionName(transactionCode);
+                    }
+                    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                    {
+                      java.lang.String descriptor = DESCRIPTOR;
+                      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                        data.enforceInterface(descriptor);
+                      }
+                      switch (code)
+                      {
+                        case INTERFACE_TRANSACTION:
+                        {
+                          reply.writeString(descriptor);
+                          return true;
+                        }
+                      }
+                      switch (code)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          this.PermissionProtected();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          this.MultiplePermissionsAll();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          this.MultiplePermissionsAny();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          this.NonManifestPermission();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          java.util.List<java.lang.String> _arg0;
+                          _arg0 = data.createStringArrayList();
+                          data.enforceNoDataAvail();
+                          this.SetGranted(_arg0);
+                          reply.writeNoException();
+                          break;
+                        }
+                        default:
+                        {
+                          return super.onTransact(code, data, reply, flags);
+                        }
+                      }
+                      return true;
+                    }
+                    private static class Proxy implements android.aidl.tests.permission.IProtected
+                    {
+                      private android.os.IBinder mRemote;
+                      Proxy(android.os.IBinder remote)
+                      {
+                        mRemote = remote;
+                      }
+                      @Override public android.os.IBinder asBinder()
+                      {
+                        return mRemote;
+                      }
+                      public java.lang.String getInterfaceDescriptor()
+                      {
+                        return DESCRIPTOR;
+                      }
+                      @Override public void PermissionProtected() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void NonManifestPermission() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      // Used by the integration tests to dynamically set permissions that are considered granted.
+                      @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          _data.writeStringList(permissions);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                    }
+                    static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                    /** Helper method to enforce permissions for PermissionProtected */
+                    protected void PermissionProtected_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                    /** Helper method to enforce permissions for MultiplePermissionsAll */
+                    protected void MultiplePermissionsAll_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+                    /** Helper method to enforce permissions for MultiplePermissionsAny */
+                    protected void MultiplePermissionsAny_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+                    /** Helper method to enforce permissions for NonManifestPermission */
+                    protected void NonManifestPermission_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source);
+                    }
+                    static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+                    /** @hide */
+                    public int getMaxTransactionId()
+                    {
+                      return 4;
+                    }
+                  }
+                  
+                  @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                  public void PermissionProtected() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAll() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAny() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+                  public void NonManifestPermission() throws android.os.RemoteException;
+                  // Used by the integration tests to dynamically set permissions that are considered granted.
+                  @android.annotation.RequiresNoPermission
+                  public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException;
+                }
+                """
+            ).indented(),
+                *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun test_generated_IProtectedInterface() {
+        lint().files(
+                java(
+                    """
+                    /*
+                     * This file is auto-generated.  DO NOT MODIFY.
+                     */
+                    package android.aidl.tests.permission;
+                    public interface IProtectedInterface extends android.os.IInterface
+                    {
+                      /** Default implementation for IProtectedInterface. */
+                      public static class Default implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        @Override public void Method1() throws android.os.RemoteException
+                        {
+                        }
+                        @Override public void Method2() throws android.os.RemoteException
+                        {
+                        }
+                        @Override
+                        public android.os.IBinder asBinder() {
+                          return null;
+                        }
+                      }
+                      /** Local-side IPC implementation stub class. */
+                      public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        private final android.os.PermissionEnforcer mEnforcer;
+                        /** Construct the stub using the Enforcer provided. */
+                        public Stub(android.os.PermissionEnforcer enforcer)
+                        {
+                          this.attachInterface(this, DESCRIPTOR);
+                          if (enforcer == null) {
+                            throw new IllegalArgumentException("enforcer cannot be null");
+                          }
+                          mEnforcer = enforcer;
+                        }
+                        @Deprecated
+                        /** Default constructor. */
+                        public Stub() {
+                          this(android.os.PermissionEnforcer.fromContext(
+                             android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                        }
+                        /**
+                         * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface,
+                         * generating a proxy if needed.
+                         */
+                        public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj)
+                        {
+                          if ((obj==null)) {
+                            return null;
+                          }
+                          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                          if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) {
+                            return ((android.aidl.tests.permission.IProtectedInterface)iin);
+                          }
+                          return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj);
+                        }
+                        @Override public android.os.IBinder asBinder()
+                        {
+                          return this;
+                        }
+                        /** @hide */
+                        public static java.lang.String getDefaultTransactionName(int transactionCode)
+                        {
+                          switch (transactionCode)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              return "Method1";
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              return "Method2";
+                            }
+                            default:
+                            {
+                              return null;
+                            }
+                          }
+                        }
+                        /** @hide */
+                        public java.lang.String getTransactionName(int transactionCode)
+                        {
+                          return this.getDefaultTransactionName(transactionCode);
+                        }
+                        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                        {
+                          java.lang.String descriptor = DESCRIPTOR;
+                          if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                            data.enforceInterface(descriptor);
+                          }
+                          switch (code)
+                          {
+                            case INTERFACE_TRANSACTION:
+                            {
+                              reply.writeString(descriptor);
+                              return true;
+                            }
+                          }
+                          switch (code)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              this.Method1();
+                              reply.writeNoException();
+                              break;
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              this.Method2();
+                              reply.writeNoException();
+                              break;
+                            }
+                            default:
+                            {
+                              return super.onTransact(code, data, reply, flags);
+                            }
+                          }
+                          return true;
+                        }
+                        private static class Proxy implements android.aidl.tests.permission.IProtectedInterface
+                        {
+                          private android.os.IBinder mRemote;
+                          Proxy(android.os.IBinder remote)
+                          {
+                            mRemote = remote;
+                          }
+                          @Override public android.os.IBinder asBinder()
+                          {
+                            return mRemote;
+                          }
+                          public java.lang.String getInterfaceDescriptor()
+                          {
+                            return DESCRIPTOR;
+                          }
+                          @Override public void Method1() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                          @Override public void Method2() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                        }
+                        static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                        /** Helper method to enforce permissions for Method1 */
+                        protected void Method1_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                        /** Helper method to enforce permissions for Method2 */
+                        protected void Method2_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        /** @hide */
+                        public int getMaxTransactionId()
+                        {
+                          return 1;
+                        }
+                      }
+                      
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method1() throws android.os.RemoteException;
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method2() throws android.os.RemoteException;
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /* Stubs */
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub)
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
index 3c1d1e8..618bbcc 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionDetectorTest.kt
@@ -189,6 +189,28 @@
 1 errors, 0 warnings""".addLineContinuation())
     }
 
+    fun testDetectIssuesMissingAnnotationOnMethodWhenClassIsCalledDefault() {
+        lint().files(java(
+            """
+            package test.pkg;
+            public class Default extends IFooMethod.Stub {
+                public void testMethod() {}
+            }
+            """).indented(),
+            *stubs
+        )
+            .run()
+            .expect(
+                """
+                src/test/pkg/Default.java:3: Error: The method Default.testMethod \
+                overrides the method Stub.testMethod which is annotated with @EnforcePermission. The same annotation must be used on Default.testMethod [MissingEnforcePermissionAnnotation]
+                    public void testMethod() {}
+                                ~~~~~~~~~~
+                1 errors, 0 warnings 
+                """.addLineContinuation()
+            )
+    }
+
     /* Stubs */
 
     // A service with permission annotation on the class.
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
new file mode 100644
index 0000000..5a63bb4
--- /dev/null
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorCodegenTest.kt
@@ -0,0 +1,557 @@
+/*
+ * 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.google.android.lint.aidl
+
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestLintTask
+import com.android.tools.lint.checks.infrastructure.TestMode
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Issue
+
+@Suppress("UnstableApiUsage")
+class EnforcePermissionHelperDetectorCodegenTest : LintDetectorTest() {
+    override fun getDetector(): Detector = EnforcePermissionHelperDetector()
+
+    override fun getIssues(): List<Issue> = listOf(
+            EnforcePermissionHelperDetector.ISSUE_ENFORCE_PERMISSION_HELPER
+    )
+
+    override fun lint(): TestLintTask = super.lint().allowMissingSdk(true)
+
+    fun test_generated_IProtected() {
+        lint().testModes(TestMode.DEFAULT).files(
+            java(
+                """
+                /*
+                 * This file is auto-generated.  DO NOT MODIFY.
+                 */
+                package android.aidl.tests.permission;
+                public interface IProtected extends android.os.IInterface
+                {
+                  /** Default implementation for IProtected. */
+                  public static class Default implements android.aidl.tests.permission.IProtected
+                  {
+                    @Override public void PermissionProtected() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                    {
+                    }
+                    @Override public void NonManifestPermission() throws android.os.RemoteException
+                    {
+                    }
+                    // Used by the integration tests to dynamically set permissions that are considered granted.
+                    @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                    {
+                    }
+                    @Override
+                    public android.os.IBinder asBinder() {
+                      return null;
+                    }
+                  }
+                  /** Local-side IPC implementation stub class. */
+                  public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtected
+                  {
+                    private final android.os.PermissionEnforcer mEnforcer;
+                    /** Construct the stub using the Enforcer provided. */
+                    public Stub(android.os.PermissionEnforcer enforcer)
+                    {
+                      this.attachInterface(this, DESCRIPTOR);
+                      if (enforcer == null) {
+                        throw new IllegalArgumentException("enforcer cannot be null");
+                      }
+                      mEnforcer = enforcer;
+                    }
+                    @Deprecated
+                    /** Default constructor. */
+                    public Stub() {
+                      this(android.os.PermissionEnforcer.fromContext(
+                         android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                    }
+                    /**
+                     * Cast an IBinder object into an android.aidl.tests.permission.IProtected interface,
+                     * generating a proxy if needed.
+                     */
+                    public static android.aidl.tests.permission.IProtected asInterface(android.os.IBinder obj)
+                    {
+                      if ((obj==null)) {
+                        return null;
+                      }
+                      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                      if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtected))) {
+                        return ((android.aidl.tests.permission.IProtected)iin);
+                      }
+                      return new android.aidl.tests.permission.IProtected.Stub.Proxy(obj);
+                    }
+                    @Override public android.os.IBinder asBinder()
+                    {
+                      return this;
+                    }
+                    /** @hide */
+                    public static java.lang.String getDefaultTransactionName(int transactionCode)
+                    {
+                      switch (transactionCode)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          return "PermissionProtected";
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          return "MultiplePermissionsAll";
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          return "MultiplePermissionsAny";
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          return "NonManifestPermission";
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          return "SetGranted";
+                        }
+                        default:
+                        {
+                          return null;
+                        }
+                      }
+                    }
+                    /** @hide */
+                    public java.lang.String getTransactionName(int transactionCode)
+                    {
+                      return this.getDefaultTransactionName(transactionCode);
+                    }
+                    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                    {
+                      java.lang.String descriptor = DESCRIPTOR;
+                      if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                        data.enforceInterface(descriptor);
+                      }
+                      switch (code)
+                      {
+                        case INTERFACE_TRANSACTION:
+                        {
+                          reply.writeString(descriptor);
+                          return true;
+                        }
+                      }
+                      switch (code)
+                      {
+                        case TRANSACTION_PermissionProtected:
+                        {
+                          this.PermissionProtected();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAll:
+                        {
+                          this.MultiplePermissionsAll();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_MultiplePermissionsAny:
+                        {
+                          this.MultiplePermissionsAny();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_NonManifestPermission:
+                        {
+                          this.NonManifestPermission();
+                          reply.writeNoException();
+                          break;
+                        }
+                        case TRANSACTION_SetGranted:
+                        {
+                          java.util.List<java.lang.String> _arg0;
+                          _arg0 = data.createStringArrayList();
+                          data.enforceNoDataAvail();
+                          this.SetGranted(_arg0);
+                          reply.writeNoException();
+                          break;
+                        }
+                        default:
+                        {
+                          return super.onTransact(code, data, reply, flags);
+                        }
+                      }
+                      return true;
+                    }
+                    private static class Proxy implements android.aidl.tests.permission.IProtected
+                    {
+                      private android.os.IBinder mRemote;
+                      Proxy(android.os.IBinder remote)
+                      {
+                        mRemote = remote;
+                      }
+                      @Override public android.os.IBinder asBinder()
+                      {
+                        return mRemote;
+                      }
+                      public java.lang.String getInterfaceDescriptor()
+                      {
+                        return DESCRIPTOR;
+                      }
+                      @Override public void PermissionProtected() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_PermissionProtected, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAll() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAll, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void MultiplePermissionsAny() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_MultiplePermissionsAny, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      @Override public void NonManifestPermission() throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_NonManifestPermission, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                      // Used by the integration tests to dynamically set permissions that are considered granted.
+                      @Override public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException
+                      {
+                        android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                        android.os.Parcel _reply = android.os.Parcel.obtain();
+                        try {
+                          _data.writeInterfaceToken(DESCRIPTOR);
+                          _data.writeStringList(permissions);
+                          boolean _status = mRemote.transact(Stub.TRANSACTION_SetGranted, _data, _reply, 0);
+                          _reply.readException();
+                        }
+                        finally {
+                          _reply.recycle();
+                          _data.recycle();
+                        }
+                      }
+                    }
+                    static final int TRANSACTION_PermissionProtected = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                    /** Helper method to enforce permissions for PermissionProtected */
+                    protected void PermissionProtected_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.Manifest.permission.READ_PHONE_STATE, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAll = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                    /** Helper method to enforce permissions for MultiplePermissionsAll */
+                    protected void MultiplePermissionsAll_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAllOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_MultiplePermissionsAny = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
+                    /** Helper method to enforce permissions for MultiplePermissionsAny */
+                    protected void MultiplePermissionsAny_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermissionAnyOf(new String[]{android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE}, source);
+                    }
+                    static final int TRANSACTION_NonManifestPermission = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
+                    /** Helper method to enforce permissions for NonManifestPermission */
+                    protected void NonManifestPermission_enforcePermission() throws SecurityException {
+                      android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                      mEnforcer.enforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, source);
+                    }
+                    static final int TRANSACTION_SetGranted = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
+                    /** @hide */
+                    public int getMaxTransactionId()
+                    {
+                      return 4;
+                    }
+                  }
+                  
+                  @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                  public void PermissionProtected() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(allOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAll() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(anyOf = {android.Manifest.permission.INTERNET, android.Manifest.permission.VIBRATE})
+                  public void MultiplePermissionsAny() throws android.os.RemoteException;
+                  @android.annotation.EnforcePermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)
+                  public void NonManifestPermission() throws android.os.RemoteException;
+                  // Used by the integration tests to dynamically set permissions that are considered granted.
+                  @android.annotation.RequiresNoPermission
+                  public void SetGranted(java.util.List<java.lang.String> permissions) throws android.os.RemoteException;
+                }
+                """
+            ).indented(),
+                *stubs
+        )
+            .run()
+            .expectClean()
+    }
+
+    fun test_generated_IProtectedInterface() {
+        lint().files(
+                java(
+                    """
+                    /*
+                     * This file is auto-generated.  DO NOT MODIFY.
+                     */
+                    package android.aidl.tests.permission;
+                    public interface IProtectedInterface extends android.os.IInterface
+                    {
+                      /** Default implementation for IProtectedInterface. */
+                      public static class Default implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        @Override public void Method1() throws android.os.RemoteException
+                        {
+                        }
+                        @Override public void Method2() throws android.os.RemoteException
+                        {
+                        }
+                        @Override
+                        public android.os.IBinder asBinder() {
+                          return null;
+                        }
+                      }
+                      /** Local-side IPC implementation stub class. */
+                      public static abstract class Stub extends android.os.Binder implements android.aidl.tests.permission.IProtectedInterface
+                      {
+                        private final android.os.PermissionEnforcer mEnforcer;
+                        /** Construct the stub using the Enforcer provided. */
+                        public Stub(android.os.PermissionEnforcer enforcer)
+                        {
+                          this.attachInterface(this, DESCRIPTOR);
+                          if (enforcer == null) {
+                            throw new IllegalArgumentException("enforcer cannot be null");
+                          }
+                          mEnforcer = enforcer;
+                        }
+                        @Deprecated
+                        /** Default constructor. */
+                        public Stub() {
+                          this(android.os.PermissionEnforcer.fromContext(
+                             android.app.ActivityThread.currentActivityThread().getSystemContext()));
+                        }
+                        /**
+                         * Cast an IBinder object into an android.aidl.tests.permission.IProtectedInterface interface,
+                         * generating a proxy if needed.
+                         */
+                        public static android.aidl.tests.permission.IProtectedInterface asInterface(android.os.IBinder obj)
+                        {
+                          if ((obj==null)) {
+                            return null;
+                          }
+                          android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
+                          if (((iin!=null)&&(iin instanceof android.aidl.tests.permission.IProtectedInterface))) {
+                            return ((android.aidl.tests.permission.IProtectedInterface)iin);
+                          }
+                          return new android.aidl.tests.permission.IProtectedInterface.Stub.Proxy(obj);
+                        }
+                        @Override public android.os.IBinder asBinder()
+                        {
+                          return this;
+                        }
+                        /** @hide */
+                        public static java.lang.String getDefaultTransactionName(int transactionCode)
+                        {
+                          switch (transactionCode)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              return "Method1";
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              return "Method2";
+                            }
+                            default:
+                            {
+                              return null;
+                            }
+                          }
+                        }
+                        /** @hide */
+                        public java.lang.String getTransactionName(int transactionCode)
+                        {
+                          return this.getDefaultTransactionName(transactionCode);
+                        }
+                        @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
+                        {
+                          java.lang.String descriptor = DESCRIPTOR;
+                          if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
+                            data.enforceInterface(descriptor);
+                          }
+                          switch (code)
+                          {
+                            case INTERFACE_TRANSACTION:
+                            {
+                              reply.writeString(descriptor);
+                              return true;
+                            }
+                          }
+                          switch (code)
+                          {
+                            case TRANSACTION_Method1:
+                            {
+                              this.Method1();
+                              reply.writeNoException();
+                              break;
+                            }
+                            case TRANSACTION_Method2:
+                            {
+                              this.Method2();
+                              reply.writeNoException();
+                              break;
+                            }
+                            default:
+                            {
+                              return super.onTransact(code, data, reply, flags);
+                            }
+                          }
+                          return true;
+                        }
+                        private static class Proxy implements android.aidl.tests.permission.IProtectedInterface
+                        {
+                          private android.os.IBinder mRemote;
+                          Proxy(android.os.IBinder remote)
+                          {
+                            mRemote = remote;
+                          }
+                          @Override public android.os.IBinder asBinder()
+                          {
+                            return mRemote;
+                          }
+                          public java.lang.String getInterfaceDescriptor()
+                          {
+                            return DESCRIPTOR;
+                          }
+                          @Override public void Method1() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method1, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                          @Override public void Method2() throws android.os.RemoteException
+                          {
+                            android.os.Parcel _data = android.os.Parcel.obtain(asBinder());
+                            android.os.Parcel _reply = android.os.Parcel.obtain();
+                            try {
+                              _data.writeInterfaceToken(DESCRIPTOR);
+                              boolean _status = mRemote.transact(Stub.TRANSACTION_Method2, _data, _reply, 0);
+                              _reply.readException();
+                            }
+                            finally {
+                              _reply.recycle();
+                              _data.recycle();
+                            }
+                          }
+                        }
+                        static final int TRANSACTION_Method1 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
+                        /** Helper method to enforce permissions for Method1 */
+                        protected void Method1_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        static final int TRANSACTION_Method2 = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
+                        /** Helper method to enforce permissions for Method2 */
+                        protected void Method2_enforcePermission() throws SecurityException {
+                          android.content.AttributionSource source = new android.content.AttributionSource(getCallingUid(), null, null);
+                          mEnforcer.enforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION, source);
+                        }
+                        /** @hide */
+                        public int getMaxTransactionId()
+                        {
+                          return 1;
+                        }
+                      }
+                      
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method1() throws android.os.RemoteException;
+                      @android.annotation.EnforcePermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+                      public void Method2() throws android.os.RemoteException;
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expectClean()
+    }
+
+    /* Stubs */
+
+    private val manifestPermissionStub: TestFile = java(
+        """
+        package android.Manifest;
+        class permission {
+          public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+          public static final String INTERNET = "android.permission.INTERNET";
+        }
+        """
+    ).indented()
+
+    private val enforcePermissionAnnotationStub: TestFile = java(
+        """
+        package android.annotation;
+        public @interface EnforcePermission {}
+        """
+    ).indented()
+
+    private val stubs = arrayOf(manifestPermissionStub, enforcePermissionAnnotationStub)
+}
diff --git a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
index 31e4846..4799184 100644
--- a/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
+++ b/tools/lint/global/checks/src/test/java/com/google/android/lint/aidl/EnforcePermissionHelperDetectorTest.kt
@@ -150,6 +150,31 @@
             .expectClean()
     }
 
+    fun testInterfaceDefaultMethod_wouldStillReport() {
+        lint().files(
+                java(
+                    """
+                    public interface IProtected extends android.os.IInterface {
+                        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                        default void PermissionProtected() throws android.os.RemoteException {
+                            String foo = "bar";
+                        }
+                    }
+                    """
+                ).indented(),
+                *stubs
+        )
+                .run()
+                .expect(
+                    """
+                    src/IProtected.java:2: Error: Method must start with super.PermissionProtected_enforcePermission() [MissingEnforcePermissionHelper]
+                        @android.annotation.EnforcePermission(android.Manifest.permission.READ_PHONE_STATE)
+                        ^
+                    1 errors, 0 warnings
+                    """
+                )
+    }
+
     companion object {
         val stubs = arrayOf(aidlStub, contextStub, binderStub)
     }