Merge "Add session participant count"
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index aec60f2..b2bd8d7 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -266,6 +266,27 @@
         }
     }
 
+    /** Tests switching to an uninitialized user with wait times between iterations. */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_realistic() throws Exception {
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            final int startUser = ActivityManager.getCurrentUser();
+            final int userId = createUserNoFlags();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(userId);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            removeUser(userId);
+            mRunner.resumeTimingForNextIteration();
+        }
+    }
+
     /** Tests switching to a previously-started, but no-longer-running, user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_stopped() throws RemoteException {
@@ -286,6 +307,30 @@
         }
     }
 
+    /** Tests switching to a previously-started, but no-longer-running, user with wait
+     * times between iterations */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_stopped_realistic() throws RemoteException {
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            switchUserNoCheck(startUser);
+            stopUserAfterWaitingForBroadcastIdle(testUser, true);
+            attestFalse("Failed to stop user " + testUser, mAm.isUserRunning(testUser));
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests switching to an already-created already-running non-owner background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void switchUser_running() throws RemoteException {
@@ -306,6 +351,29 @@
         }
     }
 
+    /** Tests switching to an already-created already-running non-owner background user, with wait
+     * times between iterations */
+    @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+    public void switchUser_running_realistic() throws RemoteException {
+        final int startUser = ActivityManager.getCurrentUser();
+        final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
+        while (mRunner.keepRunning()) {
+            mRunner.pauseTiming();
+            waitCoolDownPeriod();
+            Log.d(TAG, "Starting timer");
+            mRunner.resumeTiming();
+
+            switchUser(testUser);
+
+            mRunner.pauseTiming();
+            Log.d(TAG, "Stopping timer");
+            waitForBroadcastIdle();
+            switchUserNoCheck(startUser);
+            mRunner.resumeTimingForNextIteration();
+        }
+        removeUser(testUser);
+    }
+
     /** Tests stopping a background user. */
     @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
     public void stopUser() throws RemoteException {
@@ -902,4 +970,18 @@
     private void waitForBroadcastIdle() {
         ShellHelper.runShellCommand("am wait-for-broadcast-idle");
     }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+            // Ignore
+        }
+    }
+
+    private void waitCoolDownPeriod() {
+        final int tenSeconds = 1000 * 10;
+        waitForBroadcastIdle();
+        sleep(tenSeconds);
+    }
 }
diff --git a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
index 4242cf8..5d67c96 100644
--- a/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
+++ b/apex/jobscheduler/framework/java/android/app/JobSchedulerImpl.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.app.job.IJobScheduler;
 import android.app.job.IUserVisibleJobObserver;
@@ -25,10 +26,13 @@
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
 import android.os.RemoteException;
+import android.util.ArrayMap;
 
 import java.util.List;
-
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Concrete implementation of the JobScheduler interface
@@ -41,16 +45,42 @@
 public class JobSchedulerImpl extends JobScheduler {
     IJobScheduler mBinder;
     private final Context mContext;
+    private final String mNamespace;
 
     public JobSchedulerImpl(@NonNull Context context, IJobScheduler binder) {
+        this(context, binder, null);
+    }
+
+    private JobSchedulerImpl(@NonNull Context context, IJobScheduler binder,
+            @Nullable String namespace) {
         mContext = context;
         mBinder = binder;
+        mNamespace = namespace;
+    }
+
+    private JobSchedulerImpl(JobSchedulerImpl jsi, @Nullable String namespace) {
+        this(jsi.mContext, jsi.mBinder, namespace);
+    }
+
+    @NonNull
+    @Override
+    public JobScheduler forNamespace(@NonNull String namespace) {
+        if (namespace == null) {
+            throw new IllegalArgumentException("namespace cannot be null");
+        }
+        return new JobSchedulerImpl(this, namespace);
+    }
+
+    @Nullable
+    @Override
+    public String getNamespace() {
+        return mNamespace;
     }
 
     @Override
     public int schedule(JobInfo job) {
         try {
-            return mBinder.schedule(job);
+            return mBinder.schedule(mNamespace, job);
         } catch (RemoteException e) {
             return JobScheduler.RESULT_FAILURE;
         }
@@ -59,7 +89,7 @@
     @Override
     public int enqueue(JobInfo job, JobWorkItem work) {
         try {
-            return mBinder.enqueue(job, work);
+            return mBinder.enqueue(mNamespace, job, work);
         } catch (RemoteException e) {
             return JobScheduler.RESULT_FAILURE;
         }
@@ -68,7 +98,7 @@
     @Override
     public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag) {
         try {
-            return mBinder.scheduleAsPackage(job, packageName, userId, tag);
+            return mBinder.scheduleAsPackage(mNamespace, job, packageName, userId, tag);
         } catch (RemoteException e) {
             return JobScheduler.RESULT_FAILURE;
         }
@@ -77,23 +107,44 @@
     @Override
     public void cancel(int jobId) {
         try {
-            mBinder.cancel(jobId);
+            mBinder.cancel(mNamespace, jobId);
         } catch (RemoteException e) {}
-
     }
 
     @Override
     public void cancelAll() {
         try {
+            mBinder.cancelAllInNamespace(mNamespace);
+        } catch (RemoteException e) {}
+    }
+
+    @Override
+    public void cancelInAllNamespaces() {
+        try {
             mBinder.cancelAll();
         } catch (RemoteException e) {}
-
     }
 
     @Override
     public List<JobInfo> getAllPendingJobs() {
         try {
-            return mBinder.getAllPendingJobs().getList();
+            return mBinder.getAllPendingJobsInNamespace(mNamespace).getList();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    @Override
+    public Map<String, List<JobInfo>> getPendingJobsInAllNamespaces() {
+        try {
+            final Map<String, ParceledListSlice<JobInfo>> parceledList =
+                    mBinder.getAllPendingJobs();
+            final ArrayMap<String, List<JobInfo>> jobMap = new ArrayMap<>();
+            final Set<String> keys = parceledList.keySet();
+            for (String key : keys) {
+                jobMap.put(key, parceledList.get(key).getList());
+            }
+            return jobMap;
         } catch (RemoteException e) {
             return null;
         }
@@ -102,7 +153,7 @@
     @Override
     public JobInfo getPendingJob(int jobId) {
         try {
-            return mBinder.getPendingJob(jobId);
+            return mBinder.getPendingJob(mNamespace, jobId);
         } catch (RemoteException e) {
             return null;
         }
@@ -111,7 +162,7 @@
     @Override
     public int getPendingJobReason(int jobId) {
         try {
-            return mBinder.getPendingJobReason(jobId);
+            return mBinder.getPendingJobReason(mNamespace, jobId);
         } catch (RemoteException e) {
             return PENDING_JOB_REASON_UNDEFINED;
         }
diff --git a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
index c87a2af..a1f1954 100644
--- a/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
+++ b/apex/jobscheduler/framework/java/android/app/job/IJobScheduler.aidl
@@ -21,20 +21,24 @@
 import android.app.job.JobSnapshot;
 import android.app.job.JobWorkItem;
 import android.content.pm.ParceledListSlice;
+import java.util.Map;
 
  /**
   * IPC interface that supports the app-facing {@link #JobScheduler} api.
   * {@hide}
   */
 interface IJobScheduler {
-    int schedule(in JobInfo job);
-    int enqueue(in JobInfo job, in JobWorkItem work);
-    int scheduleAsPackage(in JobInfo job, String packageName, int userId, String tag);
-    void cancel(int jobId);
+    int schedule(String namespace, in JobInfo job);
+    int enqueue(String namespace, in JobInfo job, in JobWorkItem work);
+    int scheduleAsPackage(String namespace, in JobInfo job, String packageName, int userId, String tag);
+    void cancel(String namespace, int jobId);
     void cancelAll();
-    ParceledListSlice getAllPendingJobs();
-    JobInfo getPendingJob(int jobId);
-    int getPendingJobReason(int jobId);
+    void cancelAllInNamespace(String namespace);
+    // Returns Map<String, ParceledListSlice>, where the keys are the namespaces.
+    Map<String, ParceledListSlice<JobInfo>> getAllPendingJobs();
+    ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace);
+    JobInfo getPendingJob(String namespace, int jobId);
+    int getPendingJobReason(String namespace, int jobId);
     boolean canRunLongJobs(String packageName);
     boolean hasRunLongJobsPermission(String packageName, int userId);
     List<JobInfo> getStartedJobs();
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index deb97a5..e0fffb4 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -924,7 +924,8 @@
     @SuppressWarnings("UnsafeParcelApi")
     private JobInfo(Parcel in) {
         jobId = in.readInt();
-        extras = in.readPersistableBundle();
+        final PersistableBundle persistableExtras = in.readPersistableBundle();
+        extras = persistableExtras != null ? persistableExtras : PersistableBundle.EMPTY;
         transientExtras = in.readBundle();
         if (in.readInt() != 0) {
             clipData = ClipData.CREATOR.createFromParcel(in);
@@ -1251,6 +1252,9 @@
          * in them all being treated the same. The priorities each have slightly different
          * behaviors, as noted in their relevant javadoc.
          *
+         * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * the priority will only affect sorting order within the job's namespace.
+         *
          * <b>NOTE:</b> Setting all of your jobs to high priority will not be
          * beneficial to your app and in fact may hurt its performance in the
          * long run.
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index a5a7f93..18ddffb 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -277,6 +277,8 @@
 
     @UnsupportedAppUsage
     private final int jobId;
+    @Nullable
+    private final String mJobNamespace;
     private final PersistableBundle extras;
     private final Bundle transientExtras;
     private final ClipData clipData;
@@ -295,7 +297,7 @@
     private String debugStopReason; // Human readable stop reason for debugging.
 
     /** @hide */
-    public JobParameters(IBinder callback, int jobId, PersistableBundle extras,
+    public JobParameters(IBinder callback, String namespace, int jobId, PersistableBundle extras,
             Bundle transientExtras, ClipData clipData, int clipGrantFlags,
             boolean overrideDeadlineExpired, boolean isExpedited,
             boolean isUserInitiated, Uri[] triggeredContentUris,
@@ -312,6 +314,7 @@
         this.mTriggeredContentUris = triggeredContentUris;
         this.mTriggeredContentAuthorities = triggeredContentAuthorities;
         this.network = network;
+        this.mJobNamespace = namespace;
     }
 
     /**
@@ -322,6 +325,19 @@
     }
 
     /**
+     * Get the namespace this job was placed in.
+     *
+     * @see JobScheduler#forNamespace(String)
+     * @return The namespace this job was scheduled in. Will be {@code null} if there was no
+     * explicit namespace set and this job is therefore in the default namespace.
+     * @hide
+     */
+    @Nullable
+    public String getJobNamespace() {
+        return mJobNamespace;
+    }
+
+    /**
      * @return The reason {@link JobService#onStopJob(JobParameters)} was called on this job. Will
      * be {@link #STOP_REASON_UNDEFINED} if {@link JobService#onStopJob(JobParameters)} has not
      * yet been called.
@@ -540,6 +556,7 @@
 
     private JobParameters(Parcel in) {
         jobId = in.readInt();
+        mJobNamespace = in.readString();
         extras = in.readPersistableBundle();
         transientExtras = in.readBundle();
         if (in.readInt() != 0) {
@@ -581,6 +598,7 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(jobId);
+        dest.writeString(mJobNamespace);
         dest.writePersistableBundle(extras);
         dest.writeBundle(transientExtras);
         if (clipData != null) {
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
index 659db9f..33668c7 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java
@@ -38,6 +38,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.List;
+import java.util.Map;
 
 /**
  * This is an API for scheduling various types of jobs against the framework that will be executed
@@ -264,6 +265,31 @@
     }
 
     /**
+     * Get a JobScheduler instance that is dedicated to a specific namespace. Any API calls using
+     * this instance will interact with jobs in that namespace, unless the API documentation says
+     * otherwise. Attempting to update a job scheduled in another namespace will not be possible
+     * but will instead create or update the job inside the current namespace. A JobScheduler
+     * instance dedicated to a namespace must be used to schedule or update jobs in that namespace.
+     * @see #getNamespace()
+     * @hide
+     */
+    @NonNull
+    public JobScheduler forNamespace(@NonNull String namespace) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
+     * Get the namespace this JobScheduler instance is operating in. A {@code null} value means
+     * that the app has not specified a namespace for this instance, and it is therefore using the
+     * default namespace.
+     * @hide
+     */
+    @Nullable
+    public String getNamespace() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * 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
      * running, it will be stopped.
@@ -311,7 +337,7 @@
      * but there are situations where it may get this wrong and count the JobInfo as changing.
      * (That said, you should be relatively safe with a simple set of consistent data in these
      * fields.)  You should never use {@link JobInfo.Builder#setClipData(ClipData, int)} with
-     * work you are enqueue, since currently this will always be treated as a different JobInfo,
+     * work you are enqueuing, since currently this will always be treated as a different JobInfo,
      * even if the ClipData contents are exactly the same.</p>
      *
      * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's
@@ -319,6 +345,16 @@
      * version {@link android.os.Build.VERSION_CODES#Q}. As such, the system may throttle calls to
      * this API if calls are made too frequently in a short amount of time.
      *
+     * <p class="caution"><strong>Note:</strong> Prior to Android version
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+     * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+     * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+     * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * JobWorkItems can be persisted alongside the hosting job.
+     * However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+     * {@link JobWorkItem.Builder#setExtras(PersistableBundle)} for any information that needs
+     * to be persisted.
+     *
      * <p>Note: The JobService component needs to be enabled in order to successfully schedule a
      * job.
      *
@@ -364,6 +400,15 @@
     public abstract void cancelAll();
 
     /**
+     * Cancel <em>all</em> jobs that have been scheduled by the calling application, regardless of
+     * namespace.
+     * @hide
+     */
+    public void cancelInAllNamespaces() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Retrieve all jobs that have been scheduled by the calling application.
      *
      * @return a list of all of the app's scheduled jobs.  This includes jobs that are
@@ -372,6 +417,21 @@
     public abstract @NonNull List<JobInfo> getAllPendingJobs();
 
     /**
+     * Retrieve all jobs that have been scheduled by the calling application within the current
+     * namespace.
+     *
+     * @return a list of all of the app's scheduled jobs scheduled with the current namespace.
+     * If a namespace hasn't been explicitly set with {@link #forNamespace(String)},
+     * then this will return jobs in the default namespace.
+     * This includes jobs that are currently started as well as those that are still waiting to run.
+     * @hide
+     */
+    @NonNull
+    public Map<String, List<JobInfo>> getPendingJobsInAllNamespaces() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Look up the description of a scheduled job.
      *
      * @return The {@link JobInfo} description of the given scheduled job, or {@code null}
@@ -400,6 +460,8 @@
      * Returns {@code true} if the app currently holds the
      * {@link android.Manifest.permission#RUN_LONG_JOBS} permission, allowing it to run long jobs.
      * @hide
+     * TODO(255371817): consider exposing this to apps who could call
+     * {@link #scheduleAsPackage(JobInfo, String, int, String)}
      */
     public boolean hasRunLongJobsPermission(@NonNull String packageName, @UserIdInt int userId) {
         return false;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index 32945e0..18167e2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,20 +19,33 @@
 import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
 
 import android.annotation.BytesLong;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.compat.Compatibility;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Intent;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 /**
  * A unit of work that can be enqueued for a job using
  * {@link JobScheduler#enqueue JobScheduler.enqueue}.  See
  * {@link JobParameters#dequeueWork() JobParameters.dequeueWork} for more details.
+ *
+ * <p class="caution"><strong>Note:</strong> Prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems could not be persisted.
+ * Apps were not allowed to enqueue JobWorkItems with persisted jobs and the system would throw
+ * an {@link IllegalArgumentException} if they attempted to do so. Starting with
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, JobWorkItems can be persisted alongside
+ * the hosting job. However, Intents cannot be persisted. Set a {@link PersistableBundle} using
+ * {@link Builder#setExtras(PersistableBundle)} for any information that needs to be persisted.
  */
 final public class JobWorkItem implements Parcelable {
+    @NonNull
+    private final PersistableBundle mExtras;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     final Intent mIntent;
     private final long mNetworkDownloadBytes;
@@ -49,6 +62,10 @@
      * Create a new piece of work, which can be submitted to
      * {@link JobScheduler#enqueue JobScheduler.enqueue}.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent The general Intent describing this work.
      */
     public JobWorkItem(Intent intent) {
@@ -62,6 +79,10 @@
      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
      * details about how to estimate network traffic.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent The general Intent describing this work.
      * @param downloadBytes The estimated size of network traffic that will be
      *            downloaded by this job work item, in bytes.
@@ -79,6 +100,10 @@
      * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
      * details about how to estimate network traffic.
      *
+     * <p>
+     * Intents cannot be used for persisted JobWorkItems.
+     * Use {@link Builder#setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+     *
      * @param intent            The general Intent describing this work.
      * @param downloadBytes     The estimated size of network traffic that will be
      *                          downloaded by this job work item, in bytes.
@@ -89,6 +114,7 @@
      */
     public JobWorkItem(@Nullable Intent intent, @BytesLong long downloadBytes,
             @BytesLong long uploadBytes, @BytesLong long minimumChunkBytes) {
+        mExtras = PersistableBundle.EMPTY;
         mIntent = intent;
         mNetworkDownloadBytes = downloadBytes;
         mNetworkUploadBytes = uploadBytes;
@@ -96,6 +122,25 @@
         enforceValidity(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
     }
 
+    private JobWorkItem(@NonNull Builder builder) {
+        mDeliveryCount = builder.mDeliveryCount;
+        mExtras = builder.mExtras.deepCopy();
+        mIntent = builder.mIntent;
+        mNetworkDownloadBytes = builder.mNetworkDownloadBytes;
+        mNetworkUploadBytes = builder.mNetworkUploadBytes;
+        mMinimumChunkBytes = builder.mMinimumNetworkChunkBytes;
+    }
+
+    /**
+     * Return the extras associated with this work.
+     *
+     * @see Builder#setExtras(PersistableBundle)
+     */
+    @NonNull
+    public PersistableBundle getExtras() {
+        return mExtras;
+    }
+
     /**
      * Return the Intent associated with this work.
      */
@@ -176,6 +221,7 @@
     /**
      * @hide
      */
+    @Nullable
     public Object getGrants() {
         return mGrants;
     }
@@ -186,6 +232,8 @@
         sb.append(mWorkId);
         sb.append(" intent=");
         sb.append(mIntent);
+        sb.append(" extras=");
+        sb.append(mExtras);
         if (mNetworkDownloadBytes != NETWORK_BYTES_UNKNOWN) {
             sb.append(" downloadBytes=");
             sb.append(mNetworkDownloadBytes);
@@ -207,6 +255,140 @@
     }
 
     /**
+     * Builder class for constructing {@link JobWorkItem} objects.
+     */
+    public static final class Builder {
+        private int mDeliveryCount;
+        private PersistableBundle mExtras = PersistableBundle.EMPTY;
+        private Intent mIntent;
+        private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN;
+        private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN;
+        private long mMinimumNetworkChunkBytes = NETWORK_BYTES_UNKNOWN;
+
+        /**
+         * Initialize a new Builder to construct a {@link JobWorkItem} object.
+         */
+        public Builder() {
+        }
+
+        /**
+         * @see JobWorkItem#getDeliveryCount()
+         * @return This object for method chaining
+         * @hide
+         */
+        @NonNull
+        public Builder setDeliveryCount(int deliveryCount) {
+            mDeliveryCount = deliveryCount;
+            return this;
+        }
+
+        /**
+         * Set optional extras. This can be persisted, so we only allow primitive types.
+         * @param extras Bundle containing extras you want the scheduler to hold on to for you.
+         * @return This object for method chaining
+         * @see JobWorkItem#getExtras()
+         */
+        @NonNull
+        public Builder setExtras(@NonNull PersistableBundle extras) {
+            if (extras == null) {
+                throw new IllegalArgumentException("extras cannot be null");
+            }
+            mExtras = extras;
+            return this;
+        }
+
+        /**
+         * Set an intent with information relevant to this work item.
+         *
+         * <p>
+         * Intents cannot be used for persisted JobWorkItems.
+         * Use {@link #setExtras(PersistableBundle)} instead for persisted JobWorkItems.
+         *
+         * @return This object for method chaining
+         * @see JobWorkItem#getIntent()
+         */
+        @NonNull
+        public Builder setIntent(@NonNull Intent intent) {
+            mIntent = intent;
+            return this;
+        }
+
+        /**
+         * Set the estimated size of network traffic that will be performed for this work item,
+         * in bytes.
+         *
+         * See {@link JobInfo.Builder#setEstimatedNetworkBytes(long, long)} for
+         * details about how to estimate network traffic.
+         *
+         * @param downloadBytes The estimated size of network traffic that will be
+         *                      downloaded for this work item, in bytes.
+         * @param uploadBytes   The estimated size of network traffic that will be
+         *                      uploaded for this work item, in bytes.
+         * @return This object for method chaining
+         * @see JobInfo.Builder#setEstimatedNetworkBytes(long, long)
+         * @see JobWorkItem#getEstimatedNetworkDownloadBytes()
+         * @see JobWorkItem#getEstimatedNetworkUploadBytes()
+         */
+        @NonNull
+        @SuppressLint("MissingGetterMatchingBuilder")
+        public Builder setEstimatedNetworkBytes(@BytesLong long downloadBytes,
+                @BytesLong long uploadBytes) {
+            if (downloadBytes != NETWORK_BYTES_UNKNOWN && downloadBytes < 0) {
+                throw new IllegalArgumentException(
+                        "Invalid network download bytes: " + downloadBytes);
+            }
+            if (uploadBytes != NETWORK_BYTES_UNKNOWN && uploadBytes < 0) {
+                throw new IllegalArgumentException("Invalid network upload bytes: " + uploadBytes);
+            }
+            mNetworkDownloadBytes = downloadBytes;
+            mNetworkUploadBytes = uploadBytes;
+            return this;
+        }
+
+        /**
+         * Set the minimum size of non-resumable network traffic this work item requires, in bytes.
+         * When the upload or download can be easily paused and resumed, use this to set the
+         * smallest size that must be transmitted between start and stop events to be considered
+         * successful. If the transfer cannot be paused and resumed, then this should be the sum
+         * of the values provided to {@link #setEstimatedNetworkBytes(long, long)}.
+         *
+         * See {@link JobInfo.Builder#setMinimumNetworkChunkBytes(long)} for
+         * details about how to set the minimum chunk.
+         *
+         * @param chunkSizeBytes The smallest piece of data that cannot be easily paused and
+         *                       resumed, in bytes.
+         * @return This object for method chaining
+         * @see JobInfo.Builder#setMinimumNetworkChunkBytes(long)
+         * @see JobWorkItem#getMinimumNetworkChunkBytes()
+         * @see JobWorkItem#JobWorkItem(android.content.Intent, long, long, long)
+         */
+        @NonNull
+        public Builder setMinimumNetworkChunkBytes(@BytesLong long chunkSizeBytes) {
+            if (chunkSizeBytes != NETWORK_BYTES_UNKNOWN && chunkSizeBytes <= 0) {
+                throw new IllegalArgumentException("Minimum chunk size must be positive");
+            }
+            mMinimumNetworkChunkBytes = chunkSizeBytes;
+            return this;
+        }
+
+        /**
+         * @return The JobWorkItem object to hand to the JobScheduler. This object is immutable.
+         */
+        @NonNull
+        public JobWorkItem build() {
+            return build(Compatibility.isChangeEnabled(JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES));
+        }
+
+        /** @hide */
+        @NonNull
+        public JobWorkItem build(boolean rejectNegativeNetworkEstimates) {
+            JobWorkItem jobWorkItem = new JobWorkItem(this);
+            jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
+            return jobWorkItem;
+        }
+    }
+
+    /**
      * @hide
      */
     public void enforceValidity(boolean rejectNegativeNetworkEstimates) {
@@ -249,6 +431,7 @@
         } else {
             out.writeInt(0);
         }
+        out.writePersistableBundle(mExtras);
         out.writeLong(mNetworkDownloadBytes);
         out.writeLong(mNetworkUploadBytes);
         out.writeLong(mMinimumChunkBytes);
@@ -274,6 +457,8 @@
         } else {
             mIntent = null;
         }
+        final PersistableBundle extras = in.readPersistableBundle();
+        mExtras = extras != null ? extras : PersistableBundle.EMPTY;
         mNetworkDownloadBytes = in.readLong();
         mNetworkUploadBytes = in.readLong();
         mMinimumChunkBytes = in.readLong();
diff --git a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
index afcbe7d..311a9b2 100644
--- a/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
+++ b/apex/jobscheduler/framework/java/android/app/job/UserVisibleJobSummary.java
@@ -17,9 +17,12 @@
 package android.app.job;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Objects;
+
 /**
  * Summary of a scheduled job that the user is meant to be aware of.
  *
@@ -30,13 +33,16 @@
     private final int mSourceUserId;
     @NonNull
     private final String mSourcePackageName;
+    @Nullable
+    private final String mNamespace;
     private final int mJobId;
 
     public UserVisibleJobSummary(int callingUid, int sourceUserId,
-            @NonNull String sourcePackageName, int jobId) {
+            @NonNull String sourcePackageName, String namespace, int jobId) {
         mCallingUid = callingUid;
         mSourceUserId = sourceUserId;
         mSourcePackageName = sourcePackageName;
+        mNamespace = namespace;
         mJobId = jobId;
     }
 
@@ -44,6 +50,7 @@
         mCallingUid = in.readInt();
         mSourceUserId = in.readInt();
         mSourcePackageName = in.readString();
+        mNamespace = in.readString();
         mJobId = in.readInt();
     }
 
@@ -55,6 +62,10 @@
         return mJobId;
     }
 
+    public String getNamespace() {
+        return mNamespace;
+    }
+
     public int getSourceUserId() {
         return mSourceUserId;
     }
@@ -71,6 +82,7 @@
         return mCallingUid == that.mCallingUid
                 && mSourceUserId == that.mSourceUserId
                 && mSourcePackageName.equals(that.mSourcePackageName)
+                && Objects.equals(mNamespace, that.mNamespace)
                 && mJobId == that.mJobId;
     }
 
@@ -80,6 +92,9 @@
         result = 31 * result + mCallingUid;
         result = 31 * result + mSourceUserId;
         result = 31 * result + mSourcePackageName.hashCode();
+        if (mNamespace != null) {
+            result = 31 * result + mNamespace.hashCode();
+        }
         result = 31 * result + mJobId;
         return result;
     }
@@ -90,6 +105,7 @@
                 + "callingUid=" + mCallingUid
                 + ", sourceUserId=" + mSourceUserId
                 + ", sourcePackageName='" + mSourcePackageName + "'"
+                + ", namespace=" + mNamespace
                 + ", jobId=" + mJobId
                 + "}";
     }
@@ -104,6 +120,7 @@
         dest.writeInt(mCallingUid);
         dest.writeInt(mSourceUserId);
         dest.writeString(mSourcePackageName);
+        dest.writeString(mNamespace);
         dest.writeInt(mJobId);
     }
 
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 397d2c4..b806ef8 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -615,7 +615,7 @@
     private boolean isSimilarJobRunningLocked(JobStatus job) {
         for (int i = mRunningJobs.size() - 1; i >= 0; --i) {
             JobStatus js = mRunningJobs.valueAt(i);
-            if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) {
+            if (job.matches(js.getUid(), js.getNamespace(), js.getJobId())) {
                 return true;
             }
         }
@@ -1687,12 +1687,12 @@
 
     @GuardedBy("mLock")
     boolean executeTimeoutCommandLocked(PrintWriter pw, String pkgName, int userId,
-            boolean hasJobId, int jobId) {
+            @Nullable String namespace, boolean hasJobId, int jobId) {
         boolean foundSome = false;
         for (int i = 0; i < mActiveServices.size(); i++) {
             final JobServiceContext jc = mActiveServices.get(i);
             final JobStatus js = jc.getRunningJobLocked();
-            if (jc.timeoutIfExecutingLocked(pkgName, userId, hasJobId, jobId, "shell")) {
+            if (jc.timeoutIfExecutingLocked(pkgName, userId, namespace, hasJobId, jobId, "shell")) {
                 foundSome = true;
                 pw.print("Timing out: ");
                 js.printUniqueId(pw);
@@ -1709,11 +1709,13 @@
      */
     @Nullable
     @GuardedBy("mLock")
-    Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid, int jobId) {
+    Pair<Long, Long> getEstimatedNetworkBytesLocked(String pkgName, int uid,
+            String namespace, int jobId) {
         for (int i = 0; i < mActiveServices.size(); i++) {
             final JobServiceContext jc = mActiveServices.get(i);
             final JobStatus js = jc.getRunningJobLocked();
-            if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+            if (js != null && js.matches(uid, namespace, jobId)
+                    && js.getSourcePackageName().equals(pkgName)) {
                 return jc.getEstimatedNetworkBytes();
             }
         }
@@ -1726,11 +1728,13 @@
      */
     @Nullable
     @GuardedBy("mLock")
-    Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid, int jobId) {
+    Pair<Long, Long> getTransferredNetworkBytesLocked(String pkgName, int uid,
+            String namespace, int jobId) {
         for (int i = 0; i < mActiveServices.size(); i++) {
             final JobServiceContext jc = mActiveServices.get(i);
             final JobStatus js = jc.getRunningJobLocked();
-            if (js != null && js.matches(uid, jobId) && js.getSourcePackageName().equals(pkgName)) {
+            if (js != null && js.matches(uid, namespace, jobId)
+                    && js.getSourcePackageName().equals(pkgName)) {
                 return jc.getTransferredNetworkBytes();
             }
         }
@@ -1753,6 +1757,9 @@
         pendingJobQueue.resetIterator();
         while ((js = pendingJobQueue.next()) != null) {
             s.append("(")
+                    .append("{")
+                    .append(js.getNamespace())
+                    .append("} ")
                     .append(js.getJob().getId())
                     .append(", ")
                     .append(js.getUid())
@@ -1777,6 +1784,9 @@
                 if (job == null) {
                     s.append("nothing");
                 } else {
+                    if (job.getNamespace() != null) {
+                        s.append(job.getNamespace()).append(":");
+                    }
                     s.append(job.getJobId()).append("/").append(job.getUid());
                 }
                 s.append(")");
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 1147e07..8defa16 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -89,6 +89,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseArrayMap;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.SparseSetArray;
@@ -149,6 +150,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
@@ -377,8 +379,12 @@
     @GuardedBy("mLock")
     private final ArraySet<JobStatus> mChangedJobList = new ArraySet<>();
 
+    /**
+     * Cached pending job reasons. Mapping from UID -> namespace -> job ID -> reason.
+     */
     @GuardedBy("mPendingJobReasonCache") // Use its own lock to avoid blocking JS processing
-    private final SparseArray<SparseIntArray> mPendingJobReasonCache = new SparseArray<>();
+    private final SparseArrayMap<String, SparseIntArray> mPendingJobReasonCache =
+            new SparseArrayMap<>();
 
     /**
      * Named indices into standby bucket arrays, for clarity in referring to
@@ -1333,7 +1339,7 @@
     private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
 
     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
-            int userId, String tag) {
+            int userId, @Nullable String namespace, String tag) {
         // Rate limit excessive schedule() calls.
         final String servicePkg = job.getService().getPackageName();
         if (job.isPersisted() && (packageName == null || packageName.equals(servicePkg))) {
@@ -1392,7 +1398,7 @@
         }
 
         synchronized (mLock) {
-            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
+            final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, namespace, job.getId());
 
             if (work != null && toCancel != null) {
                 // Fast path: we are adding work to an existing job, and the JobInfo is not
@@ -1400,6 +1406,7 @@
                 if (toCancel.getJob().equals(job)) {
 
                     toCancel.enqueueWorkLocked(work);
+                    mJobs.touchJob(toCancel);
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
@@ -1409,7 +1416,8 @@
                 }
             }
 
-            JobStatus jobStatus = JobStatus.createFromJobInfo(job, uId, packageName, userId, tag);
+            JobStatus jobStatus =
+                    JobStatus.createFromJobInfo(job, uId, packageName, userId, namespace, tag);
 
             // Return failure early if expedited job quota used up.
             if (jobStatus.isRequestedExpeditedJob()) {
@@ -1504,26 +1512,47 @@
         return JobScheduler.RESULT_SUCCESS;
     }
 
-    public List<JobInfo> getPendingJobs(int uid) {
+    private ArrayMap<String, List<JobInfo>> getPendingJobs(int uid) {
+        final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>();
+        synchronized (mLock) {
+            ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
+            // Write out for loop to avoid addAll() creating an Iterator.
+            for (int i = jobs.size() - 1; i >= 0; i--) {
+                final JobStatus job = jobs.valueAt(i);
+                List<JobInfo> outList = outMap.get(job.getNamespace());
+                if (outList == null) {
+                    outList = new ArrayList<JobInfo>(jobs.size());
+                    outMap.put(job.getNamespace(), outList);
+                }
+
+                outList.add(job.getJob());
+            }
+            return outMap;
+        }
+    }
+
+    private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
             ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size());
             // Write out for loop to avoid addAll() creating an Iterator.
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 final JobStatus job = jobs.valueAt(i);
-                outList.add(job.getJob());
+                if (Objects.equals(namespace, job.getNamespace())) {
+                    outList.add(job.getJob());
+                }
             }
             return outList;
         }
     }
 
     @JobScheduler.PendingJobReason
-    private int getPendingJobReason(int uid, int jobId) {
+    private int getPendingJobReason(int uid, String namespace, 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);
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
             if (jobIdToReason != null) {
                 reason = jobIdToReason.get(jobId, JobScheduler.PENDING_JOB_REASON_UNDEFINED);
                 if (reason != JobScheduler.PENDING_JOB_REASON_UNDEFINED) {
@@ -1532,16 +1561,17 @@
             }
         }
         synchronized (mLock) {
-            reason = getPendingJobReasonLocked(uid, jobId);
+            reason = getPendingJobReasonLocked(uid, namespace, jobId);
             if (DEBUG) {
-                Slog.v(TAG, "getPendingJobReason(" + uid + "," + jobId + ")=" + reason);
+                Slog.v(TAG, "getPendingJobReason("
+                        + uid + "," + namespace + "," + jobId + ")=" + reason);
             }
         }
         synchronized (mPendingJobReasonCache) {
-            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid);
+            SparseIntArray jobIdToReason = mPendingJobReasonCache.get(uid, namespace);
             if (jobIdToReason == null) {
                 jobIdToReason = new SparseIntArray();
-                mPendingJobReasonCache.put(uid, jobIdToReason);
+                mPendingJobReasonCache.add(uid, namespace, jobIdToReason);
             }
             jobIdToReason.put(jobId, reason);
         }
@@ -1550,10 +1580,10 @@
 
     @JobScheduler.PendingJobReason
     @GuardedBy("mLock")
-    private int getPendingJobReasonLocked(int uid, int jobId) {
+    private int getPendingJobReasonLocked(int uid, String namespace, int jobId) {
         // Very similar code to isReadyToBeExecutedLocked.
 
-        JobStatus job = mJobs.getJobByUidAndJobId(uid, jobId);
+        JobStatus job = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
         if (job == null) {
             // Job doesn't exist.
             return JobScheduler.PENDING_JOB_REASON_INVALID_JOB_ID;
@@ -1645,12 +1675,12 @@
         return JobScheduler.PENDING_JOB_REASON_UNDEFINED;
     }
 
-    public JobInfo getPendingJob(int uid, int jobId) {
+    private JobInfo getPendingJob(int uid, @Nullable String namespace, int jobId) {
         synchronized (mLock) {
             ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid);
             for (int i = jobs.size() - 1; i >= 0; i--) {
                 JobStatus job = jobs.valueAt(i);
-                if (job.getJobId() == jobId) {
+                if (job.getJobId() == jobId && Objects.equals(namespace, job.getNamespace())) {
                     return job.getJob();
                 }
             }
@@ -1726,12 +1756,20 @@
      * This will remove the job from the master list, and cancel the job if it was staged for
      * execution or being executed.
      *
-     * @param uid Uid to check against for removal of a job.
+     * @param uid              Uid to check against for removal of a job.
      * @param includeSourceApp Whether to include jobs scheduled for this UID by another UID.
      *                         If false, only jobs scheduled by this UID will be cancelled.
      */
     public boolean cancelJobsForUid(int uid, boolean includeSourceApp,
             @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+        return cancelJobsForUid(uid, includeSourceApp,
+                /* namespaceOnly */ false, /* namespace */ null,
+                reason, internalReasonCode, debugReason);
+    }
+
+    private boolean cancelJobsForUid(int uid, boolean includeSourceApp,
+            boolean namespaceOnly, @Nullable String namespace,
+            @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
             return false;
@@ -1748,8 +1786,10 @@
             }
             for (int i = 0; i < jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.valueAt(i);
-                cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
-                jobsCanceled = true;
+                if (!namespaceOnly || Objects.equals(namespace, toRemove.getNamespace())) {
+                    cancelJobImplLocked(toRemove, null, reason, internalReasonCode, debugReason);
+                    jobsCanceled = true;
+                }
             }
         }
         return jobsCanceled;
@@ -1763,11 +1803,11 @@
      * @param uid   Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    private boolean cancelJob(int uid, int jobId, int callingUid,
+    private boolean cancelJob(int uid, String namespace, int jobId, int callingUid,
             @JobParameters.StopReason int reason) {
         JobStatus toCancel;
         synchronized (mLock) {
-            toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
+            toCancel = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
             if (toCancel != null) {
                 cancelJobImplLocked(toCancel, null, reason,
                         JobParameters.INTERNAL_STOP_REASON_CANCELED,
@@ -2197,7 +2237,8 @@
         jobStatus.stopTrackingJobLocked(incomingJob);
 
         synchronized (mPendingJobReasonCache) {
-            SparseIntArray reasonCache = mPendingJobReasonCache.get(jobStatus.getUid());
+            SparseIntArray reasonCache =
+                    mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
             if (reasonCache != null) {
                 reasonCache.delete(jobStatus.getJobId());
             }
@@ -2228,7 +2269,8 @@
     /** 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());
+            final SparseIntArray reasons =
+                    mPendingJobReasonCache.get(jobStatus.getUid(), jobStatus.getNamespace());
             if (reasons != null) {
                 reasons.delete(jobStatus.getJobId());
             }
@@ -2490,7 +2532,8 @@
             if (DEBUG) {
                 Slog.d(TAG, "Could not find job to remove. Was job removed while executing?");
             }
-            JobStatus newJs = mJobs.getJobByUidAndJobId(jobStatus.getUid(), jobStatus.getJobId());
+            JobStatus newJs = mJobs.getJobByUidAndJobId(
+                    jobStatus.getUid(), jobStatus.getNamespace(), jobStatus.getJobId());
             if (newJs != null) {
                 // This job was stopped because the app scheduled a new job with the same job ID.
                 // Check if the new job is ready to run.
@@ -2947,10 +2990,12 @@
                     synchronized (mPendingJobReasonCache) {
                         for (int i = 0; i < numRunnableJobs; ++i) {
                             final JobStatus job = runnableJobs.get(i);
-                            SparseIntArray reasons = mPendingJobReasonCache.get(job.getUid());
+                            SparseIntArray reasons =
+                                    mPendingJobReasonCache.get(job.getUid(), job.getNamespace());
                             if (reasons == null) {
                                 reasons = new SparseIntArray();
-                                mPendingJobReasonCache.put(job.getUid(), reasons);
+                                mPendingJobReasonCache
+                                        .add(job.getUid(), job.getNamespace(), reasons);
                             }
                             // We're force batching these jobs, so consider it an optimization
                             // policy reason.
@@ -3660,11 +3705,8 @@
             return canPersist;
         }
 
-        private void validateJob(JobInfo job, int callingUid) {
-            validateJob(job, callingUid, null);
-        }
-
-        private void validateJob(JobInfo job, int callingUid, @Nullable JobWorkItem jobWorkItem) {
+        private int validateJob(@NonNull JobInfo job, int callingUid, int sourceUserId,
+                @Nullable String sourcePkgName, @Nullable JobWorkItem jobWorkItem) {
             final boolean rejectNegativeNetworkEstimates = CompatChanges.isChangeEnabled(
                             JobInfo.REJECT_NEGATIVE_NETWORK_ESTIMATES, callingUid);
             job.enforceValidity(
@@ -3684,6 +3726,37 @@
                             + " FLAG_EXEMPT_FROM_APP_STANDBY. Job=" + job);
                 }
             }
+            if (job.isUserInitiated()) {
+                int sourceUid = -1;
+                if (sourceUserId != -1 && sourcePkgName != null) {
+                    try {
+                        sourceUid = AppGlobals.getPackageManager().getPackageUid(
+                                sourcePkgName, 0, sourceUserId);
+                    } catch (RemoteException ex) {
+                        // Can't happen, PackageManager runs in the same process.
+                    }
+                }
+                // We aim to check the permission of both the source and calling app so that apps
+                // don't attempt to bypass the permission by using other apps to do the work.
+                if (sourceUid != -1) {
+                    // Check the permission of the source app.
+                    final int sourceResult =
+                            validateRunLongJobsPermission(sourceUid, sourcePkgName);
+                    if (sourceResult != JobScheduler.RESULT_SUCCESS) {
+                        return sourceResult;
+                    }
+                }
+                final String callingPkgName = job.getService().getPackageName();
+                if (callingUid != sourceUid || !callingPkgName.equals(sourcePkgName)) {
+                    // Source app is different from calling app. Make sure the calling app also has
+                    // the permission.
+                    final int callingResult =
+                            validateRunLongJobsPermission(callingUid, callingPkgName);
+                    if (callingResult != JobScheduler.RESULT_SUCCESS) {
+                        return callingResult;
+                    }
+                }
+            }
             if (jobWorkItem != null) {
                 jobWorkItem.enforceValidity(rejectNegativeNetworkEstimates);
                 if (jobWorkItem.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN
@@ -3703,12 +3776,33 @@
                         }
                     }
                 }
+                if (job.isPersisted()) {
+                    // Intent.saveToXml() doesn't persist everything, so just reject all
+                    // JobWorkItems with Intents to be safe/predictable.
+                    if (jobWorkItem.getIntent() != null) {
+                        throw new IllegalArgumentException(
+                                "Cannot persist JobWorkItems with Intents");
+                    }
+                }
             }
+            return JobScheduler.RESULT_SUCCESS;
+        }
+
+        private int validateRunLongJobsPermission(int uid, String packageName) {
+            final int state = getRunLongJobsPermissionState(uid, packageName);
+            if (state == PermissionChecker.PERMISSION_HARD_DENIED) {
+                throw new SecurityException(android.Manifest.permission.RUN_LONG_JOBS
+                        + " required to schedule user-initiated jobs.");
+            }
+            if (state == PermissionChecker.PERMISSION_SOFT_DENIED) {
+                return JobScheduler.RESULT_FAILURE;
+            }
+            return JobScheduler.RESULT_SUCCESS;
         }
 
         // IJobScheduler implementation
         @Override
-        public int schedule(JobInfo job) throws RemoteException {
+        public int schedule(String namespace, JobInfo job) throws RemoteException {
             if (DEBUG) {
                 Slog.d(TAG, "Scheduling job: " + job.toString());
             }
@@ -3724,12 +3818,19 @@
                 }
             }
 
-            validateJob(job, uid);
+            final int result = validateJob(job, uid, -1, null, null);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
+
+            if (namespace != null) {
+                namespace = namespace.intern();
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
-                        null);
+                        namespace, null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -3737,7 +3838,7 @@
 
         // IJobScheduler implementation
         @Override
-        public int enqueue(JobInfo job, JobWorkItem work) throws RemoteException {
+        public int enqueue(String namespace, JobInfo job, JobWorkItem work) throws RemoteException {
             if (DEBUG) {
                 Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
             }
@@ -3745,27 +3846,31 @@
             final int userId = UserHandle.getUserId(uid);
 
             enforceValidJobRequest(uid, job);
-            if (job.isPersisted()) {
-                throw new IllegalArgumentException("Can't enqueue work for persisted jobs");
-            }
             if (work == null) {
                 throw new NullPointerException("work is null");
             }
 
-            validateJob(job, uid, work);
+            final int result = validateJob(job, uid, -1, null, work);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
+
+            if (namespace != null) {
+                namespace = namespace.intern();
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
-                        null);
+                        namespace, null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
         @Override
-        public int scheduleAsPackage(JobInfo job, String packageName, int userId, String tag)
-                throws RemoteException {
+        public int scheduleAsPackage(String namespace, JobInfo job, String packageName, int userId,
+                String tag) throws RemoteException {
             final int callerUid = Binder.getCallingUid();
             if (DEBUG) {
                 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
@@ -3783,48 +3888,75 @@
                         + " not permitted to schedule jobs for other apps");
             }
 
-            validateJob(job, callerUid);
+            int result = validateJob(job, callerUid, userId, packageName, null);
+            if (result != JobScheduler.RESULT_SUCCESS) {
+                return result;
+            }
+
+            if (namespace != null) {
+                namespace = namespace.intern();
+            }
 
             final long ident = Binder.clearCallingIdentity();
             try {
                 return JobSchedulerService.this.scheduleAsPackage(job, null, callerUid,
-                        packageName, userId, tag);
+                        packageName, userId, namespace, tag);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
         @Override
-        public ParceledListSlice<JobInfo> getAllPendingJobs() throws RemoteException {
+        public Map<String, ParceledListSlice<JobInfo>> getAllPendingJobs() throws RemoteException {
             final int uid = Binder.getCallingUid();
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                return new ParceledListSlice<>(JobSchedulerService.this.getPendingJobs(uid));
+                final ArrayMap<String, List<JobInfo>> jobs =
+                        JobSchedulerService.this.getPendingJobs(uid);
+                final ArrayMap<String, ParceledListSlice<JobInfo>> outMap = new ArrayMap<>();
+                for (int i = 0; i < jobs.size(); ++i) {
+                    outMap.put(jobs.keyAt(i), new ParceledListSlice<>(jobs.valueAt(i)));
+                }
+                return outMap;
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
         @Override
-        public int getPendingJobReason(int jobId) throws RemoteException {
+        public ParceledListSlice<JobInfo> getAllPendingJobsInNamespace(String namespace)
+                throws RemoteException {
             final int uid = Binder.getCallingUid();
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.getPendingJobReason(uid, jobId);
+                return new ParceledListSlice<>(
+                        JobSchedulerService.this.getPendingJobsInNamespace(uid, namespace));
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
 
         @Override
-        public JobInfo getPendingJob(int jobId) throws RemoteException {
+        public JobInfo getPendingJob(String namespace, int jobId) throws RemoteException {
             final int uid = Binder.getCallingUid();
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.getPendingJob(uid, jobId);
+                return JobSchedulerService.this.getPendingJob(uid, namespace, jobId);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public int getPendingJobReason(String namespace, int jobId) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                return JobSchedulerService.this.getPendingJobReason(uid, namespace, jobId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -3847,12 +3979,29 @@
         }
 
         @Override
-        public void cancel(int jobId) throws RemoteException {
+        public void cancelAllInNamespace(String namespace) throws RemoteException {
+            final int uid = Binder.getCallingUid();
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                JobSchedulerService.this.cancelJobsForUid(uid,
+                        // Documentation says only jobs scheduled BY the app will be cancelled
+                        /* includeSourceApp */ false,
+                        /* namespaceOnly */ true, namespace,
+                        JobParameters.STOP_REASON_CANCELLED_BY_APP,
+                        JobParameters.INTERNAL_STOP_REASON_CANCELED,
+                        "cancelAllInNamespace() called by app, callingUid=" + uid);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override
+        public void cancel(String namespace, int jobId) throws RemoteException {
             final int uid = Binder.getCallingUid();
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                JobSchedulerService.this.cancelJob(uid, jobId, uid,
+                JobSchedulerService.this.cancelJob(uid, namespace, jobId, uid,
                         JobParameters.STOP_REASON_CANCELLED_BY_APP);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -4030,8 +4179,9 @@
     }
 
     // Shell command infrastructure: run the given job immediately
-    int executeRunCommand(String pkgName, int userId, int jobId, boolean satisfied, boolean force) {
-        Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + userId
+    int executeRunCommand(String pkgName, int userId, @Nullable String namespace,
+            int jobId, boolean satisfied, boolean force) {
+        Slog.d(TAG, "executeRunCommand(): " + pkgName + "/" + namespace + "/" + userId
                 + " " + jobId + " s=" + satisfied + " f=" + force);
 
         try {
@@ -4042,7 +4192,7 @@
             }
 
             synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                 if (js == null) {
                     return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                 }
@@ -4072,14 +4222,14 @@
 
     // Shell command infrastructure: immediately timeout currently executing jobs
     int executeTimeoutCommand(PrintWriter pw, String pkgName, int userId,
-            boolean hasJobId, int jobId) {
+            @Nullable String namespace, boolean hasJobId, int jobId) {
         if (DEBUG) {
             Slog.v(TAG, "executeTimeoutCommand(): " + pkgName + "/" + userId + " " + jobId);
         }
 
         synchronized (mLock) {
             final boolean foundSome = mConcurrencyManager.executeTimeoutCommandLocked(pw,
-                    pkgName, userId, hasJobId, jobId);
+                    pkgName, userId, namespace, hasJobId, jobId);
             if (!foundSome) {
                 pw.println("No matching executing jobs found.");
             }
@@ -4088,7 +4238,7 @@
     }
 
     // Shell command infrastructure: cancel a scheduled job
-    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+    int executeCancelCommand(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
             boolean hasJobId, int jobId) {
         if (DEBUG) {
             Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
@@ -4116,7 +4266,8 @@
             }
         } else {
             pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
-            if (!cancelJob(pkgUid, jobId, Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
+            if (!cancelJob(pkgUid, namespace, jobId,
+                    Process.SHELL_UID, JobParameters.STOP_REASON_USER)) {
                 pw.println("No matching job found.");
             }
         }
@@ -4163,8 +4314,8 @@
         }
     }
 
-    int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
-            int byteOption) {
+    int getEstimatedNetworkBytes(PrintWriter pw, String pkgName, int userId, String namespace,
+            int jobId, int byteOption) {
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4176,9 +4327,10 @@
             }
 
             synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                 if (DEBUG) {
-                    Slog.d(TAG, "get-estimated-network-bytes " + uid + "/" + jobId + ": " + js);
+                    Slog.d(TAG, "get-estimated-network-bytes " + uid + "/"
+                            + namespace + "/" + jobId + ": " + js);
                 }
                 if (js == null) {
                     pw.print("unknown("); UserHandle.formatUid(pw, uid);
@@ -4188,8 +4340,8 @@
 
                 final long downloadBytes;
                 final long uploadBytes;
-                final Pair<Long, Long> bytes =
-                        mConcurrencyManager.getEstimatedNetworkBytesLocked(pkgName, uid, jobId);
+                final Pair<Long, Long> bytes = mConcurrencyManager.getEstimatedNetworkBytesLocked(
+                        pkgName, uid, namespace, jobId);
                 if (bytes == null) {
                     downloadBytes = js.getEstimatedNetworkDownloadBytes();
                     uploadBytes = js.getEstimatedNetworkUploadBytes();
@@ -4210,8 +4362,8 @@
         return 0;
     }
 
-    int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, int jobId,
-            int byteOption) {
+    int getTransferredNetworkBytes(PrintWriter pw, String pkgName, int userId, String namespace,
+            int jobId, int byteOption) {
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4223,9 +4375,10 @@
             }
 
             synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
                 if (DEBUG) {
-                    Slog.d(TAG, "get-transferred-network-bytes " + uid + "/" + jobId + ": " + js);
+                    Slog.d(TAG, "get-transferred-network-bytes " + uid
+                            + namespace + "/" + "/" + jobId + ": " + js);
                 }
                 if (js == null) {
                     pw.print("unknown("); UserHandle.formatUid(pw, uid);
@@ -4235,8 +4388,8 @@
 
                 final long downloadBytes;
                 final long uploadBytes;
-                final Pair<Long, Long> bytes =
-                        mConcurrencyManager.getTransferredNetworkBytesLocked(pkgName, uid, jobId);
+                final Pair<Long, Long> bytes = mConcurrencyManager.getTransferredNetworkBytesLocked(
+                        pkgName, uid, namespace, jobId);
                 if (bytes == null) {
                     downloadBytes = 0;
                     uploadBytes = 0;
@@ -4257,11 +4410,16 @@
         return 0;
     }
 
+    /** Returns true if both the appop and permission are granted. */
     private boolean checkRunLongJobsPermission(int packageUid, String packageName) {
-        // Returns true if both the appop and permission are granted.
+        return getRunLongJobsPermissionState(packageUid, packageName)
+                == PermissionChecker.PERMISSION_GRANTED;
+    }
+
+    private int getRunLongJobsPermissionState(int packageUid, String packageName) {
         return PermissionChecker.checkPermissionForPreflight(getTestableContext(),
                 android.Manifest.permission.RUN_LONG_JOBS, PermissionChecker.PID_UNKNOWN,
-                packageUid, packageName) == PermissionChecker.PERMISSION_GRANTED;
+                packageUid, packageName);
     }
 
     @VisibleForTesting
@@ -4280,7 +4438,8 @@
     }
 
     // Shell command infrastructure
-    int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
+    int getJobState(PrintWriter pw, String pkgName, int userId, @Nullable String namespace,
+            int jobId) {
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
                     userId != UserHandle.USER_ALL ? userId : UserHandle.USER_SYSTEM);
@@ -4292,11 +4451,17 @@
             }
 
             synchronized (mLock) {
-                final JobStatus js = mJobs.getJobByUidAndJobId(uid, jobId);
-                if (DEBUG) Slog.d(TAG, "get-job-state " + uid + "/" + jobId + ": " + js);
+                final JobStatus js = mJobs.getJobByUidAndJobId(uid, namespace, jobId);
+                if (DEBUG) {
+                    Slog.d(TAG,
+                            "get-job-state " + namespace + "/" + uid + "/" + jobId + ": " + js);
+                }
                 if (js == null) {
-                    pw.print("unknown("); UserHandle.formatUid(pw, uid);
-                    pw.print("/jid"); pw.print(jobId); pw.println(")");
+                    pw.print("unknown(");
+                    UserHandle.formatUid(pw, uid);
+                    pw.print("/jid");
+                    pw.print(jobId);
+                    pw.println(")");
                     return JobSchedulerShellCommand.CMD_ERR_NO_JOB;
                 }
 
@@ -4472,7 +4637,9 @@
                     }
                     jobPrinted = true;
 
-                    pw.print("JOB #"); job.printUniqueId(pw); pw.print(": ");
+                    pw.print("JOB ");
+                    job.printUniqueId(pw);
+                    pw.print(": ");
                     pw.println(job.toShortStringExceptUniqueId());
 
                     pw.increaseIndent();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index 36ba8dd..2eeb25e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -16,6 +16,7 @@
 
 package com.android.server.job;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.pm.IPackageManager;
@@ -107,7 +108,8 @@
         }
     }
 
-    private boolean printError(int errCode, String pkgName, int userId, int jobId) {
+    private boolean printError(int errCode, String pkgName, int userId, @Nullable String namespace,
+            int jobId) {
         PrintWriter pw;
         switch (errCode) {
             case CMD_ERR_NO_PACKAGE:
@@ -124,6 +126,10 @@
                 pw.print(jobId);
                 pw.print(" in package ");
                 pw.print(pkgName);
+                if (namespace != null) {
+                    pw.print(" / namespace ");
+                    pw.print(namespace);
+                }
                 pw.print(" / user ");
                 pw.println(userId);
                 return true;
@@ -134,6 +140,10 @@
                 pw.print(jobId);
                 pw.print(" in package ");
                 pw.print(pkgName);
+                if (namespace != null) {
+                    pw.print(" / namespace ");
+                    pw.print(namespace);
+                }
                 pw.print(" / user ");
                 pw.print(userId);
                 pw.println(" has functional constraints but --force not specified");
@@ -150,6 +160,7 @@
         boolean force = false;
         boolean satisfied = false;
         int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -169,6 +180,11 @@
                     userId = Integer.parseInt(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -185,8 +201,9 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            int ret = mInternal.executeRunCommand(pkgName, userId, jobId, satisfied, force);
-            if (printError(ret, pkgName, userId, jobId)) {
+            int ret = mInternal.executeRunCommand(pkgName, userId, namespace,
+                    jobId, satisfied, force);
+            if (printError(ret, pkgName, userId, namespace, jobId)) {
                 return ret;
             }
 
@@ -207,6 +224,7 @@
         checkPermission("force timeout jobs");
 
         int userId = UserHandle.USER_ALL;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -216,6 +234,11 @@
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -232,7 +255,8 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mInternal.executeTimeoutCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+            return mInternal.executeTimeoutCommand(pw, pkgName, userId, namespace,
+                    jobIdStr != null, jobId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -242,6 +266,7 @@
         checkPermission("cancel jobs");
 
         int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -251,6 +276,11 @@
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -268,7 +298,8 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+            return mInternal.executeCancelCommand(pw, pkgName, userId, namespace,
+                    jobIdStr != null, jobId);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -319,6 +350,7 @@
         checkPermission("get estimated bytes");
 
         int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -328,6 +360,11 @@
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -344,8 +381,9 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, jobId, byteOption);
-            printError(ret, pkgName, userId, jobId);
+            int ret = mInternal.getEstimatedNetworkBytes(pw, pkgName, userId, namespace,
+                    jobId, byteOption);
+            printError(ret, pkgName, userId, namespace, jobId);
             return ret;
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -368,6 +406,7 @@
         checkPermission("get transferred bytes");
 
         int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -377,6 +416,11 @@
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -393,8 +437,9 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, jobId, byteOption);
-            printError(ret, pkgName, userId, jobId);
+            int ret = mInternal.getTransferredNetworkBytes(pw, pkgName, userId, namespace,
+                    jobId, byteOption);
+            printError(ret, pkgName, userId, namespace, jobId);
             return ret;
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -405,6 +450,7 @@
         checkPermission("get job state");
 
         int userId = UserHandle.USER_SYSTEM;
+        String namespace = null;
 
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -414,6 +460,11 @@
                     userId = UserHandle.parseUserArg(getNextArgRequired());
                     break;
 
+                case "-n":
+                case "--namespace":
+                    namespace = getNextArgRequired();
+                    break;
+
                 default:
                     pw.println("Error: unknown option '" + opt + "'");
                     return -1;
@@ -430,8 +481,8 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
-            int ret = mInternal.getJobState(pw, pkgName, userId, jobId);
-            printError(ret, pkgName, userId, jobId);
+            int ret = mInternal.getJobState(pw, pkgName, userId, namespace, jobId);
+            printError(ret, pkgName, userId, namespace, jobId);
             return ret;
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -521,7 +572,8 @@
         pw.println("Job scheduler (jobscheduler) commands:");
         pw.println("  help");
         pw.println("    Print this help text.");
-        pw.println("  run [-f | --force] [-s | --satisfied] [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  run [-f | --force] [-s | --satisfied] [-u | --user USER_ID]"
+                + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
         pw.println("    Trigger immediate execution of a specific scheduled job. For historical");
         pw.println("    reasons, some constraints, such as battery, are ignored when this");
         pw.println("    command is called. If you don't want any constraints to be ignored,");
@@ -530,23 +582,30 @@
         pw.println("      -f or --force: run the job even if technical constraints such as");
         pw.println("         connectivity are not currently met. This is incompatible with -f ");
         pw.println("         and so an error will be reported if both are given.");
+        pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
+        pw.println("         is null (no namespace).");
         pw.println("      -s or --satisfied: run the job only if all constraints are met.");
         pw.println("         This is incompatible with -f and so an error will be reported");
         pw.println("         if both are given.");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
-        pw.println("  timeout [-u | --user USER_ID] [PACKAGE] [JOB_ID]");
+        pw.println("  timeout [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
+                + " [PACKAGE] [JOB_ID]");
         pw.println("    Trigger immediate timeout of currently executing jobs, as if their.");
         pw.println("    execution timeout had expired.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
-        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+        pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
+        pw.println("         is null (no namespace).");
+        pw.println("  cancel [-u | --user USER_ID] [-n | --namespace NAMESPACE] PACKAGE [JOB_ID]");
         pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
         pw.println("    by that package will be canceled.  USE WITH CAUTION.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
+        pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
+        pw.println("         is null (no namespace).");
         pw.println("  heartbeat [num]");
         pw.println("    No longer used.");
         pw.println("  monitor-battery [on|off]");
@@ -558,12 +617,14 @@
         pw.println("    Return whether the battery is currently considered to be charging.");
         pw.println("  get-battery-not-low");
         pw.println("    Return whether the battery is currently considered to not be low.");
-        pw.println("  get-estimated-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  get-estimated-download-bytes [-u | --user USER_ID]"
+                + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
         pw.println("    Return the most recent estimated download bytes for the job.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
-        pw.println("  get-estimated-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  get-estimated-upload-bytes [-u | --user USER_ID]"
+                + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
         pw.println("    Return the most recent estimated upload bytes for the job.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
@@ -572,17 +633,20 @@
         pw.println("    Return the last storage update sequence number that was received.");
         pw.println("  get-storage-not-low");
         pw.println("    Return whether storage is currently considered to not be low.");
-        pw.println("  get-transferred-download-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  get-transferred-download-bytes [-u | --user USER_ID]"
+                + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
         pw.println("    Return the most recent transferred download bytes for the job.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
-        pw.println("  get-transferred-upload-bytes [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  get-transferred-upload-bytes [-u | --user USER_ID]"
+                + " [-n | --namespace NAMESPACE] PACKAGE JOB_ID");
         pw.println("    Return the most recent transferred upload bytes for the job.");
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
-        pw.println("  get-job-state [-u | --user USER_ID] PACKAGE JOB_ID");
+        pw.println("  get-job-state [-u | --user USER_ID] [-n | --namespace NAMESPACE]"
+                + " PACKAGE JOB_ID");
         pw.println("    Return the current state of a job, may be any combination of:");
         pw.println("      pending: currently on the pending list, waiting to be active");
         pw.println("      active: job is actively running");
@@ -594,6 +658,8 @@
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         the primary or system user");
+        pw.println("      -n or --namespace: specify the namespace this job sits in; the default");
+        pw.println("         is null (no namespace).");
         pw.println("  trigger-dock-state [idle|active]");
         pw.println("    Trigger wireless charging dock state.  Active by default.");
         pw.println();
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 15fc3c9..ce7da86 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -64,6 +64,8 @@
 import com.android.server.tare.EconomyManagerInternal;
 import com.android.server.tare.JobSchedulerEconomicPolicy;
 
+import java.util.Objects;
+
 /**
  * Handles client binding and lifecycle of a job. Jobs execute one at a time on an instance of this
  * class.
@@ -304,7 +306,8 @@
                 job.changedAuthorities.toArray(triggeredAuthorities);
             }
             final JobInfo ji = job.getJob();
-            mParams = new JobParameters(mRunningCallback, job.getJobId(), ji.getExtras(),
+            mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(),
+                    ji.getExtras(),
                     ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(),
                     isDeadlineExpired, job.shouldTreatAsExpeditedJob(),
                     job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities,
@@ -518,11 +521,12 @@
     }
 
     @GuardedBy("mLock")
-    boolean timeoutIfExecutingLocked(String pkgName, int userId, boolean matchJobId, int jobId,
-            String reason) {
+    boolean timeoutIfExecutingLocked(String pkgName, int userId, @Nullable String namespace,
+            boolean matchJobId, int jobId, String reason) {
         final JobStatus executing = getRunningJobLocked();
         if (executing != null && (userId == UserHandle.USER_ALL || userId == executing.getUserId())
                 && (pkgName == null || pkgName.equals(executing.getSourcePackageName()))
+                && Objects.equals(namespace, executing.getNamespace())
                 && (!matchJobId || jobId == executing.getJobId())) {
             if (mVerb == VERB_EXECUTING) {
                 mParams.setStopReason(JobParameters.STOP_REASON_TIMEOUT,
@@ -611,6 +615,9 @@
                             "last work dequeued");
                     // This will finish the job.
                     doCallbackLocked(false, "last work dequeued");
+                } else {
+                    // Delivery count has been updated, so persist JobWorkItem change.
+                    mService.mJobs.touchJob(mRunningJob);
                 }
                 return work;
             }
@@ -628,6 +635,7 @@
                     // Exception-throwing-can down the road to JobParameters.completeWork >:(
                     return true;
                 }
+                mService.mJobs.touchJob(mRunningJob);
                 return mRunningJob.completeWorkLocked(workId);
             }
         } finally {
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 a1153e3..88270494 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -25,6 +25,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.job.JobInfo;
+import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.net.NetworkRequest;
@@ -66,6 +67,7 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.function.Consumer;
@@ -309,6 +311,15 @@
         mJobSet.removeJobsOfUnlistedUsers(keepUserIds);
     }
 
+    /** Note a change in the specified JobStatus that necessitates writing job state to disk. */
+    void touchJob(@NonNull JobStatus jobStatus) {
+        if (!jobStatus.isPersisted()) {
+            return;
+        }
+        mPendingJobWriteUids.put(jobStatus.getUid(), true);
+        maybeWriteStatusToDiskAsync();
+    }
+
     @VisibleForTesting
     public void clear() {
         mJobSet.clear();
@@ -386,8 +397,8 @@
      * @return the JobStatus that matches the provided uId and jobId, or null if none found.
      */
     @Nullable
-    public JobStatus getJobByUidAndJobId(int uid, int jobId) {
-        return mJobSet.get(uid, jobId);
+    public JobStatus getJobByUidAndJobId(int uid, @Nullable String namespace, int jobId) {
+        return mJobSet.get(uid, namespace, jobId);
     }
 
     /**
@@ -429,6 +440,7 @@
     private static final String XML_TAG_PERIODIC = "periodic";
     private static final String XML_TAG_ONEOFF = "one-off";
     private static final String XML_TAG_EXTRAS = "extras";
+    private static final String XML_TAG_JOB_WORK_ITEM = "job-work-item";
 
     private void migrateJobFilesAsync() {
         synchronized (mLock) {
@@ -723,6 +735,7 @@
                     writeConstraintsToXml(out, jobStatus);
                     writeExecutionCriteriaToXml(out, jobStatus);
                     writeBundleToXml(jobStatus.getJob().getExtras(), out);
+                    writeJobWorkItemsToXml(out, jobStatus);
                     out.endTag(null, XML_TAG_JOB);
 
                     numJobs++;
@@ -764,6 +777,9 @@
             if (jobStatus.getSourcePackageName() != null) {
                 out.attribute(null, "sourcePackageName", jobStatus.getSourcePackageName());
             }
+            if (jobStatus.getNamespace() != null) {
+                out.attribute(null, "namespace", jobStatus.getNamespace());
+            }
             if (jobStatus.getSourceTag() != null) {
                 out.attribute(null, "sourceTag", jobStatus.getSourceTag());
             }
@@ -902,6 +918,53 @@
                 out.endTag(null, XML_TAG_ONEOFF);
             }
         }
+
+        private void writeJobWorkItemsToXml(@NonNull TypedXmlSerializer out,
+                @NonNull JobStatus jobStatus) throws IOException, XmlPullParserException {
+            // Write executing first since they're technically at the front of the queue.
+            writeJobWorkItemListToXml(out, jobStatus.executingWork);
+            writeJobWorkItemListToXml(out, jobStatus.pendingWork);
+        }
+
+        private void writeJobWorkItemListToXml(@NonNull TypedXmlSerializer out,
+                @Nullable List<JobWorkItem> jobWorkItems)
+                throws IOException, XmlPullParserException {
+            if (jobWorkItems == null) {
+                return;
+            }
+            // Write the items in list order to maintain the enqueue order.
+            final int size = jobWorkItems.size();
+            for (int i = 0; i < size; ++i) {
+                final JobWorkItem item = jobWorkItems.get(i);
+                if (item.getGrants() != null) {
+                    // We currently don't allow persisting jobs when grants are involved.
+                    // TODO(256618122): allow persisting JobWorkItems with grant flags
+                    continue;
+                }
+                if (item.getIntent() != null) {
+                    // Intent.saveToXml() doesn't persist everything, so we shouldn't attempt to
+                    // persist these JobWorkItems at all.
+                    Slog.wtf(TAG, "Encountered JobWorkItem with Intent in persisting list");
+                    continue;
+                }
+                out.startTag(null, XML_TAG_JOB_WORK_ITEM);
+                out.attributeInt(null, "delivery-count", item.getDeliveryCount());
+                if (item.getEstimatedNetworkDownloadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-download-bytes",
+                            item.getEstimatedNetworkDownloadBytes());
+                }
+                if (item.getEstimatedNetworkUploadBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "estimated-upload-bytes",
+                            item.getEstimatedNetworkUploadBytes());
+                }
+                if (item.getMinimumNetworkChunkBytes() != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                    out.attributeLong(null, "minimum-network-chunk-bytes",
+                            item.getMinimumNetworkChunkBytes());
+                }
+                writeBundleToXml(item.getExtras(), out);
+                out.endTag(null, XML_TAG_JOB_WORK_ITEM);
+            }
+        }
     };
 
     /**
@@ -1135,6 +1198,7 @@
             }
 
             String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
+            final String namespace = parser.getAttributeValue(null, "namespace");
             final String sourceTag = parser.getAttributeValue(null, "sourceTag");
 
             int eventType;
@@ -1257,7 +1321,13 @@
                 Slog.e(TAG, "Persisted extras contained invalid data", e);
                 return null;
             }
-            parser.nextTag(); // Consume </extras>
+            eventType = parser.nextTag(); // Consume </extras>
+
+            List<JobWorkItem> jobWorkItems = null;
+            if (eventType == XmlPullParser.START_TAG
+                    && XML_TAG_JOB_WORK_ITEM.equals(parser.getName())) {
+                jobWorkItems = readJobWorkItemsFromXml(parser);
+            }
 
             final JobInfo builtJob;
             try {
@@ -1292,10 +1362,15 @@
                     sourceUserId, nowElapsed);
             JobStatus js = new JobStatus(
                     builtJob, uid, sourcePackageName, sourceUserId,
-                    appBucket, sourceTag,
+                    appBucket, namespace, sourceTag,
                     elapsedRuntimes.first, elapsedRuntimes.second,
                     lastSuccessfulRunTime, lastFailedRunTime,
                     (rtcIsGood) ? null : rtcRuntimes, internalFlags, /* dynamicConstraints */ 0);
+            if (jobWorkItems != null) {
+                for (int i = 0; i < jobWorkItems.size(); ++i) {
+                    js.enqueueWorkLocked(jobWorkItems.get(i));
+                }
+            }
             return js;
         }
 
@@ -1468,6 +1543,64 @@
                     parser.getAttributeLong(null, "deadline", JobStatus.NO_LATEST_RUNTIME);
             return Pair.create(earliestRunTimeRtc, latestRunTimeRtc);
         }
+
+        @NonNull
+        private List<JobWorkItem> readJobWorkItemsFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            List<JobWorkItem> jobWorkItems = new ArrayList<>();
+
+            for (int eventType = parser.getEventType(); eventType != XmlPullParser.END_DOCUMENT;
+                    eventType = parser.next()) {
+                final String tagName = parser.getName();
+                if (!XML_TAG_JOB_WORK_ITEM.equals(tagName)) {
+                    // We're no longer operating with work items.
+                    break;
+                }
+                try {
+                    JobWorkItem jwi = readJobWorkItemFromXml(parser);
+                    if (jwi != null) {
+                        jobWorkItems.add(jwi);
+                    }
+                } catch (Exception e) {
+                    // If there's an issue with one JobWorkItem, drop only the one item and not the
+                    // whole job.
+                    Slog.e(TAG, "Problem with persisted JobWorkItem", e);
+                }
+            }
+
+            return jobWorkItems;
+        }
+
+        @Nullable
+        private JobWorkItem readJobWorkItemFromXml(TypedXmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            JobWorkItem.Builder jwiBuilder = new JobWorkItem.Builder();
+
+            jwiBuilder
+                    .setDeliveryCount(parser.getAttributeInt(null, "delivery-count"))
+                    .setEstimatedNetworkBytes(
+                            parser.getAttributeLong(null,
+                                    "estimated-download-bytes", JobInfo.NETWORK_BYTES_UNKNOWN),
+                            parser.getAttributeLong(null,
+                                    "estimated-upload-bytes", JobInfo.NETWORK_BYTES_UNKNOWN))
+                    .setMinimumNetworkChunkBytes(parser.getAttributeLong(null,
+                            "minimum-network-chunk-bytes", JobInfo.NETWORK_BYTES_UNKNOWN));
+            parser.next();
+            try {
+                final PersistableBundle extras = PersistableBundle.restoreFromXml(parser);
+                jwiBuilder.setExtras(extras);
+            } catch (IllegalArgumentException e) {
+                Slog.e(TAG, "Persisted extras contained invalid data", e);
+                return null;
+            }
+
+            try {
+                return jwiBuilder.build();
+            } catch (Exception e) {
+                Slog.e(TAG, "Invalid JobWorkItem", e);
+                return null;
+            }
+        }
     }
 
     /** Set of all tracked jobs. */
@@ -1592,12 +1725,12 @@
             return jobs != null && jobs.contains(job);
         }
 
-        public JobStatus get(int uid, int jobId) {
+        public JobStatus get(int uid, @Nullable String namespace, int jobId) {
             ArraySet<JobStatus> jobs = mJobs.get(uid);
             if (jobs != null) {
                 for (int i = jobs.size() - 1; i >= 0; i--) {
                     JobStatus job = jobs.valueAt(i);
-                    if (job.getJobId() == jobId) {
+                    if (job.getJobId() == jobId && Objects.equals(namespace, job.getNamespace())) {
                         return job;
                     }
                 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 0eacfd6..36a26f0 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -28,6 +28,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Objects;
 import java.util.PriorityQueue;
 
 /**
@@ -280,12 +281,14 @@
                 return job1EJ ? -1 : 1;
             }
 
-            final int job1Priority = job1.getEffectivePriority();
-            final int job2Priority = job2.getEffectivePriority();
-            if (job1Priority != job2Priority) {
-                // Use the priority set by an app for intra-app job ordering. Higher
-                // priority should be before lower priority.
-                return Integer.compare(job2Priority, job1Priority);
+            if (Objects.equals(job1.getNamespace(), job2.getNamespace())) {
+                final int job1Priority = job1.getEffectivePriority();
+                final int job2Priority = job2.getEffectivePriority();
+                if (job1Priority != job2Priority) {
+                    // Use the priority set by an app for intra-app job ordering. Higher
+                    // priority should be before lower priority.
+                    return Integer.compare(job2Priority, job1Priority);
+                }
             }
 
             if (job1.lastEvaluatedBias != job2.lastEvaluatedBias) {
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 9b6186e..b0b6a01 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
@@ -30,6 +30,7 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
@@ -70,6 +71,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Objects;
 import java.util.function.Predicate;
 
 /**
@@ -225,6 +227,8 @@
     final int sourceUserId;
     final int sourceUid;
     final String sourceTag;
+    @Nullable
+    private final String mNamespace;
 
     final String tag;
 
@@ -515,6 +519,7 @@
      * @param standbyBucket The standby bucket that the source package is currently assigned to,
      *     cached here for speed of handling during runnability evaluations (and updated when bucket
      *     assignments are changed)
+     * @param namespace The custom namespace the app put this job in.
      * @param tag A string associated with the job for debugging/logging purposes.
      * @param numFailures Count of how many times this job has requested a reschedule because
      *     its work was not yet finished.
@@ -529,13 +534,15 @@
      * @param internalFlags Non-API property flags about this job
      */
     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
-            int sourceUserId, int standbyBucket, String tag, int numFailures, int numSystemStops,
+            int sourceUserId, int standbyBucket, @Nullable String namespace, String tag,
+            int numFailures, int numSystemStops,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
             int dynamicConstraints) {
         this.job = job;
         this.callingUid = callingUid;
         this.standbyBucket = standbyBucket;
+        mNamespace = namespace;
 
         int tempSourceUid = -1;
         if (sourceUserId != -1 && sourcePackageName != null) {
@@ -658,7 +665,7 @@
     public JobStatus(JobStatus jobStatus) {
         this(jobStatus.getJob(), jobStatus.getUid(),
                 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
-                jobStatus.getStandbyBucket(),
+                jobStatus.getStandbyBucket(), jobStatus.getNamespace(),
                 jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
@@ -669,6 +676,12 @@
                 Slog.i(TAG, "Cloning job with persisted run times", new RuntimeException("here"));
             }
         }
+        if (jobStatus.executingWork != null && jobStatus.executingWork.size() > 0) {
+            executingWork = new ArrayList<>(jobStatus.executingWork);
+        }
+        if (jobStatus.pendingWork != null && jobStatus.pendingWork.size() > 0) {
+            pendingWork = new ArrayList<>(jobStatus.pendingWork);
+        }
     }
 
     /**
@@ -680,13 +693,13 @@
      * standby bucket is whatever the OS thinks it should be at this moment.
      */
     public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
-            int standbyBucket, String sourceTag,
+            int standbyBucket, @Nullable String namespace, String sourceTag,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime,
             Pair<Long, Long> persistedExecutionTimesUTC,
             int innerFlags, int dynamicConstraints) {
         this(job, callingUid, sourcePkgName, sourceUserId,
-                standbyBucket,
+                standbyBucket, namespace,
                 sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
@@ -710,7 +723,7 @@
             long lastSuccessfulRunTime, long lastFailedRunTime) {
         this(rescheduling.job, rescheduling.getUid(),
                 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
-                rescheduling.getStandbyBucket(),
+                rescheduling.getStandbyBucket(), rescheduling.getNamespace(),
                 rescheduling.getSourceTag(), numFailures, numSystemStops,
                 newEarliestRuntimeElapsedMillis,
                 newLatestRuntimeElapsedMillis,
@@ -727,7 +740,7 @@
      *     caller.
      */
     public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
-            int sourceUserId, String tag) {
+            int sourceUserId, @Nullable String namespace, String tag) {
         final long elapsedNow = sElapsedRealtimeClock.millis();
         final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
         if (job.isPeriodic()) {
@@ -749,7 +762,7 @@
         int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                 sourceUserId, elapsedNow);
         return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
-                standbyBucket, tag, /* numFailures */ 0, /* numSystemStops */ 0,
+                standbyBucket, namespace, tag, /* numFailures */ 0, /* numSystemStops */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
                 /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
@@ -897,6 +910,12 @@
     }
 
     public void printUniqueId(PrintWriter pw) {
+        if (mNamespace != null) {
+            pw.print(mNamespace);
+            pw.print(":");
+        } else {
+            pw.print("#");
+        }
         UserHandle.formatUid(pw, callingUid);
         pw.print("/");
         pw.print(job.getId());
@@ -1036,6 +1055,10 @@
         return true;
     }
 
+    public String getNamespace() {
+        return mNamespace;
+    }
+
     public String getSourceTag() {
         return sourceTag;
     }
@@ -1362,7 +1385,8 @@
     public UserVisibleJobSummary getUserVisibleJobSummary() {
         if (mUserVisibleJobSummary == null) {
             mUserVisibleJobSummary = new UserVisibleJobSummary(
-                    callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+                    callingUid, getSourceUserId(), getSourcePackageName(),
+                    getNamespace(), getJobId());
         }
         return mUserVisibleJobSummary;
     }
@@ -1989,8 +2013,12 @@
         return (sat & mRequiredConstraintsOfInterest) == mRequiredConstraintsOfInterest;
     }
 
-    public boolean matches(int uid, int jobId) {
-        return this.job.getId() == jobId && this.callingUid == uid;
+    /**
+     * Returns true if the given parameters match this job's unique identifier.
+     */
+    public boolean matches(int uid, @Nullable String namespace, int jobId) {
+        return this.job.getId() == jobId && this.callingUid == uid
+                && Objects.equals(mNamespace, namespace);
     }
 
     @Override
@@ -1998,7 +2026,13 @@
         StringBuilder sb = new StringBuilder(128);
         sb.append("JobStatus{");
         sb.append(Integer.toHexString(System.identityHashCode(this)));
-        sb.append(" #");
+        if (mNamespace != null) {
+            sb.append(" ");
+            sb.append(mNamespace);
+            sb.append(":");
+        } else {
+            sb.append(" #");
+        }
         UserHandle.formatUid(sb, callingUid);
         sb.append("/");
         sb.append(job.getId());
@@ -2087,6 +2121,9 @@
     public String toShortString() {
         StringBuilder sb = new StringBuilder();
         sb.append(Integer.toHexString(System.identityHashCode(this)));
+        if (mNamespace != null) {
+            sb.append(" {").append(mNamespace).append("}");
+        }
         sb.append(" #");
         UserHandle.formatUid(sb, callingUid);
         sb.append("/");
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 80512f7..00d9a4b 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -603,6 +603,15 @@
     mFlingerSurface = s;
     mTargetInset = -1;
 
+    // Rotate the boot animation according to the value specified in the sysprop
+    // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+    // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
+    // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
+    // This is needed to support having boot animation in orientations different from the natural
+    // device orientation. For example, on tablets that may want to keep natural orientation
+    // portrait for applications compatibility and to have the boot animation in landscape.
+    rotateAwayFromNaturalOrientationIfNeeded();
+
     projectSceneToWindow();
 
     // Register a display event receiver
@@ -616,6 +625,50 @@
     return NO_ERROR;
 }
 
+void BootAnimation::rotateAwayFromNaturalOrientationIfNeeded() {
+    const auto orientation = parseOrientationProperty();
+
+    if (orientation == ui::ROTATION_0) {
+        // Do nothing if the sysprop isn't set or is set to ROTATION_0.
+        return;
+    }
+
+    if (orientation == ui::ROTATION_90 || orientation == ui::ROTATION_270) {
+        std::swap(mWidth, mHeight);
+        std::swap(mInitWidth, mInitHeight);
+        mFlingerSurfaceControl->updateDefaultBufferSize(mWidth, mHeight);
+    }
+
+    Rect displayRect(0, 0, mWidth, mHeight);
+    Rect layerStackRect(0, 0, mWidth, mHeight);
+
+    SurfaceComposerClient::Transaction t;
+    t.setDisplayProjection(mDisplayToken, orientation, layerStackRect, displayRect);
+    t.apply();
+}
+
+ui::Rotation BootAnimation::parseOrientationProperty() {
+    const auto displayIds = SurfaceComposerClient::getPhysicalDisplayIds();
+    if (displayIds.size() == 0) {
+        return ui::ROTATION_0;
+    }
+    const auto displayId = displayIds[0];
+    const auto syspropName = [displayId] {
+        std::stringstream ss;
+        ss << "ro.bootanim.set_orientation_" << displayId.value;
+        return ss.str();
+    }();
+    const auto syspropValue = android::base::GetProperty(syspropName, "ORIENTATION_0");
+    if (syspropValue == "ORIENTATION_90") {
+        return ui::ROTATION_90;
+    } else if (syspropValue == "ORIENTATION_180") {
+        return ui::ROTATION_180;
+    } else if (syspropValue == "ORIENTATION_270") {
+        return ui::ROTATION_270;
+    }
+    return ui::ROTATION_0;
+}
+
 void BootAnimation::projectSceneToWindow() {
     glViewport(0, 0, mWidth, mHeight);
     glScissor(0, 0, mWidth, mHeight);
diff --git a/cmds/bootanimation/BootAnimation.h b/cmds/bootanimation/BootAnimation.h
index 8658205..8683b71 100644
--- a/cmds/bootanimation/BootAnimation.h
+++ b/cmds/bootanimation/BootAnimation.h
@@ -30,6 +30,8 @@
 #include <utils/Thread.h>
 #include <binder/IBinder.h>
 
+#include <ui/Rotation.h>
+
 #include <EGL/egl.h>
 #include <GLES2/gl2.h>
 
@@ -200,6 +202,8 @@
     ui::Size limitSurfaceSize(int width, int height) const;
     void resizeSurface(int newWidth, int newHeight);
     void projectSceneToWindow();
+    void rotateAwayFromNaturalOrientationIfNeeded();
+    ui::Rotation parseOrientationProperty();
 
     bool shouldStopPlayingPart(const Animation::Part& part, int fadedFramesCount,
                                int lastDisplayedProgress);
diff --git a/core/api/current.txt b/core/api/current.txt
index b6db2ef..b63b0aa 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -363,6 +363,7 @@
     field public static final int allowGameFpsOverride = 16844378; // 0x101065a
     field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612
     field public static final int allowParallelSyncs = 16843570; // 0x1010332
+    field public static final int allowSharedIsolatedProcess;
     field public static final int allowSingleTap = 16843353; // 0x1010259
     field public static final int allowTaskReparenting = 16843268; // 0x1010204
     field public static final int allowUndo = 16843999; // 0x10104df
@@ -934,6 +935,8 @@
     field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234
     field @Deprecated public static final int keyWidth = 16843325; // 0x101023d
     field public static final int keyboardLayout = 16843691; // 0x10103ab
+    field public static final int keyboardLayoutType;
+    field public static final int keyboardLocale;
     field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d
     field public static final int keyboardNavigationCluster = 16844096; // 0x1010540
     field public static final int keycode = 16842949; // 0x10100c5
@@ -4612,6 +4615,7 @@
     method public boolean getLockTaskMode();
     method public int getSplashScreenStyle();
     method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
+    method public boolean isShareIdentityEnabled();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -5648,6 +5652,11 @@
     field public static final int MODE_UNKNOWN = 0; // 0x0
   }
 
+  public class GrammaticalInflectionManager {
+    method public int getApplicationGrammaticalGender();
+    method public void setRequestedApplicationGrammaticalGender(int);
+  }
+
   public class Instrumentation {
     ctor public Instrumentation();
     method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -7498,8 +7507,8 @@
     method public boolean getCameraDisabled(@Nullable android.content.ComponentName);
     method @Deprecated @Nullable public String getCertInstallerPackage(@NonNull android.content.ComponentName) throws java.lang.SecurityException;
     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 @Deprecated public boolean getCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName);
+    method @Deprecated public boolean getCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName);
     method @NonNull public java.util.Set<java.lang.String> getCrossProfilePackages(@NonNull android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getCrossProfileWidgetProviders(@NonNull android.content.ComponentName);
     method public int getCurrentFailedPasswordAttempts();
@@ -7519,6 +7528,8 @@
     method public int getLockTaskFeatures(@NonNull android.content.ComponentName);
     method @NonNull public String[] getLockTaskPackages(@NonNull android.content.ComponentName);
     method @Nullable public CharSequence getLongSupportMessage(@NonNull android.content.ComponentName);
+    method @Nullable public android.app.admin.PackagePolicy getManagedProfileCallerIdAccessPolicy();
+    method @Nullable public android.app.admin.PackagePolicy getManagedProfileContactsAccessPolicy();
     method public long getManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
     method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
@@ -7647,8 +7658,8 @@
     method public void setCommonCriteriaModeEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setConfiguredNetworksLockdownState(@NonNull android.content.ComponentName, boolean);
     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 @Deprecated public void setCrossProfileCallerIdDisabled(@NonNull android.content.ComponentName, boolean);
+    method @Deprecated public void setCrossProfileContactsSearchDisabled(@NonNull android.content.ComponentName, boolean);
     method public void setCrossProfilePackages(@NonNull android.content.ComponentName, @NonNull java.util.Set<java.lang.String>);
     method public void setDefaultSmsApplication(@NonNull android.content.ComponentName, @NonNull String);
     method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
@@ -7667,6 +7678,8 @@
     method public void setLockTaskPackages(@NonNull android.content.ComponentName, @NonNull String[]) throws java.lang.SecurityException;
     method public void setLogoutEnabled(@NonNull android.content.ComponentName, boolean);
     method public void setLongSupportMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+    method public void setManagedProfileCallerIdAccessPolicy(@Nullable android.app.admin.PackagePolicy);
+    method public void setManagedProfileContactsAccessPolicy(@Nullable android.app.admin.PackagePolicy);
     method public void setManagedProfileMaximumTimeOff(@NonNull android.content.ComponentName, long);
     method public void setMasterVolumeMuted(@NonNull android.content.ComponentName, boolean);
     method public void setMaximumFailedPasswordsForWipe(@NonNull android.content.ComponentName, int);
@@ -7978,6 +7991,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.NetworkEvent> CREATOR;
   }
 
+  public final class PackagePolicy implements android.os.Parcelable {
+    ctor public PackagePolicy(int);
+    ctor public PackagePolicy(int, @NonNull java.util.Set<java.lang.String>);
+    method public int describeContents();
+    method @NonNull public java.util.Set<java.lang.String> getPackageNames();
+    method public int getPolicyType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.PackagePolicy> CREATOR;
+    field public static final int PACKAGE_POLICY_ALLOWLIST = 3; // 0x3
+    field public static final int PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM = 2; // 0x2
+    field public static final int PACKAGE_POLICY_BLOCKLIST = 1; // 0x1
+  }
+
   public final class PreferentialNetworkServiceConfig implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public int[] getExcludedUids();
@@ -8569,12 +8595,22 @@
     method public int getDeliveryCount();
     method public long getEstimatedNetworkDownloadBytes();
     method public long getEstimatedNetworkUploadBytes();
+    method @NonNull public android.os.PersistableBundle getExtras();
     method public android.content.Intent getIntent();
     method public long getMinimumNetworkChunkBytes();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
   }
 
+  public static final class JobWorkItem.Builder {
+    ctor public JobWorkItem.Builder();
+    method @NonNull public android.app.job.JobWorkItem build();
+    method @NonNull public android.app.job.JobWorkItem.Builder setEstimatedNetworkBytes(long, long);
+    method @NonNull public android.app.job.JobWorkItem.Builder setExtras(@NonNull android.os.PersistableBundle);
+    method @NonNull public android.app.job.JobWorkItem.Builder setIntent(@NonNull android.content.Intent);
+    method @NonNull public android.app.job.JobWorkItem.Builder setMinimumNetworkChunkBytes(long);
+  }
+
 }
 
 package android.app.people {
@@ -9878,6 +9914,7 @@
     method @Deprecated public abstract int getWallpaperDesiredMinimumHeight();
     method @Deprecated public abstract int getWallpaperDesiredMinimumWidth();
     method public abstract void grantUriPermission(String, android.net.Uri, int);
+    method public boolean isDeviceContext();
     method public abstract boolean isDeviceProtectedStorage();
     method public boolean isRestricted();
     method public boolean isUiContext();
@@ -9893,6 +9930,7 @@
     method public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable android.database.DatabaseErrorHandler);
     method @Deprecated public abstract android.graphics.drawable.Drawable peekWallpaper();
     method public void registerComponentCallbacks(android.content.ComponentCallbacks);
+    method public void registerDeviceIdChangeListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter);
     method @Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter, int);
     method @Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler);
@@ -9932,6 +9970,7 @@
     method public abstract boolean stopService(android.content.Intent);
     method public abstract void unbindService(@NonNull android.content.ServiceConnection);
     method public void unregisterComponentCallbacks(android.content.ComponentCallbacks);
+    method public void unregisterDeviceIdChangeListener(@NonNull java.util.function.IntConsumer);
     method public abstract void unregisterReceiver(android.content.BroadcastReceiver);
     method public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int);
     field public static final String ACCESSIBILITY_SERVICE = "accessibility";
@@ -9953,6 +9992,7 @@
     field public static final int BIND_INCLUDE_CAPABILITIES = 4096; // 0x1000
     field public static final int BIND_NOT_FOREGROUND = 4; // 0x4
     field public static final int BIND_NOT_PERCEPTIBLE = 256; // 0x100
+    field public static final int BIND_SHARED_ISOLATED_PROCESS = 8192; // 0x2000
     field public static final int BIND_WAIVE_PRIORITY = 32; // 0x20
     field public static final String BIOMETRIC_SERVICE = "biometric";
     field public static final String BLOB_STORE_SERVICE = "blob_store";
@@ -9982,6 +10022,7 @@
     field public static final String FILE_INTEGRITY_SERVICE = "file_integrity";
     field public static final String FINGERPRINT_SERVICE = "fingerprint";
     field public static final String GAME_SERVICE = "game";
+    field public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
     field public static final String HARDWARE_PROPERTIES_SERVICE = "hardware_properties";
     field public static final String HEALTHCONNECT_SERVICE = "healthconnect";
     field public static final String INPUT_METHOD_SERVICE = "input_method";
@@ -10010,6 +10051,7 @@
     field public static final String NFC_SERVICE = "nfc";
     field public static final String NOTIFICATION_SERVICE = "notification";
     field public static final String NSD_SERVICE = "servicediscovery";
+    field public static final String OVERLAY_SERVICE = "overlay";
     field public static final String PEOPLE_SERVICE = "people";
     field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
     field public static final String POWER_SERVICE = "power";
@@ -11178,6 +11220,49 @@
 
 }
 
+package android.content.om {
+
+  public class FabricatedOverlay {
+    ctor public FabricatedOverlay(@NonNull String, @NonNull String);
+    method @NonNull public android.content.om.OverlayIdentifier getIdentifier();
+    method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
+    method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
+    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
+    method public void setTargetOverlayable(@Nullable String);
+  }
+
+  public final class OverlayIdentifier implements android.os.Parcelable {
+    method public int describeContents();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayIdentifier> CREATOR;
+  }
+
+  public final class OverlayInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.content.om.OverlayIdentifier getOverlayIdentifier();
+    method @Nullable public String getOverlayName();
+    method @Nullable public String getTargetOverlayableName();
+    method @NonNull public String getTargetPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
+  }
+
+  public class OverlayManager {
+    method @NonNull @NonUiContext public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(@NonNull String);
+  }
+
+  public final class OverlayManagerTransaction implements android.os.Parcelable {
+    ctor public OverlayManagerTransaction(@NonNull android.content.om.OverlayManager);
+    method @NonUiContext public void commit() throws java.io.IOException, android.content.pm.PackageManager.NameNotFoundException;
+    method public int describeContents();
+    method @NonNull public void registerFabricatedOverlay(@NonNull android.content.om.FabricatedOverlay);
+    method @NonNull public void unregisterFabricatedOverlay(@NonNull android.content.om.OverlayIdentifier);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayManagerTransaction> CREATOR;
+  }
+
+}
+
 package android.content.pm {
 
   public class ActivityInfo extends android.content.pm.ComponentInfo implements android.os.Parcelable {
@@ -11194,6 +11279,7 @@
     field public static final int CONFIG_DENSITY = 4096; // 0x1000
     field public static final int CONFIG_FONT_SCALE = 1073741824; // 0x40000000
     field public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 268435456; // 0x10000000
+    field public static final int CONFIG_GRAMMATICAL_GENDER = 32768; // 0x8000
     field public static final int CONFIG_KEYBOARD = 16; // 0x10
     field public static final int CONFIG_KEYBOARD_HIDDEN = 32; // 0x20
     field public static final int CONFIG_LAYOUT_DIRECTION = 8192; // 0x2000
@@ -11742,6 +11828,7 @@
     method public void registerSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback, @NonNull android.os.Handler);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull String, @NonNull android.content.IntentSender);
     method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.IntentSender);
+    method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void uninstall(@NonNull android.content.pm.VersionedPackage, int, @NonNull android.content.IntentSender);
     method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public void uninstallExistingPackage(@NonNull String, @Nullable android.content.IntentSender);
     method public void unregisterSessionCallback(@NonNull android.content.pm.PackageInstaller.SessionCallback);
     method public void updateSessionAppIcon(int, @Nullable android.graphics.Bitmap);
@@ -11870,6 +11957,8 @@
     method public int getInstallReason();
     method @Nullable public String getInstallerAttributionTag();
     method @Nullable public String getInstallerPackageName();
+    method public int getInstallerUid();
+    method @NonNull public boolean getIsPreApprovalRequested();
     method public int getMode();
     method public int getOriginatingUid();
     method @Nullable public android.net.Uri getOriginatingUri();
@@ -11920,6 +12009,7 @@
     method public void setInstallLocation(int);
     method public void setInstallReason(int);
     method public void setInstallScenario(int);
+    method public void setInstallerPackageName(@Nullable String);
     method public void setKeepApplicationEnabledSetting();
     method public void setMultiPackage();
     method public void setOriginatingUid(int);
@@ -11975,6 +12065,7 @@
     method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public boolean[] canPackageQuery(@NonNull String, @NonNull String[]) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract boolean canRequestPackageInstalls();
     method public abstract String[] canonicalToCurrentPackageNames(@NonNull String[]);
     method @CheckResult public abstract int checkPermission(@NonNull String, @NonNull String);
@@ -12179,6 +12270,7 @@
     field public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS = "android.hardware.identity_credential_direct_access";
     field public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
     field public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+    field public static final String FEATURE_IPSEC_TUNNEL_MIGRATION = "android.software.ipsec_tunnel_migration";
     field public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
     field public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY = "android.hardware.keystore.app_attest_key";
     field public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY = "android.hardware.keystore.limited_use_key";
@@ -12528,6 +12620,7 @@
     method public void dump(android.util.Printer, String);
     method public int getForegroundServiceType();
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ServiceInfo> CREATOR;
+    field public static final int FLAG_ALLOW_SHARED_ISOLATED_PROCESS = 16; // 0x10
     field public static final int FLAG_EXTERNAL_SERVICE = 4; // 0x4
     field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2
     field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000
@@ -12806,6 +12899,7 @@
     method public int diff(android.content.res.Configuration);
     method public boolean equals(android.content.res.Configuration);
     method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
+    method public int getGrammaticalGender();
     method public int getLayoutDirection();
     method @NonNull public android.os.LocaleList getLocales();
     method public boolean isLayoutSizeAtLeast(int);
@@ -12835,6 +12929,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = 2147483647; // 0x7fffffff
+    field public static final int GRAMMATICAL_GENDER_FEMININE = 3; // 0x3
+    field public static final int GRAMMATICAL_GENDER_MASCULINE = 4; // 0x4
+    field public static final int GRAMMATICAL_GENDER_NEUTRAL = 2; // 0x2
+    field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
     field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_YES = 2; // 0x2
@@ -13082,6 +13180,7 @@
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadOverlay(@NonNull android.content.om.OverlayInfo) throws java.io.IOException;
   }
 
 }
@@ -13167,13 +13266,14 @@
 
   public final class GetCredentialRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public android.os.Bundle getData();
     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.credentials.GetCredentialRequest> CREATOR;
   }
 
   public static final class GetCredentialRequest.Builder {
-    ctor public GetCredentialRequest.Builder();
+    ctor public GetCredentialRequest.Builder(@NonNull android.os.Bundle);
     method @NonNull public android.credentials.GetCredentialRequest.Builder addGetCredentialOption(@NonNull android.credentials.GetCredentialOption);
     method @NonNull public android.credentials.GetCredentialRequest build();
     method @NonNull public android.credentials.GetCredentialRequest.Builder setGetCredentialOptions(@NonNull java.util.List<android.credentials.GetCredentialOption>);
@@ -14855,6 +14955,8 @@
     enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
     enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
     enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020_HLG;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020_PQ;
     enum_constant public static final android.graphics.ColorSpace.Named BT709;
     enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
     enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
@@ -17761,6 +17863,7 @@
     method public void onCaptureSequenceAborted(@NonNull android.hardware.camera2.CameraCaptureSession, int);
     method public void onCaptureSequenceCompleted(@NonNull android.hardware.camera2.CameraCaptureSession, int, long);
     method public void onCaptureStarted(@NonNull android.hardware.camera2.CameraCaptureSession, @NonNull android.hardware.camera2.CaptureRequest, long, long);
+    method public void onReadoutStarted(@NonNull android.hardware.camera2.CameraCaptureSession, @NonNull android.hardware.camera2.CaptureRequest, long, long);
   }
 
   public abstract static class CameraCaptureSession.StateCallback {
@@ -17958,8 +18061,10 @@
     method @Nullable public android.util.Range<java.lang.Long> getEstimatedCaptureLatencyRangeMillis(int, @NonNull android.util.Size, int);
     method @NonNull public <T> java.util.List<android.util.Size> getExtensionSupportedSizes(int, @NonNull Class<T>);
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
+    method @NonNull public java.util.List<android.util.Size> getPostviewSupportedSizes(int, @NonNull android.util.Size, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
     method public boolean isCaptureProcessProgressAvailable(int);
+    method public boolean isPostviewAvailable(int);
     field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
     field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
     field public static final int EXTENSION_BOKEH = 2; // 0x2
@@ -18234,6 +18339,7 @@
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA = 14; // 0xe
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR = 16; // 0x10
     field public static final int REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING = 7; // 0x7
+    field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW = 6; // 0x6
     field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT = 0; // 0x0
     field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 1; // 0x1
     field public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 4; // 0x4
@@ -18493,6 +18599,7 @@
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Float> REPROCESS_EFFECTIVE_EXPOSURE_FACTOR;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Byte> REQUEST_PIPELINE_DEPTH;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_CROP_REGION;
+    field @NonNull public static final android.hardware.camera2.CaptureResult.Key<android.graphics.Rect> SCALER_RAW_CROP_REGION;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SCALER_ROTATE_AND_CROP;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<float[]> SENSOR_DYNAMIC_BLACK_LEVEL;
     field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> SENSOR_DYNAMIC_WHITE_LEVEL;
@@ -18631,7 +18738,9 @@
     method @NonNull public java.util.concurrent.Executor getExecutor();
     method public int getExtension();
     method @NonNull public java.util.List<android.hardware.camera2.params.OutputConfiguration> getOutputConfigurations();
+    method @Nullable public android.hardware.camera2.params.OutputConfiguration getPostviewOutputConfiguration();
     method @NonNull public android.hardware.camera2.CameraExtensionSession.StateCallback getStateCallback();
+    method public void setPostviewOutputConfiguration(@Nullable android.hardware.camera2.params.OutputConfiguration);
   }
 
   public final class Face {
@@ -18750,6 +18859,7 @@
     method public int getSurfaceGroupId();
     method @NonNull public java.util.List<android.view.Surface> getSurfaces();
     method public int getTimestampBase();
+    method public boolean isReadoutTimestampUsed();
     method public void removeSensorPixelModeUsed(int);
     method public void removeSurface(@NonNull android.view.Surface);
     method public void setDynamicRangeProfile(long);
@@ -18757,6 +18867,7 @@
     method public void setPhysicalCameraId(@Nullable String);
     method public void setStreamUseCase(long);
     method public void setTimestampBase(int);
+    method public void useReadoutTimestamp(boolean);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.camera2.params.OutputConfiguration> CREATOR;
     field public static final int MIRROR_MODE_AUTO = 0; // 0x0
@@ -19882,8 +19993,9 @@
     method public int describeContents();
     method @NonNull public android.location.GnssClock getClock();
     method @NonNull public java.util.Collection<android.location.GnssAutomaticGainControl> getGnssAutomaticGainControls();
-    method public boolean getIsFullTracking();
     method @NonNull public java.util.Collection<android.location.GnssMeasurement> getMeasurements();
+    method public boolean hasFullTracking();
+    method public boolean isFullTracking();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementsEvent> CREATOR;
   }
@@ -19892,9 +20004,10 @@
     ctor public GnssMeasurementsEvent.Builder();
     ctor public GnssMeasurementsEvent.Builder(@NonNull android.location.GnssMeasurementsEvent);
     method @NonNull public android.location.GnssMeasurementsEvent build();
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder clearFullTracking();
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setClock(@NonNull android.location.GnssClock);
+    method @NonNull public android.location.GnssMeasurementsEvent.Builder setFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setGnssAutomaticGainControls(@NonNull java.util.Collection<android.location.GnssAutomaticGainControl>);
-    method @NonNull public android.location.GnssMeasurementsEvent.Builder setIsFullTracking(boolean);
     method @NonNull public android.location.GnssMeasurementsEvent.Builder setMeasurements(@NonNull java.util.Collection<android.location.GnssMeasurement>);
   }
 
@@ -20396,6 +20509,8 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioDescriptor> CREATOR;
     field public static final int STANDARD_EDID = 1; // 0x1
     field public static final int STANDARD_NONE = 0; // 0x0
+    field public static final int STANDARD_SADB = 2; // 0x2
+    field public static final int STANDARD_VSADB = 3; // 0x3
   }
 
   public abstract class AudioDeviceCallback {
@@ -25517,6 +25632,7 @@
   public abstract static class MediaProjection.Callback {
     ctor public MediaProjection.Callback();
     method public void onCapturedContentResize(int, int);
+    method public void onCapturedContentVisibilityChanged(boolean);
     method public void onStop();
   }
 
@@ -39334,6 +39450,8 @@
     method public final boolean onUnbind(@NonNull android.content.Intent);
     method public abstract void performControlAction(@NonNull String, @NonNull android.service.controls.actions.ControlAction, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method public static void requestAddControl(@NonNull android.content.Context, @NonNull android.content.ComponentName, @NonNull android.service.controls.Control);
+    field public static final String EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS = "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+    field public static final String META_DATA_PANEL_ACTIVITY = "android.service.controls.META_DATA_PANEL_ACTIVITY";
     field public static final String SERVICE_CONTROLS = "android.service.controls.ControlsProviderService";
     field @NonNull public static final String TAG = "ControlsProviderService";
   }
@@ -41011,6 +41129,32 @@
     field public static final int RTT_MODE_VCO = 3; // 0x3
   }
 
+  public final class CallAttributes implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.net.Uri getAddress();
+    method public int getCallCapabilities();
+    method public int getCallType();
+    method public int getDirection();
+    method @NonNull public CharSequence getDisplayName();
+    method @NonNull public android.telecom.PhoneAccountHandle getPhoneAccountHandle();
+    method public void writeToParcel(@Nullable android.os.Parcel, int);
+    field public static final int AUDIO_CALL = 1; // 0x1
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallAttributes> CREATOR;
+    field public static final int DIRECTION_INCOMING = 1; // 0x1
+    field public static final int DIRECTION_OUTGOING = 2; // 0x2
+    field public static final int SUPPORTS_SET_INACTIVE = 2; // 0x2
+    field public static final int SUPPORTS_STREAM = 4; // 0x4
+    field public static final int SUPPORTS_TRANSFER = 8; // 0x8
+    field public static final int VIDEO_CALL = 2; // 0x2
+  }
+
+  public static final class CallAttributes.Builder {
+    ctor public CallAttributes.Builder(@NonNull android.telecom.PhoneAccountHandle, int, @NonNull CharSequence, @NonNull android.net.Uri);
+    method @NonNull public android.telecom.CallAttributes build();
+    method @NonNull public android.telecom.CallAttributes.Builder setCallCapabilities(int);
+    method @NonNull public android.telecom.CallAttributes.Builder setCallType(int);
+  }
+
   public final class CallAudioState implements android.os.Parcelable {
     ctor public CallAudioState(boolean, int, int);
     method public static String audioRouteToString(int);
@@ -41025,10 +41169,21 @@
     field public static final int ROUTE_BLUETOOTH = 2; // 0x2
     field public static final int ROUTE_EARPIECE = 1; // 0x1
     field public static final int ROUTE_SPEAKER = 8; // 0x8
+    field public static final int ROUTE_STREAMING = 16; // 0x10
     field public static final int ROUTE_WIRED_HEADSET = 4; // 0x4
     field public static final int ROUTE_WIRED_OR_EARPIECE = 5; // 0x5
   }
 
+  public final class CallControl implements java.lang.AutoCloseable {
+    method public void close();
+    method public void disconnect(@NonNull android.telecom.DisconnectCause, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method @NonNull public android.os.ParcelUuid getCallId();
+    method public void rejectCall(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void setActive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void setInactive(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+    method public void startCallStreaming(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.telecom.CallException>);
+  }
+
   public final class CallEndpoint implements android.os.Parcelable {
     ctor public CallEndpoint(@NonNull CharSequence, int, @NonNull android.os.ParcelUuid);
     method public int describeContents();
@@ -41059,6 +41214,32 @@
     field public static final int ERROR_UNSPECIFIED = 4; // 0x4
   }
 
+  public interface CallEventCallback {
+    method public void onAnswer(int, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onCallAudioStateChanged(@NonNull android.telecom.CallAudioState);
+    method public void onCallStreamingFailed(int);
+    method public void onCallStreamingStarted(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onDisconnect(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onReject(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onSetActive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+    method public void onSetInactive(@NonNull java.util.function.Consumer<java.lang.Boolean>);
+  }
+
+  public final class CallException extends java.lang.RuntimeException implements android.os.Parcelable {
+    ctor public CallException(@Nullable String);
+    ctor public CallException(@Nullable String, int);
+    method public int describeContents();
+    method public int getCode();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field public static final int CODE_CALL_CANNOT_BE_SET_TO_ACTIVE = 4; // 0x4
+    field public static final int CODE_CALL_IS_NOT_BEING_TRACKED = 3; // 0x3
+    field public static final int CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5; // 0x5
+    field public static final int CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2; // 0x2
+    field public static final int CODE_ERROR_UNKNOWN = 1; // 0x1
+    field public static final int CODE_OPERATION_TIMED_OUT = 6; // 0x6
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.CallException> CREATOR;
+  }
+
   public abstract class CallRedirectionService extends android.app.Service {
     ctor public CallRedirectionService();
     method public final void cancelCall();
@@ -41103,6 +41284,18 @@
     method public android.telecom.CallScreeningService.CallResponse.Builder setSkipNotification(boolean);
   }
 
+  public abstract class CallStreamingService extends android.app.Service {
+    ctor public CallStreamingService();
+    method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public void onCallStreamingStarted(@NonNull android.telecom.StreamingCall);
+    method public void onCallStreamingStateChanged(int);
+    method public void onCallStreamingStopped();
+    field public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+    field public static final int STREAMING_FAILED_ALREADY_STREAMING = 1; // 0x1
+    field public static final int STREAMING_FAILED_NO_SENDER = 2; // 0x2
+    field public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3; // 0x3
+  }
+
   public abstract class Conference extends android.telecom.Conferenceable {
     ctor public Conference(android.telecom.PhoneAccountHandle);
     method public final boolean addConnection(android.telecom.Connection);
@@ -41568,6 +41761,8 @@
     field public static final int CAPABILITY_RTT = 4096; // 0x1000
     field public static final int CAPABILITY_SELF_MANAGED = 2048; // 0x800
     field public static final int CAPABILITY_SIM_SUBSCRIPTION = 4; // 0x4
+    field public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 524288; // 0x80000
+    field public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 262144; // 0x40000
     field public static final int CAPABILITY_SUPPORTS_VIDEO_CALLING = 1024; // 0x400
     field public static final int CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS = 65536; // 0x10000
     field public static final int CAPABILITY_VIDEO_CALLING = 8; // 0x8
@@ -41756,10 +41951,27 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StatusHints> CREATOR;
   }
 
+  public final class StreamingCall implements android.os.Parcelable {
+    ctor public StreamingCall(@NonNull android.content.ComponentName, @NonNull String, @NonNull android.net.Uri, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @NonNull public android.net.Uri getAddress();
+    method @NonNull public android.content.ComponentName getComponentName();
+    method @NonNull public String getDisplayName();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getState();
+    method public void setStreamingState(int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telecom.StreamingCall> CREATOR;
+    field public static final int STATE_DISCONNECTED = 3; // 0x3
+    field public static final int STATE_HOLDING = 2; // 0x2
+    field public static final int STATE_STREAMING = 1; // 0x1
+  }
+
   public class TelecomManager {
     method public void acceptHandover(android.net.Uri, int, android.telecom.PhoneAccountHandle);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall();
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ANSWER_PHONE_CALLS, android.Manifest.permission.MODIFY_PHONE_STATE}) public void acceptRingingCall(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) public void addCall(@NonNull android.telecom.CallAttributes, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telecom.CallControl,android.telecom.CallException>, @NonNull android.telecom.CallEventCallback);
     method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
     method public void addNewIncomingConference(@NonNull android.telecom.PhoneAccountHandle, @NonNull android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void cancelMissedCallsNotification();
@@ -42587,9 +42799,16 @@
   public static final class CarrierConfigManager.ImsSms {
     field public static final String KEY_PREFIX = "imssms.";
     field public static final String KEY_SMS_CSFB_RETRY_ON_FAILURE_BOOL = "imssms.sms_csfb_retry_on_failure_bool";
+    field public static final String KEY_SMS_MAX_RETRY_COUNT_INT = "imssms.sms_max_retry_count_int";
+    field public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT = "imssms.sms_max_retry_count_over_ims_int";
     field public static final String KEY_SMS_OVER_IMS_FORMAT_INT = "imssms.sms_over_ims_format_int";
+    field public static final String KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT = "imssms.sms_rover_ims_send_retry_delay_millis_int";
     field public static final String KEY_SMS_OVER_IMS_SUPPORTED_BOOL = "imssms.sms_over_ims_supported_bool";
     field public static final String KEY_SMS_OVER_IMS_SUPPORTED_RATS_INT_ARRAY = "imssms.sms_over_ims_supported_rats_int_array";
+    field public static final String KEY_SMS_RP_CAUSE_VALUES_TO_FALLBACK_INT_ARRAY = "imssms.sms_rp_cause_values_to_fallback_int_array";
+    field public static final String KEY_SMS_RP_CAUSE_VALUES_TO_RETRY_OVER_IMS_INT_ARRAY = "imssms.sms_rp_cause_values_to_retry_over_ims_int_array";
+    field public static final String KEY_SMS_TR1_TIMER_MILLIS_INT = "imssms.sms_tr1_timer_millis_int";
+    field public static final String KEY_SMS_TR2_TIMER_MILLIS_INT = "imssms.sms_tr2_timer_millis_int";
     field public static final int SMS_FORMAT_3GPP = 0; // 0x0
     field public static final int SMS_FORMAT_3GPP2 = 1; // 0x1
   }
@@ -44076,6 +44295,29 @@
     field public static final int RESULT_SMS_SEND_RETRY_FAILED = 30; // 0x1e
     field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf
     field public static final int RESULT_UNEXPECTED_EVENT_STOP_SENDING = 28; // 0x1c
+    field public static final int SMS_RP_CAUSE_CALL_BARRING = 10; // 0xa
+    field public static final int SMS_RP_CAUSE_CONGESTION = 42; // 0x2a
+    field public static final int SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER = 27; // 0x1b
+    field public static final int SMS_RP_CAUSE_FACILITY_NOT_IMPLEMENTED = 69; // 0x45
+    field public static final int SMS_RP_CAUSE_FACILITY_NOT_SUBSCRIBED = 50; // 0x32
+    field public static final int SMS_RP_CAUSE_FACILITY_REJECTED = 29; // 0x1d
+    field public static final int SMS_RP_CAUSE_INFORMATION_ELEMENT_NON_EXISTENT = 99; // 0x63
+    field public static final int SMS_RP_CAUSE_INTERWORKING_UNSPECIFIED = 127; // 0x7f
+    field public static final int SMS_RP_CAUSE_INVALID_MANDATORY_INFORMATION = 96; // 0x60
+    field public static final int SMS_RP_CAUSE_INVALID_MESSAGE_REFERENCE_VALUE = 81; // 0x51
+    field public static final int SMS_RP_CAUSE_MESSAGE_INCOMPATIBLE_WITH_PROTOCOL_STATE = 98; // 0x62
+    field public static final int SMS_RP_CAUSE_MESSAGE_TYPE_NON_EXISTENT = 97; // 0x61
+    field public static final int SMS_RP_CAUSE_NETWORK_OUT_OF_ORDER = 38; // 0x26
+    field public static final int SMS_RP_CAUSE_OPERATOR_DETERMINED_BARRING = 8; // 0x8
+    field public static final int SMS_RP_CAUSE_PROTOCOL_ERROR = 111; // 0x6f
+    field public static final int SMS_RP_CAUSE_RESERVED = 11; // 0xb
+    field public static final int SMS_RP_CAUSE_RESOURCES_UNAVAILABLE = 47; // 0x2f
+    field public static final int SMS_RP_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE = 95; // 0x5f
+    field public static final int SMS_RP_CAUSE_SHORT_MESSAGE_TRANSFER_REJECTED = 21; // 0x15
+    field public static final int SMS_RP_CAUSE_TEMPORARY_FAILURE = 41; // 0x29
+    field public static final int SMS_RP_CAUSE_UNALLOCATED_NUMBER = 1; // 0x1
+    field public static final int SMS_RP_CAUSE_UNIDENTIFIED_SUBSCRIBER = 28; // 0x1c
+    field public static final int SMS_RP_CAUSE_UNKNOWN_SUBSCRIBER = 30; // 0x1e
     field public static final int STATUS_ON_ICC_FREE = 0; // 0x0
     field public static final int STATUS_ON_ICC_READ = 1; // 0x1
     field public static final int STATUS_ON_ICC_SENT = 5; // 0x5
@@ -53750,6 +53992,7 @@
 package android.view.autofill {
 
   public final class AutofillId implements android.os.Parcelable {
+    method @NonNull public static android.view.autofill.AutofillId create(@NonNull android.view.View, int);
     method public int describeContents();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.autofill.AutofillId> CREATOR;
@@ -54201,7 +54444,7 @@
   }
 
   public abstract class HandwritingGesture {
-    method @Nullable public String getFallbackText();
+    method @Nullable public final String getFallbackText();
     field public static final int GESTURE_TYPE_DELETE = 4; // 0x4
     field public static final int GESTURE_TYPE_DELETE_RANGE = 64; // 0x40
     field public static final int GESTURE_TYPE_INSERT = 2; // 0x2
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 314fd03..2546294 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -63,6 +63,8 @@
   public class DevicePolicyManager {
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void acknowledgeNewUserDisclaimer();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public android.os.UserHandle getLogoutUser();
+    method public boolean hasManagedProfileCallerIdAccess(@NonNull android.os.UserHandle, @NonNull String);
+    method public boolean hasManagedProfileContactsAccess(@NonNull android.os.UserHandle, @NonNull String);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int logoutUser();
     field public static final String ACTION_SHOW_NEW_USER_DISCLAIMER = "android.app.action.SHOW_NEW_USER_DISCLAIMER";
   }
@@ -300,6 +302,17 @@
 
 }
 
+package android.net.wifi {
+
+  public final class WifiKeystore {
+    method @NonNull public static byte[] get(@NonNull String);
+    method @NonNull public static String[] list(@NonNull String);
+    method public static boolean put(@NonNull String, @NonNull byte[]);
+    method public static boolean remove(@NonNull String);
+  }
+
+}
+
 package android.os {
 
   public class ArtModuleServiceManager {
@@ -430,6 +443,7 @@
   }
 
   public static final class Settings.Config extends android.provider.Settings.NameValueTable {
+    method @RequiresPermission(android.Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS) public static void clearMonitorCallback(@NonNull android.content.ContentResolver);
     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>);
@@ -437,6 +451,7 @@
     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.MONITOR_DEVICE_CONFIG_ACCESS) public static void setMonitorCallback(@NonNull android.content.ContentResolver, @NonNull java.util.concurrent.Executor, @NonNull android.provider.DeviceConfig.MonitorCallback);
     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);
@@ -474,6 +489,10 @@
 
 package android.telephony {
 
+  public class CarrierConfigManager {
+    field public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT = "min_udp_port_4500_nat_timeout_sec_int";
+  }
+
   public abstract class CellSignalStrength {
     method public static int getNumSignalStrengthLevels();
   }
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 46f51c7..c09c6c5 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -212,6 +212,7 @@
     field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+    field public static final String MONITOR_DEVICE_CONFIG_ACCESS = "android.permission.MONITOR_DEVICE_CONFIG_ACCESS";
     field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
     field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
     field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -352,7 +353,6 @@
     field public static final String USE_COLORIZED_NOTIFICATIONS = "android.permission.USE_COLORIZED_NOTIFICATIONS";
     field public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
     field public static final String UWB_PRIVILEGED = "android.permission.UWB_PRIVILEGED";
-    field public static final String WAKEUP_SURFACE_FLINGER = "android.permission.WAKEUP_SURFACE_FLINGER";
     field public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS";
     field public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission.WHITELIST_RESTRICTED_PERMISSIONS";
     field public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
@@ -457,6 +457,7 @@
     field public static final int config_systemUi = 17039418; // 0x104003a
     field public static final int config_systemUiIntelligence = 17039410; // 0x1040032
     field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037
+    field public static final int config_systemWearHealthService;
     field public static final int config_systemWellbeing = 17039408; // 0x1040030
     field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f
     field public static final int safety_protection_display_text = 17039425; // 0x1040041
@@ -1397,12 +1398,15 @@
     method @NonNull public java.time.Instant getEndTime();
     method public int getEventType();
     method @NonNull public java.time.Instant getStartTime();
+    method @NonNull public android.os.PersistableBundle getVendorData();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.ambientcontext.AmbientContextEvent> CREATOR;
     field public static final int EVENT_BACK_DOUBLE_TAP = 3; // 0x3
     field public static final int EVENT_COUGH = 1; // 0x1
     field public static final int EVENT_SNORE = 2; // 0x2
     field public static final int EVENT_UNKNOWN = 0; // 0x0
+    field public static final int EVENT_VENDOR_WEARABLE_START = 100000; // 0x186a0
+    field public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
     field public static final int LEVEL_HIGH = 5; // 0x5
     field public static final int LEVEL_LOW = 1; // 0x1
     field public static final int LEVEL_MEDIUM = 3; // 0x3
@@ -1419,6 +1423,7 @@
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEndTime(@NonNull java.time.Instant);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setEventType(int);
     method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setStartTime(@NonNull java.time.Instant);
+    method @NonNull public android.app.ambientcontext.AmbientContextEvent.Builder setVendorData(@NonNull android.os.PersistableBundle);
   }
 
   public final class AmbientContextEventRequest implements android.os.Parcelable {
@@ -1491,11 +1496,13 @@
     method @RequiresPermission(android.Manifest.permission.BACKUP) public android.content.Intent getDataManagementIntent(String);
     method @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public CharSequence getDataManagementIntentLabel(@NonNull String);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.BACKUP) public String getDataManagementLabel(@NonNull String);
+    method @NonNull public android.app.backup.BackupRestoreEventLogger getDelayedRestoreLogger();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String getDestinationString(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isAppEligibleForBackup(String);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupEnabled();
     method @RequiresPermission(android.Manifest.permission.BACKUP) public boolean isBackupServiceActive(android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public String[] listAllTransports();
+    method @NonNull public void reportDelayedRestoreResult(@NonNull android.app.backup.BackupRestoreEventLogger);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver);
     method @RequiresPermission(android.Manifest.permission.BACKUP) public int requestBackup(String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int);
     method @Deprecated public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
@@ -3326,6 +3333,7 @@
     field public static final String CATEGORY_LEANBACK_SETTINGS = "android.intent.category.LEANBACK_SETTINGS";
     field public static final String EXTRA_CALLING_PACKAGE = "android.intent.extra.CALLING_PACKAGE";
     field public static final String EXTRA_FORCE_FACTORY_RESET = "android.intent.extra.FORCE_FACTORY_RESET";
+    field public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
     field public static final String EXTRA_INSTANT_APP_ACTION = "android.intent.extra.INSTANT_APP_ACTION";
     field public static final String EXTRA_INSTANT_APP_BUNDLES = "android.intent.extra.INSTANT_APP_BUNDLES";
     field public static final String EXTRA_INSTANT_APP_EXTRAS = "android.intent.extra.INSTANT_APP_EXTRAS";
@@ -3341,6 +3349,7 @@
     field public static final String EXTRA_RESULT_NEEDED = "android.intent.extra.RESULT_NEEDED";
     field public static final String EXTRA_ROLE_NAME = "android.intent.extra.ROLE_NAME";
     field public static final String EXTRA_SHOWING_ATTRIBUTION = "android.intent.extra.SHOWING_ATTRIBUTION";
+    field public static final String EXTRA_UNINSTALL_ALL_USERS = "android.intent.extra.UNINSTALL_ALL_USERS";
     field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
     field public static final String EXTRA_USER_HANDLE = "android.intent.extra.user_handle";
     field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
@@ -3424,15 +3433,10 @@
 package android.content.om {
 
   public final class OverlayInfo implements android.os.Parcelable {
-    method public int describeContents();
     method @Nullable public String getCategory();
     method @NonNull public String getPackageName();
-    method @Nullable public String getTargetOverlayableName();
-    method @NonNull public String getTargetPackageName();
     method public int getUserId();
     method public boolean isEnabled();
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
   }
 
   public class OverlayManager {
@@ -3447,9 +3451,11 @@
 package android.content.pm {
 
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
+    method @RequiresPermission(android.Manifest.permission.DELETE_PACKAGES) public boolean hasFragileUserData();
     method public boolean isEncryptionAware();
     method public boolean isInstantApp();
     method public boolean isOem();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
     method public boolean isProduct();
     method public boolean isVendor();
     field public String credentialProtectedDataDir;
@@ -3567,16 +3573,32 @@
   }
 
   public class PackageInstaller {
+    method @NonNull public android.content.pm.PackageInstaller.InstallInfo getInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
+    field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
+    field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
     field public static final int DATA_LOADER_TYPE_NONE = 0; // 0x0
     field public static final int DATA_LOADER_TYPE_STREAMING = 1; // 0x1
+    field public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
+    field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
+    field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
   }
 
+  public static class PackageInstaller.InstallInfo {
+    method public long calculateInstalledSize(@NonNull android.content.pm.PackageInstaller.SessionParams) throws java.io.IOException;
+    method public int getInstallLocation();
+    method @NonNull public String getPackageName();
+  }
+
+  public static class PackageInstaller.PackageParsingException extends java.lang.Exception {
+    method public int getErrorCode();
+  }
+
   public static class PackageInstaller.Session implements java.io.Closeable {
     method @RequiresPermission("com.android.permission.USE_INSTALLER_V2") public void addFile(int, @NonNull String, long, @NonNull byte[], @Nullable byte[]);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void commitTransferred(@NonNull android.content.IntentSender);
@@ -3623,7 +3645,7 @@
   public abstract class PackageManager {
     method @RequiresPermission("android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS") public abstract void addOnPermissionsChangeListener(@NonNull android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public abstract boolean arePermissionsIndividuallyControlled();
-    method @NonNull public boolean[] canPackageQuery(@NonNull String, @NonNull String[]) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public boolean canUserUninstall(@NonNull String, @NonNull android.os.UserHandle);
     method @NonNull public abstract java.util.List<android.content.IntentFilter> getAllIntentFilters(@NonNull String);
     method @NonNull @RequiresPermission("android.permission.GET_APP_METADATA") public android.os.PersistableBundle getAppMetadata(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public android.content.pm.ApplicationInfo getApplicationInfoAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -3642,6 +3664,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
+    method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
     method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public abstract void grantRuntimePermission(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
@@ -3669,11 +3692,19 @@
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
+    method @NonNull public boolean shouldShowNewAppInstalledNotification();
     method @Deprecated @RequiresPermission(android.Manifest.permission.SET_PREFERRED_APPLICATIONS) public abstract boolean updateIntentVerificationStatusAsUser(@NonNull String, int, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS}) public abstract void updatePermissionFlags(@NonNull String, @NonNull String, @android.content.pm.PackageManager.PermissionFlags int, @android.content.pm.PackageManager.PermissionFlags int, @NonNull android.os.UserHandle);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT) public abstract void verifyIntentFilter(int, int, @NonNull java.util.List<java.lang.String>);
     field public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
     field public static final String ACTION_REQUEST_PERMISSIONS_FOR_OTHER = "android.content.pm.action.REQUEST_PERMISSIONS_FOR_OTHER";
+    field public static final int DELETE_ALL_USERS = 2; // 0x2
+    field public static final int DELETE_FAILED_ABORTED = -5; // 0xfffffffb
+    field public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2; // 0xfffffffe
+    field public static final int DELETE_FAILED_INTERNAL_ERROR = -1; // 0xffffffff
+    field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc
+    field public static final int DELETE_KEEP_DATA = 1; // 0x1
+    field public static final int DELETE_SUCCEEDED = 1; // 0x1
     field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
     field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
@@ -3781,6 +3812,11 @@
   @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_AUTO_REVOKED}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
+  public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable {
+    method public void onUninstallComplete(@NonNull String, int, @Nullable String);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.UninstallCompleteCallback> CREATOR;
+  }
+
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     field @StringRes public final int backgroundRequestDetailResourceId;
     field @StringRes public final int backgroundRequestResourceId;
@@ -4240,6 +4276,7 @@
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfiguration(android.hardware.display.BrightnessConfiguration);
     method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public void setBrightnessConfigurationForDisplay(@NonNull android.hardware.display.BrightnessConfiguration, @NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.CONTROL_DISPLAY_SATURATION) public void setSaturationLevel(float);
+    field public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 65536; // 0x10000
     field public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1024; // 0x400
   }
 
@@ -4531,12 +4568,14 @@
 
   public final class HdmiPortInfo implements android.os.Parcelable {
     ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean);
+    ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean, boolean);
     method public int describeContents();
     method public int getAddress();
     method public int getId();
     method public int getType();
     method public boolean isArcSupported();
     method public boolean isCecSupported();
+    method public boolean isEarcSupported();
     method public boolean isMhlSupported();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiPortInfo> CREATOR;
@@ -5817,6 +5856,8 @@
     field public static final int DATA_STATUS_DISABLED_CONTAMINANT = 4; // 0x4
     field public static final int DATA_STATUS_DISABLED_DEBUG = 32; // 0x20
     field public static final int DATA_STATUS_DISABLED_DOCK = 8; // 0x8
+    field public static final int DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 128; // 0x80
+    field public static final int DATA_STATUS_DISABLED_DOCK_HOST_MODE = 64; // 0x40
     field public static final int DATA_STATUS_DISABLED_FORCE = 16; // 0x10
     field public static final int DATA_STATUS_DISABLED_OVERHEAT = 2; // 0x2
     field public static final int DATA_STATUS_ENABLED = 1; // 0x1
@@ -6592,6 +6633,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages();
     method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
     method public boolean isAudioServerRunning();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled();
     method public boolean isHdmiSystemAudioSupported();
     method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission(android.Manifest.permission.ACCESS_ULTRASOUND) public boolean isUltrasoundSupported();
@@ -6610,6 +6652,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setActiveAssistantServiceUids(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo, @IntRange(from=0) long);
     method public void setAudioServerStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.AudioServerStateCallback);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setBluetoothVariableLatencyEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setFocusRequestResult(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForCapturePreset(int, @NonNull android.media.AudioDeviceAttributes);
@@ -6617,6 +6660,7 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicyAsync(@NonNull android.media.audiopolicy.AudioPolicy);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterMuteAwaitConnectionCallback(@NonNull android.media.AudioManager.MuteAwaitConnectionCallback);
@@ -6671,6 +6715,7 @@
   }
 
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
+    method public int getChannelMask();
     method public int getClientPid();
     method public int getClientUid();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
@@ -6678,9 +6723,11 @@
     method public android.media.PlayerProxy getPlayerProxy();
     method public int getPlayerState();
     method public int getPlayerType();
+    method @IntRange(from=0) public int getSampleRate();
     method @IntRange(from=0) public int getSessionId();
     method public boolean isActive();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+    method public boolean isSpatialized();
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
     field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
@@ -9499,7 +9546,8 @@
     method @Deprecated @Nullable public android.net.wifi.nl80211.WifiNl80211Manager.SignalPollResult signalPoll(@NonNull String);
     method public boolean startPnoScan(@NonNull String, @NonNull android.net.wifi.nl80211.PnoSettings, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.nl80211.WifiNl80211Manager.PnoScanRequestCallback);
     method @Deprecated public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>);
-    method public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>, @Nullable android.os.Bundle);
+    method @Deprecated public boolean startScan(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>, @Nullable android.os.Bundle);
+    method public int startScan2(@NonNull String, int, @Nullable java.util.Set<java.lang.Integer>, @Nullable java.util.List<byte[]>, @Nullable android.os.Bundle);
     method public boolean stopPnoScan(@NonNull String);
     method public boolean tearDownClientInterface(@NonNull String);
     method public boolean tearDownInterfaces();
@@ -9533,7 +9581,8 @@
   }
 
   public static interface WifiNl80211Manager.ScanEventCallback {
-    method public void onScanFailed();
+    method @Deprecated public void onScanFailed();
+    method public default void onScanFailed(int);
     method public void onScanResultReady();
   }
 
@@ -10911,6 +10960,7 @@
     field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
     field public static final String ACTION_TETHER_SETTINGS = "android.settings.TETHER_SETTINGS";
     field public static final String ACTION_TETHER_UNSUPPORTED_CARRIER_UI = "android.settings.TETHER_UNSUPPORTED_CARRIER_UI";
+    field public static final String ACTION_USER_SETTINGS = "android.settings.USER_SETTINGS";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -12523,6 +12573,9 @@
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method @BinderThread public abstract void onDataProvided(@NonNull android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @BinderThread public abstract void onDataStreamProvided(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    method @BinderThread public abstract void onQueryServiceStatus(@NonNull java.util.Set<java.lang.Integer>, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>);
+    method @BinderThread public abstract void onStartDetection(@NonNull android.app.ambientcontext.AmbientContextEventRequest, @NonNull String, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionServiceStatus>, @NonNull java.util.function.Consumer<android.service.ambientcontext.AmbientContextDetectionResult>);
+    method public abstract void onStopDetection(@NonNull String);
     field public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService";
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 82cc3fd..233dee9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -125,7 +125,7 @@
   public class ActivityManager {
     method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
     method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
-    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getDisplayIdsForStartingVisibleBackgroundUsers();
     method public long getTotalRam();
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
     method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
@@ -136,7 +136,7 @@
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundOnSecondaryDisplay(int, int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -814,7 +814,7 @@
   public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
     method public boolean hasRequestForegroundServiceExemption();
     method public boolean isOnBackInvokedCallbackEnabled();
-    method public boolean isPrivilegedApp();
+    method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public boolean isPrivilegedApp();
     method public boolean isSystemApp();
     method public void setEnableOnBackInvokedCallback(boolean);
     field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8
@@ -831,7 +831,6 @@
 
   public static class PackageInstaller.SessionParams implements android.os.Parcelable {
     method public void setInstallFlagAllowTest();
-    method public void setInstallerPackageName(@Nullable String);
   }
 
   public abstract class PackageManager {
@@ -1299,6 +1298,7 @@
     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 public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String);
     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);
@@ -1942,7 +1942,7 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isSplitSystemUser();
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean isUserTypeEnabled(@NonNull String);
-    method public boolean isUsersOnSecondaryDisplaysSupported();
+    method public boolean isVisibleBackgroundUsersSupported();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
   }
 
@@ -3232,7 +3232,7 @@
 package android.view.inputmethod {
 
   public abstract class HandwritingGesture {
-    method public int getGestureType();
+    method public final int getGestureType();
   }
 
   public final class InlineSuggestion implements android.os.Parcelable {
@@ -3256,6 +3256,7 @@
     method public int getDisplayId();
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 8142ee5..a81ef18 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -547,7 +547,6 @@
      */
     void skipToEndValue(boolean inReverse) {}
 
-
     /**
      * Internal use only.
      *
@@ -565,13 +564,13 @@
      * repetition. lastPlayTime is similar and is used to calculate how many repeats have been
      * done between the two times.
      */
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {}
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
 
     /**
      * 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) {}
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
 
     /**
      * Internal use only. Adds all start times (after delay) to and end times to times.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 54aaafc..257adfe 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -188,6 +188,11 @@
      */
     private long[] mChildStartAndStopTimes;
 
+    /**
+     * Tracks whether we've notified listeners of the onAnimationStart() event.
+     */
+    private boolean mStartListenersCalled;
+
     // 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() {
@@ -736,14 +741,7 @@
             startAnimation();
         }
 
-        if (mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, inReverse);
-            }
-        }
+        notifyStartListeners(inReverse);
         if (isEmptySet) {
             // In the case of empty AnimatorSet, or 0 duration scale, we will trigger the
             // onAnimationEnd() right away.
@@ -751,6 +749,32 @@
         }
     }
 
+    private void notifyStartListeners(boolean inReverse) {
+        if (mListeners != null && !mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                AnimatorListener listener = tmpListeners.get(i);
+                listener.onAnimationStart(this, inReverse);
+            }
+        }
+        mStartListenersCalled = true;
+    }
+
+    private void notifyEndListeners(boolean inReverse) {
+        if (mListeners != null && mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                AnimatorListener listener = tmpListeners.get(i);
+                listener.onAnimationEnd(this, inReverse);
+            }
+        }
+        mStartListenersCalled = false;
+    }
+
     // Returns true if set is empty or contains nothing but animator sets with no start delay.
     private static boolean isEmptySet(AnimatorSet set) {
         if (set.getStartDelay() > 0) {
@@ -823,7 +847,8 @@
     private void animateBasedOnPlayTime(
             long currentPlayTime,
             long lastPlayTime,
-            boolean inReverse
+            boolean inReverse,
+            boolean notify
     ) {
         if (currentPlayTime < 0 || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
@@ -854,8 +879,8 @@
             while (index < endIndex) {
                 long playTime = startEndTimes[index];
                 if (lastPlayTime != playTime) {
-                    animateSkipToEnds(playTime, lastPlayTime);
-                    animateValuesInRange(playTime, lastPlayTime);
+                    animateSkipToEnds(playTime, lastPlayTime, notify);
+                    animateValuesInRange(playTime, lastPlayTime, notify);
                     lastPlayTime = playTime;
                 }
                 index++;
@@ -865,15 +890,15 @@
                 index--;
                 long playTime = startEndTimes[index];
                 if (lastPlayTime != playTime) {
-                    animateSkipToEnds(playTime, lastPlayTime);
-                    animateValuesInRange(playTime, lastPlayTime);
+                    animateSkipToEnds(playTime, lastPlayTime, notify);
+                    animateValuesInRange(playTime, lastPlayTime, notify);
                     lastPlayTime = playTime;
                 }
             }
         }
         if (currentPlayTime != lastPlayTime) {
-            animateSkipToEnds(currentPlayTime, lastPlayTime);
-            animateValuesInRange(currentPlayTime, lastPlayTime);
+            animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
+            animateValuesInRange(currentPlayTime, lastPlayTime, notify);
         }
     }
 
@@ -893,10 +918,13 @@
     }
 
     @Override
-    void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
         initAnimation();
 
         if (lastPlayTime > currentPlayTime) {
+            if (notify) {
+                notifyStartListeners(true);
+            }
             for (int i = mEvents.size() - 1; i >= 0; i--) {
                 AnimationEvent event = mEvents.get(i);
                 Node node = event.mNode;
@@ -904,23 +932,31 @@
                         && node.mStartTime != DURATION_INFINITE
                 ) {
                     Animator animator = node.mAnimation;
-                    long start = node.mStartTime + animator.getStartDelay();
+                    long start = node.mStartTime;
                     long end = node.mTotalDuration == DURATION_INFINITE
                             ? Long.MAX_VALUE : node.mEndTime;
                     if (currentPlayTime <= start && start < lastPlayTime) {
                         animator.animateSkipToEnds(
-                                start - node.mStartTime,
-                                lastPlayTime - node.mStartTime
+                                0,
+                                lastPlayTime - node.mStartTime,
+                                notify
                         );
                     } else if (start <= currentPlayTime && currentPlayTime <= end) {
                         animator.animateSkipToEnds(
                                 currentPlayTime - node.mStartTime,
-                                lastPlayTime - node.mStartTime
+                                lastPlayTime - node.mStartTime,
+                                notify
                         );
                     }
                 }
             }
+            if (currentPlayTime <= 0 && notify) {
+                notifyEndListeners(true);
+            }
         } else {
+            if (notify) {
+                notifyStartListeners(false);
+            }
             int eventsSize = mEvents.size();
             for (int i = 0; i < eventsSize; i++) {
                 AnimationEvent event = mEvents.get(i);
@@ -929,29 +965,48 @@
                         && node.mStartTime != DURATION_INFINITE
                 ) {
                     Animator animator = node.mAnimation;
-                    long start = node.mStartTime + animator.getStartDelay();
+                    long start = node.mStartTime;
                     long end = node.mTotalDuration == DURATION_INFINITE
                             ? Long.MAX_VALUE : node.mEndTime;
                     if (lastPlayTime < end && end <= currentPlayTime) {
                         animator.animateSkipToEnds(
                                 end - node.mStartTime,
-                                lastPlayTime - node.mStartTime
+                                lastPlayTime - node.mStartTime,
+                                notify
                         );
                     } else if (start <= currentPlayTime && currentPlayTime <= end) {
                         animator.animateSkipToEnds(
                                 currentPlayTime - node.mStartTime,
-                                lastPlayTime - node.mStartTime
+                                lastPlayTime - node.mStartTime,
+                                notify
                         );
                     }
                 }
             }
+            if (currentPlayTime >= getTotalDuration() && notify) {
+                notifyEndListeners(false);
+            }
         }
     }
 
     @Override
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
         initAnimation();
 
+        if (notify) {
+            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+                notifyStartListeners(false);
+            } else {
+                long duration = getTotalDuration();
+                if (duration >= 0
+                        && (lastPlayTime > duration || (lastPlayTime == duration
+                        && currentPlayTime < duration))
+                ) {
+                    notifyStartListeners(true);
+                }
+            }
+        }
+
         int eventsSize = mEvents.size();
         for (int i = 0; i < eventsSize; i++) {
             AnimationEvent event = mEvents.get(i);
@@ -960,13 +1015,17 @@
                     && node.mStartTime != DURATION_INFINITE
             ) {
                 Animator animator = node.mAnimation;
-                long start = node.mStartTime + animator.getStartDelay();
+                long start = node.mStartTime;
                 long end = node.mTotalDuration == DURATION_INFINITE
                         ? Long.MAX_VALUE : node.mEndTime;
-                if (start < currentPlayTime && currentPlayTime < end) {
+                if ((start < currentPlayTime && currentPlayTime < end)
+                        || (start == currentPlayTime && lastPlayTime < start)
+                        || (end == currentPlayTime && lastPlayTime > end)
+                ) {
                     animator.animateValuesInRange(
                             currentPlayTime - node.mStartTime,
-                            Math.max(-1, lastPlayTime - node.mStartTime)
+                            Math.max(-1, lastPlayTime - node.mStartTime),
+                            notify
                     );
                 }
             }
@@ -1021,6 +1080,11 @@
      * set to this time; it will simply set the time to this value and perform any appropriate
      * actions based on that time. If the animation is already running, then setCurrentPlayTime()
      * will set the current playing time to this value and continue playing from that point.
+     * On {@link Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and above, an AnimatorSet
+     * that hasn't been {@link #start()}ed, will issue
+     * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator, boolean)}
+     * and {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator, boolean)}
+     * events.
      *
      * @param playTime The time, in milliseconds, to which the animation is advanced or rewound.
      *                 Unless the animation is reversing, the playtime is considered the time since
@@ -1042,12 +1106,12 @@
 
         initAnimation();
 
+        long lastPlayTime = mSeekState.getPlayTime();
         if (!isStarted() || isPaused()) {
             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);
                 initChildren();
@@ -1055,13 +1119,9 @@
                 skipToEndValue(!mReversing);
                 mSeekState.setPlayTime(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
-            // (i.e. doAnimationFrame(...)) to advance the animation.
-            mSeekState.setPlayTime(playTime, mReversing);
         }
+        animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+        mSeekState.setPlayTime(playTime, mReversing);
     }
 
     /**
@@ -1101,7 +1161,7 @@
 
             long previousTime = -1;
             for (long time : times) {
-                animateBasedOnPlayTime(time, previousTime, false);
+                animateBasedOnPlayTime(time, previousTime, false, false);
                 previousTime = time;
             }
         }
@@ -1397,15 +1457,7 @@
 
         // No longer receive callbacks
         removeAnimationCallback();
-        // Call end listener
-        if (mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, mReversing);
-            }
-        }
+        notifyEndListeners(mReversing);
         removeAnimationEndListener();
         mSelfPulse = true;
         mReversing = false;
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index d41c03d..7009725 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1108,18 +1108,30 @@
         }
     }
 
-    private void notifyStartListeners() {
+    private void notifyStartListeners(boolean isReversing) {
         if (mListeners != null && !mStartListenersCalled) {
             ArrayList<AnimatorListener> tmpListeners =
                     (ArrayList<AnimatorListener>) mListeners.clone();
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationStart(this, mReversing);
+                tmpListeners.get(i).onAnimationStart(this, isReversing);
             }
         }
         mStartListenersCalled = true;
     }
 
+    private void notifyEndListeners(boolean isReversing) {
+        if (mListeners != null && mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, isReversing);
+            }
+        }
+        mStartListenersCalled = false;
+    }
+
     /**
      * Start the animation playing. This version of start() takes a boolean flag that indicates
      * whether the animation should play in reverse. The flag is usually false, but may be set
@@ -1210,7 +1222,7 @@
         if ((mStarted || mRunning) && mListeners != null) {
             if (!mRunning) {
                 // If it's not yet running, then start listeners weren't called. Call them now.
-                notifyStartListeners();
+                notifyStartListeners(mReversing);
             }
             int listenersSize = mListeners.size();
             if (listenersSize > 0) {
@@ -1324,22 +1336,14 @@
         boolean notify = (mStarted || mRunning) && mListeners != null;
         if (notify && !mRunning) {
             // If it's not yet running, then start listeners weren't called. Call them now.
-            notifyStartListeners();
+            notifyStartListeners(mReversing);
         }
         mRunning = false;
         mStarted = false;
-        mStartListenersCalled = false;
         mLastFrameTime = -1;
         mFirstFrameTime = -1;
         mStartTime = -1;
-        if (notify && mListeners != null) {
-            ArrayList<AnimatorListener> tmpListeners =
-                    (ArrayList<AnimatorListener>) mListeners.clone();
-            int numListeners = tmpListeners.size();
-            for (int i = 0; i < numListeners; ++i) {
-                tmpListeners.get(i).onAnimationEnd(this, mReversing);
-            }
-        }
+        notifyEndListeners(mReversing);
         // mReversing needs to be reset *after* notifying the listeners for the end callbacks.
         mReversing = false;
         if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
@@ -1366,9 +1370,8 @@
         } else {
             mOverallFraction = 0f;
         }
-        if (mListeners != null) {
-            notifyStartListeners();
-        }
+
+        notifyStartListeners(mReversing);
     }
 
     /**
@@ -1459,13 +1462,22 @@
      * will be called.
      */
     @Override
-    void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
-        if (currentPlayTime < mStartDelay || lastPlayTime < -1) {
+    void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+        if (currentPlayTime < 0 || lastPlayTime < -1) {
             throw new UnsupportedOperationException("Error: Play time should never be negative.");
         }
 
         initAnimation();
         long duration = getTotalDuration();
+        if (notify) {
+            if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+                notifyStartListeners(false);
+            } else if (lastPlayTime > duration
+                    || (lastPlayTime == duration && currentPlayTime < duration)
+            ) {
+                notifyStartListeners(true);
+            }
+        }
         if (duration >= 0) {
             lastPlayTime = Math.min(duration, lastPlayTime);
         }
@@ -1474,8 +1486,8 @@
 
         // Check whether repeat callback is needed only when repeat count is non-zero
         if (mRepeatCount > 0) {
-            int iteration = (int) (currentPlayTime / mDuration);
-            int lastIteration = (int) (lastPlayTime / mDuration);
+            int iteration = Math.max(0, (int) (currentPlayTime / mDuration));
+            int lastIteration = Math.max(0, (int) (lastPlayTime / mDuration));
 
             // Clamp iteration to [0, mRepeatCount]
             iteration = Math.min(iteration, mRepeatCount);
@@ -1491,24 +1503,33 @@
             }
         }
 
-        if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+        if (mRepeatCount != INFINITE && currentPlayTime > (mRepeatCount + 1) * mDuration) {
             throw new IllegalStateException("Can't animate a value outside of the duration");
         } else {
             // Find the current fraction:
-            float fraction = currentPlayTime / (float) mDuration;
+            float fraction = Math.max(0, currentPlayTime) / (float) mDuration;
             fraction = getCurrentIterationFraction(fraction, false);
             animateValue(fraction);
         }
     }
 
     @Override
-    void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
-        if (currentPlayTime <= mStartDelay && lastPlayTime > mStartDelay) {
-            skipToEndValue(true);
+    void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+        boolean inReverse = currentPlayTime < lastPlayTime;
+        boolean doSkip;
+        if (currentPlayTime <= 0 && lastPlayTime > 0) {
+            doSkip = true;
         } else {
             long duration = getTotalDuration();
-            if (duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration) {
-                skipToEndValue(false);
+            doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
+        }
+        if (doSkip) {
+            if (notify) {
+                notifyStartListeners(inReverse);
+            }
+            skipToEndValue(inReverse);
+            if (notify) {
+                notifyEndListeners(inReverse);
             }
         }
     }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index d1772e37..da88f4b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1015,6 +1015,8 @@
 
     private ComponentCallbacksController mCallbacksController;
 
+    @Nullable private IVoiceInteractionManagerService mVoiceInteractionManagerService;
+
     private final WindowControllerCallback mWindowControllerCallback =
             new WindowControllerCallback() {
         /**
@@ -1624,18 +1626,17 @@
 
     private void notifyVoiceInteractionManagerServiceActivityEvent(
             @VoiceInteractionSession.VoiceInteractionActivityEventType int type) {
-
-        final IVoiceInteractionManagerService service =
-                IVoiceInteractionManagerService.Stub.asInterface(
-                        ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
-        if (service == null) {
-            Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
-                    + "VoiceInteractionManagerService");
-            return;
+        if (mVoiceInteractionManagerService == null) {
+            mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
+                    ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
+            if (mVoiceInteractionManagerService == null) {
+                Log.w(TAG, "notifyVoiceInteractionManagerServiceActivityEvent: Can not get "
+                        + "VoiceInteractionManagerService");
+                return;
+            }
         }
-
         try {
-            service.notifyActivityEventChanged(mToken, type);
+            mVoiceInteractionManagerService.notifyActivityEventChanged(mToken, type);
         } catch (RemoteException e) {
             // Empty
         }
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index ce99119..558dae5 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -59,6 +59,15 @@
         }
     }
 
+    /** Reports {@link android.app.servertransaction.RefreshCallbackItem} is executed. */
+    public void activityRefreshed(IBinder token) {
+        try {
+            getActivityClientController().activityRefreshed(token);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Reports after {@link Activity#onTopResumedActivityChanged(boolean)} is called for losing the
      * top most position.
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index cf07114..174c982 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -4380,7 +4380,7 @@
      *
      * <p>This method will allow the user to launch activities on that display, and it's typically
      * used only on automotive builds when the vehicle has multiple displays (you can verify if it's
-     * supported by calling {@link UserManager#isUsersOnSecondaryDisplaysSupported()}).
+     * supported by calling {@link UserManager#isVisibleBackgroundUsersSupported()}).
      *
      * <p><b>NOTE:</b> differently from {@link #switchUser(int)}, which stops the current foreground
      * user before starting a new one, this method does not stop the previous user running in
@@ -4404,14 +4404,13 @@
     @TestApi
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS})
-    public boolean startUserInBackgroundOnSecondaryDisplay(@UserIdInt int userId,
-            int displayId) {
-        if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+    public boolean startUserInBackgroundVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+        if (!UserManager.isVisibleBackgroundUsersEnabled()) {
             throw new UnsupportedOperationException(
                     "device does not support users on secondary displays");
         }
         try {
-            return getService().startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+            return getService().startUserInBackgroundVisibleOnDisplay(userId, displayId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4427,9 +4426,9 @@
     @Nullable
     @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
             android.Manifest.permission.INTERACT_ACROSS_USERS})
-    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+    public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
         try {
-            return getService().getSecondaryDisplayIdsForStartingBackgroundUsers();
+            return getService().getDisplayIdsForStartingVisibleBackgroundUsers();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -4589,7 +4588,7 @@
      * Stops the given {@code userId}.
      *
      * <p><b>NOTE:</b> on systems that support
-     * {@link UserManager#isUsersOnSecondaryDisplaysSupported() background users on secondary
+     * {@link UserManager#isVisibleBackgroundUsersSupported() background users on secondary
      * displays}, this method will also unassign the user from the display it was started on.
      *
      * @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index c51e8ae..6826b67 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -907,4 +907,19 @@
      * Return all client package names of a service.
      */
     public abstract ArraySet<String> getClientPackages(String servicePackageName);
+
+    /**
+     * Retrieve an IUnsafeIntentStrictModeCallback matching the given callingUid.
+     * Returns null no match is found.
+     * @param callingPid The PID mapped with the callback.
+     * @return The callback, if it exists.
+     */
+    public abstract IUnsafeIntentStrictModeCallback getRegisteredStrictModeCallback(
+            int callingPid);
+
+    /**
+     * Unregisters an IUnsafeIntentStrictModeCallback matching the given callingUid.
+     * @param callingPid The PID mapped with the callback.
+     */
+    public abstract void unregisterStrictModeCallback(int callingPid);
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 1b92312..2214c8e 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1499,13 +1499,13 @@
      * Returns whether the launching app has opted-in to sharing its identity with the launched
      * activity.
      *
+     * @return {@code true} if the launching app has opted-in to sharing its identity
+     *
      * @see #setShareIdentityEnabled(boolean)
      * @see Activity#getLaunchedFromUid()
      * @see Activity#getLaunchedFromPackage()
-     *
-     * @hide
      */
-    public boolean getShareIdentity() {
+    public boolean isShareIdentityEnabled() {
         return mShareIdentity;
     }
 
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 37749e6..8e747b2 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -62,6 +62,7 @@
 import android.app.servertransaction.TransactionExecutor;
 import android.app.servertransaction.TransactionExecutorHelper;
 import android.bluetooth.BluetoothFrameworkInitializer;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
@@ -361,6 +362,8 @@
     private int mLastProcessState = PROCESS_STATE_UNKNOWN;
     ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
     private int mLastSessionId;
+    // Holds the value of the last reported device ID value from the server for the top activity.
+    int mLastReportedDeviceId;
     final ArrayMap<IBinder, CreateServiceData> mServicesData = new ArrayMap<>();
     @UnsupportedAppUsage
     final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
@@ -546,6 +549,9 @@
         boolean hideForNow;
         Configuration createdConfig;
         Configuration overrideConfig;
+        // TODO(b/263402465): pass deviceId directly in LaunchActivityItem#execute
+        // The deviceId assigned by the server when this activity was first started.
+        int mDeviceId;
         // Used for consolidating configs before sending on to Activity.
         private Configuration tmpConfig = new Configuration();
         // Callback used for updating activity override config and camera compat control state.
@@ -608,7 +614,7 @@
         }
 
         public ActivityClientRecord(IBinder token, Intent intent, int ident,
-                ActivityInfo info, Configuration overrideConfig,
+                ActivityInfo info, Configuration overrideConfig, int deviceId,
                 String referrer, IVoiceInteractor voiceInteractor, Bundle state,
                 PersistableBundle persistentState, List<ResultInfo> pendingResults,
                 List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -630,6 +636,7 @@
             this.isForward = isForward;
             this.profilerInfo = profilerInfo;
             this.overrideConfig = overrideConfig;
+            this.mDeviceId = deviceId;
             this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo);
             mActivityOptions = activityOptions;
             mLaunchedFromBubble = launchedFromBubble;
@@ -3816,6 +3823,7 @@
 
         // Make sure we are running with the most recent config.
         mConfigurationController.handleConfigurationChanged(null, null);
+        updateDeviceIdForNonUIContexts(r.mDeviceId);
 
         if (localLOGV) Slog.v(
             TAG, "Handling launch of " + r);
@@ -4548,6 +4556,9 @@
             context.setOuterContext(service);
             service.attach(context, this, data.info.name, data.token, app,
                     ActivityManager.getService());
+            if (!service.isUiContext()) { // WindowProviderService is a UI Context.
+                service.updateDeviceId(mLastReportedDeviceId);
+            }
             service.onCreate();
             mServicesData.put(data.token, data);
             mServices.put(data.token, service);
@@ -5342,6 +5353,11 @@
         }
     }
 
+    @Override
+    public void reportRefresh(ActivityClientRecord r) {
+        ActivityClient.getInstance().activityRefreshed(r.token);
+    }
+
     private void handleSetCoreSettings(Bundle coreSettings) {
         synchronized (mCoreSettingsLock) {
             mCoreSettings = coreSettings;
@@ -6061,9 +6077,48 @@
         }
     }
 
+    private void updateDeviceIdForNonUIContexts(int deviceId) {
+        // Invalid device id is treated as a no-op.
+        if (deviceId == VirtualDeviceManager.DEVICE_ID_INVALID) {
+            return;
+        }
+        if (deviceId == mLastReportedDeviceId) {
+            return;
+        }
+        mLastReportedDeviceId = deviceId;
+        ArrayList<Context> nonUIContexts = new ArrayList<>();
+        // Update Application and Service contexts with implicit device association.
+        // UI Contexts are able to derived their device Id association from the display.
+        synchronized (mResourcesManager) {
+            final int numApps = mAllApplications.size();
+            for (int i = 0; i < numApps; i++) {
+                nonUIContexts.add(mAllApplications.get(i));
+            }
+            final int numServices = mServices.size();
+            for (int i = 0; i < numServices; i++) {
+                final Service service = mServices.valueAt(i);
+                // WindowProviderService is a UI Context.
+                if (!service.isUiContext()) {
+                    nonUIContexts.add(service);
+                }
+            }
+        }
+        for (Context context : nonUIContexts) {
+            try {
+                context.updateDeviceId(deviceId);
+            } catch (IllegalArgumentException e) {
+                // It can happen that the system already closed/removed a virtual device
+                // and the passed deviceId is no longer valid.
+                // TODO(b/263355088): check for validity of deviceId before updating
+                // instead of catching this exception once VDM add an API to validate ids.
+            }
+        }
+    }
+
     @Override
-    public void handleConfigurationChanged(Configuration config) {
+    public void handleConfigurationChanged(Configuration config, int deviceId) {
         mConfigurationController.handleConfigurationChanged(config);
+        updateDeviceIdForNonUIContexts(deviceId);
 
         // These are only done to maintain @UnsupportedAppUsage and should be removed someday.
         mCurDefaultDisplayDpi = mConfigurationController.getCurDefaultDisplayDpi();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 309b253..a7a4b35 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -3875,4 +3875,19 @@
             throw e.rethrowAsRuntimeException();
         }
     }
+
+    @Override
+    public boolean canUserUninstall(String packageName, UserHandle user) {
+        try {
+            return mPM.getBlockUninstallForUser(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public boolean shouldShowNewAppInstalledNotification() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 1;
+    }
 }
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f322ca9..3ba5783 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -139,6 +139,9 @@
     /** Restart the activity after it was stopped. */
     public abstract void performRestartActivity(@NonNull ActivityClientRecord r, boolean start);
 
+     /** Report that activity was refreshed to server. */
+    public abstract void reportRefresh(@NonNull ActivityClientRecord r);
+
     /** Set pending activity configuration in case it will be updated by other transaction item. */
     public abstract void updatePendingActivityConfiguration(@NonNull IBinder token,
             Configuration overrideConfig);
@@ -181,8 +184,8 @@
     /** Get package info. */
     public abstract LoadedApk getPackageInfoNoCheck(ApplicationInfo ai);
 
-    /** Deliver app configuration change notification. */
-    public abstract void handleConfigurationChanged(Configuration config);
+    /** Deliver app configuration change notification and device association. */
+    public abstract void handleConfigurationChanged(Configuration config, int deviceId);
 
     /**
      * Get {@link android.app.ActivityThread.ActivityClientRecord} instance that corresponds to the
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 39f7153..1120257 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -21,12 +21,14 @@
 import static android.os.StrictMode.vmIncorrectContextUseEnabled;
 import static android.view.WindowManager.LayoutParams.WindowType;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
 import android.companion.virtual.VirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.AttributionSource;
 import android.content.AutofillOptions;
@@ -121,6 +123,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 class ReceiverRestrictedContext extends ContextWrapper {
     @UnsupportedAppUsage
@@ -257,6 +260,13 @@
     /** @see Context#isConfigurationContext() */
     private boolean mIsConfigurationBasedContext;
 
+    /**
+     *  Indicates that this context was created with an explicit device ID association via
+     *  Context#createDeviceContext and under no circumstances will it ever change, even if
+     *  this context is not associated with a display id, or if the associated display id changes.
+     */
+    private boolean mIsExplicitDeviceId = false;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final int mFlags;
 
@@ -372,6 +382,24 @@
     @ServiceInitializationState
     final int[] mServiceInitializationStateArray = new int[mServiceCache.length];
 
+    private final Object mDeviceIdListenerLock = new Object();
+    /**
+     * List of listeners for deviceId changes and their associated Executor.
+     * List is lazy-initialized on first registration
+     */
+    @GuardedBy("mDeviceIdListenerLock")
+    @Nullable
+    private ArrayList<DeviceIdChangeListenerDelegate> mDeviceIdChangeListeners;
+
+    private static class DeviceIdChangeListenerDelegate {
+        final @NonNull IntConsumer mListener;
+        final @NonNull Executor mExecutor;
+        DeviceIdChangeListenerDelegate(IntConsumer listener, Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+    }
+
     @UnsupportedAppUsage
     static ContextImpl getImpl(Context context) {
         Context nextContext;
@@ -573,7 +601,8 @@
                             && !getSystemService(UserManager.class)
                                     .isUserUnlockingOrUnlocked(UserHandle.myUserId())) {
                         throw new IllegalStateException("SharedPreferences in credential encrypted "
-                                + "storage are not available until after user is unlocked");
+                                + "storage are not available until after user (id "
+                                + UserHandle.myUserId() + ") is unlocked");
                     }
                 }
                 sp = new SharedPreferencesImpl(file, mode);
@@ -2681,7 +2710,7 @@
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
                 overrideConfig, display.getDisplayAdjustments().getCompatibilityInfo(),
                 mResources.getLoaders()));
-        context.mDisplay = display;
+        context.setDisplay(display);
         // Inherit context type if the container is from System or System UI context to bypass
         // UI context check.
         context.mContextType = mContextType == CONTEXT_TYPE_SYSTEM_OR_SYSTEM_UI
@@ -2697,17 +2726,16 @@
         return context;
     }
 
+    private void setDisplay(Display display) {
+        mDisplay = display;
+        if (display != null) {
+            updateDeviceIdIfChanged(display.getDisplayId());
+        }
+    }
+
     @Override
     public @NonNull Context createDeviceContext(int deviceId) {
-        boolean validDeviceId = deviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT;
-        if (deviceId > VirtualDeviceManager.DEVICE_ID_DEFAULT) {
-            VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
-            if (vdm != null) {
-                List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
-                validDeviceId = virtualDevices.stream().anyMatch(d -> d.getDeviceId() == deviceId);
-            }
-        }
-        if (!validDeviceId) {
+        if (!isValidDeviceId(deviceId)) {
             throw new IllegalArgumentException(
                     "Not a valid ID of the default device or any virtual device: " + deviceId);
         }
@@ -2718,9 +2746,35 @@
                 mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         context.mDeviceId = deviceId;
+        context.mIsExplicitDeviceId = true;
         return context;
     }
 
+    /**
+     * Checks whether the passed {@code deviceId} is valid or not.
+     * {@link VirtualDeviceManager#DEVICE_ID_DEFAULT} is valid as it is the ID of the default
+     * device when no additional virtual devices exist. If {@code deviceId} is the id of
+     * a virtual device, it should correspond to a virtual device created by
+     * {@link VirtualDeviceManager#createVirtualDevice(int, VirtualDeviceParams)}.
+     */
+    private boolean isValidDeviceId(int deviceId) {
+        if (deviceId == VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+            return true;
+        }
+        if (deviceId > VirtualDeviceManager.DEVICE_ID_DEFAULT) {
+            VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+            if (vdm != null) {
+                List<VirtualDevice> virtualDevices = vdm.getVirtualDevices();
+                for (int i = 0; i < virtualDevices.size(); i++) {
+                    if (virtualDevices.get(i).getDeviceId() == deviceId) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
     @NonNull
     @Override
     public WindowContext createWindowContext(@WindowType int type,
@@ -2816,8 +2870,8 @@
         baseContext.setResources(windowContextResources);
         // Associate the display with window context resources so that configuration update from
         // the server side will also apply to the display's metrics.
-        baseContext.mDisplay = ResourcesManager.getInstance().getAdjustedDisplay(displayId,
-                windowContextResources);
+        baseContext.setDisplay(ResourcesManager.getInstance().getAdjustedDisplay(
+                displayId, windowContextResources));
 
         return baseContext;
     }
@@ -2961,18 +3015,109 @@
 
     @Override
     public void updateDisplay(int displayId) {
-        mDisplay = mResourcesManager.getAdjustedDisplay(displayId, mResources);
+        setDisplay(mResourcesManager.getAdjustedDisplay(displayId, mResources));
         if (mContextType == CONTEXT_TYPE_NON_UI) {
             mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
         }
     }
 
+    private void updateDeviceIdIfChanged(int displayId) {
+        if (mIsExplicitDeviceId) {
+            return;
+        }
+        VirtualDeviceManager vdm = getSystemService(VirtualDeviceManager.class);
+        if (vdm != null) {
+            int deviceId = vdm.getDeviceIdForDisplayId(displayId);
+            if (deviceId != mDeviceId) {
+                mDeviceId = deviceId;
+                notifyOnDeviceChangedListeners(mDeviceId);
+            }
+        }
+    }
+
+    @Override
+    public void updateDeviceId(int updatedDeviceId) {
+        if (!isValidDeviceId(updatedDeviceId)) {
+            throw new IllegalArgumentException(
+                    "Not a valid ID of the default device or any virtual device: " + mDeviceId);
+        }
+        if (mIsExplicitDeviceId) {
+            throw new UnsupportedOperationException(
+                    "Cannot update device ID on a Context created with createDeviceContext()");
+        }
+        mDeviceId = updatedDeviceId;
+        notifyOnDeviceChangedListeners(updatedDeviceId);
+    }
+
     @Override
     public int getDeviceId() {
         return mDeviceId;
     }
 
     @Override
+    public boolean isDeviceContext() {
+        return mIsExplicitDeviceId || isAssociatedWithDisplay();
+    }
+
+    @Override
+    public void registerDeviceIdChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer listener) {
+        Objects.requireNonNull(executor, "executor cannot be null");
+        Objects.requireNonNull(listener, "listener cannot be null");
+
+        synchronized (mDeviceIdListenerLock) {
+            if (getDeviceIdListener(listener) != null) {
+                throw new IllegalArgumentException(
+                        "attempt to call registerDeviceIdChangeListener() "
+                                + "on a previously registered listener");
+            }
+            // lazy initialization
+            if (mDeviceIdChangeListeners == null) {
+                mDeviceIdChangeListeners = new ArrayList<>();
+            }
+            mDeviceIdChangeListeners.add(new DeviceIdChangeListenerDelegate(listener, executor));
+        }
+    }
+
+    @Override
+    public void unregisterDeviceIdChangeListener(@NonNull IntConsumer listener) {
+        Objects.requireNonNull(listener, "listener cannot be null");
+        synchronized (mDeviceIdListenerLock) {
+            DeviceIdChangeListenerDelegate listenerToRemove = getDeviceIdListener(listener);
+            if (listenerToRemove != null) {
+                mDeviceIdChangeListeners.remove(listenerToRemove);
+            }
+        }
+    }
+
+    @GuardedBy("mDeviceIdListenerLock")
+    @Nullable
+    private DeviceIdChangeListenerDelegate getDeviceIdListener(
+            @Nullable IntConsumer listener) {
+        if (mDeviceIdChangeListeners == null) {
+            return null;
+        }
+        for (int i = 0; i < mDeviceIdChangeListeners.size(); i++) {
+            DeviceIdChangeListenerDelegate delegate = mDeviceIdChangeListeners.get(i);
+            if (delegate.mListener == listener) {
+                return delegate;
+            }
+        }
+        return null;
+    }
+
+    private void notifyOnDeviceChangedListeners(int deviceId) {
+        synchronized (mDeviceIdListenerLock) {
+            if (mDeviceIdChangeListeners != null) {
+                for (DeviceIdChangeListenerDelegate delegate : mDeviceIdChangeListeners) {
+                    delegate.mExecutor.execute(() ->
+                            delegate.mListener.accept(deviceId));
+                }
+            }
+        }
+    }
+
+    @Override
     public DisplayAdjustments getDisplayAdjustments(int displayId) {
         return mResources.getDisplayAdjustments();
     }
@@ -3182,8 +3327,8 @@
                 classLoader,
                 packageInfo.getApplication() == null ? null
                         : packageInfo.getApplication().getResources().getLoaders()));
-        context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
-                context.getResources());
+        context.setDisplay(resourcesManager.getAdjustedDisplay(
+                displayId, context.getResources()));
         return context;
     }
 
@@ -3193,7 +3338,6 @@
             @Nullable String splitName, @Nullable IBinder token, @Nullable UserHandle user,
             int flags, @Nullable ClassLoader classLoader, @Nullable String overrideOpPackageName) {
         mOuterContext = this;
-
         // If creator didn't specify which storage to use, use the default
         // location for application.
         if ((flags & (Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE
@@ -3227,6 +3371,8 @@
             opPackageName = container.mOpPackageName;
             setResources(container.mResources);
             mDisplay = container.mDisplay;
+            mDeviceId = container.mDeviceId;
+            mIsExplicitDeviceId = container.mIsExplicitDeviceId;
             mForceDisplayOverrideInResources = container.mForceDisplayOverrideInResources;
             mIsConfigurationBasedContext = container.mIsConfigurationBasedContext;
             mContextType = container.mContextType;
diff --git a/core/java/android/app/GrammaticalInflectionManager.java b/core/java/android/app/GrammaticalInflectionManager.java
new file mode 100644
index 0000000..1905b6a
--- /dev/null
+++ b/core/java/android/app/GrammaticalInflectionManager.java
@@ -0,0 +1,93 @@
+/*
+ * 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.annotation.SystemService;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.RemoteException;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class allow applications to control granular grammatical inflection settings (such as
+ * per-app grammatical gender).
+ */
+@SystemService(Context.GRAMMATICAL_INFLECTION_SERVICE)
+public class GrammaticalInflectionManager {
+    private static final Set<Integer> VALID_GENDER_VALUES = new HashSet<>(Arrays.asList(
+            Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED,
+            Configuration.GRAMMATICAL_GENDER_NEUTRAL,
+            Configuration.GRAMMATICAL_GENDER_FEMININE,
+            Configuration.GRAMMATICAL_GENDER_MASCULINE));
+
+    private final Context mContext;
+    private final IGrammaticalInflectionManager mService;
+
+    /** @hide Instantiated by ContextImpl */
+    public GrammaticalInflectionManager(Context context, IGrammaticalInflectionManager service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Returns the current grammatical gender for the calling app. A new value can be requested via
+     * {@link #setRequestedApplicationGrammaticalGender(int)} and will be updated with a new
+     * configuration change. The method always returns the value received with the last received
+     * configuration change.
+     *
+     * @return the value of grammatical gender
+     * @see Configuration#getGrammaticalGender
+     */
+    @Configuration.GrammaticalGender
+    public int getApplicationGrammaticalGender() {
+        return mContext.getApplicationContext()
+                .getResources()
+                .getConfiguration()
+                .getGrammaticalGender();
+    }
+
+    /**
+     * Sets the current grammatical gender for the calling app (keyed by package name and user ID
+     * retrieved from the calling pid).
+     *
+     * <p><b>Note:</b> Changes to app grammatical gender will result in a configuration change (and
+     * potentially an Activity re-creation) being applied to the specified application. For more
+     * information, see the <a
+     * href="https://developer.android.com/guide/topics/resources/runtime-changes">section on
+     * handling configuration changes</a>. The set grammatical gender are persisted across
+     * application restarts; they are backed up if the user has enabled Backup & Restore.`
+     *
+     * @param grammaticalGender the terms of address the user preferred in an application.
+     * @see Configuration#getGrammaticalGender
+     */
+    public void setRequestedApplicationGrammaticalGender(
+            @Configuration.GrammaticalGender int grammaticalGender) {
+        if (!VALID_GENDER_VALUES.contains(grammaticalGender)) {
+            throw new IllegalArgumentException("Unknown grammatical gender");
+        }
+
+        try {
+            mService.setRequestedApplicationGrammaticalGender(
+                    mContext.getPackageName(), mContext.getUserId(), grammaticalGender);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 286b84c..ecea46a 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -39,6 +39,7 @@
 interface IActivityClientController {
     oneway void activityIdle(in IBinder token, in Configuration config, in boolean stopProfiling);
     oneway void activityResumed(in IBinder token, in boolean handleSplashScreenExit);
+    oneway void activityRefreshed(in IBinder token);
     /**
      * This call is not one-way because {@link #activityPaused()) is not one-way, or
      * the top-resumed-lost could be reported after activity paused.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index b120ea7..0866d94 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -331,6 +331,7 @@
     @UnsupportedAppUsage
     void handleApplicationStrictModeViolation(in IBinder app, int penaltyMask,
             in StrictMode.ViolationInfo crashInfo);
+    void registerStrictModeCallback(in IBinder binder);
     boolean isTopActivityImmersive();
     void crashApplicationWithType(int uid, int initialPid, in String packageName, int userId,
             in String message, boolean force, int exceptionTypeId);
@@ -798,14 +799,14 @@
      */
     @JavaPassthrough(annotation=
             "@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
-    boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+    boolean startUserInBackgroundVisibleOnDisplay(int userid, int displayId);
 
     /**
-     * Gets the ids of displays that can be used on {@link #startUserInBackgroundOnSecondaryDisplay(int userId, int displayId)}.
+     * Gets the ids of displays that can be used on {@link #startUserInBackgroundVisibleOnDisplay(int userId, int displayId)}.
      *
      * <p>Typically used only by automotive builds when the vehicle has multiple displays.
      */
-    @nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
+    @nullable int[] getDisplayIdsForStartingVisibleBackgroundUsers();
 
     /** Returns if the service is a short-service is still "alive" and past the timeout. */
     boolean shouldServiceTimeOut(in ComponentName className, in IBinder token);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 91add27..461aa3c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -198,9 +198,8 @@
      * @param taskId The id of the task to set the bounds for.
      * @param bounds The new bounds.
      * @param resizeMode Resize mode defined as {@code ActivityTaskManager#RESIZE_MODE_*} constants.
-     * @return Return true on success. Otherwise false.
      */
-    boolean resizeTask(int taskId, in Rect bounds, int resizeMode);
+    void resizeTask(int taskId, in Rect bounds, int resizeMode);
     void moveRootTaskToDisplay(int taskId, int displayId);
 
     void moveTaskToRootTask(int taskId, int rootTaskId, boolean toTop);
@@ -249,11 +248,6 @@
     /** Returns an interface enabling the management of window organizers. */
     IWindowOrganizerController getWindowOrganizerController();
 
-    /**
-     * Sets whether we are currently in an interactive split screen resize operation where we
-     * are changing the docked stack size.
-     */
-    void setSplitScreenResizing(boolean resizing);
     boolean supportsLocalVoiceInteraction();
 
     // Get device configuration
diff --git a/core/java/android/app/IGrammaticalInflectionManager.aidl b/core/java/android/app/IGrammaticalInflectionManager.aidl
new file mode 100644
index 0000000..9366a45
--- /dev/null
+++ b/core/java/android/app/IGrammaticalInflectionManager.aidl
@@ -0,0 +1,19 @@
+package android.app;
+
+
+/**
+ * Internal interface used to control app-specific gender.
+ *
+ * <p>Use the {@link android.app.GrammarInflectionManager} class rather than going through
+ * this Binder interface directly. See {@link android.app.GrammarInflectionManager} for
+ * more complete documentation.
+ *
+ * @hide
+ */
+ interface IGrammaticalInflectionManager {
+
+     /**
+      * Sets a specified app’s app-specific grammatical gender.
+      */
+     void setRequestedApplicationGrammaticalGender(String appPackageName, int userId, int gender);
+ }
\ No newline at end of file
diff --git a/core/java/android/app/IUnsafeIntentStrictModeCallback.aidl b/core/java/android/app/IUnsafeIntentStrictModeCallback.aidl
new file mode 100644
index 0000000..e2b3bb1
--- /dev/null
+++ b/core/java/android/app/IUnsafeIntentStrictModeCallback.aidl
@@ -0,0 +1,28 @@
+/*
+* 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.app;
+
+import android.content.Intent;
+
+/**
+ * Callback to find out when a strict mode violation occurs.
+ * {@hide}
+ */
+oneway interface IUnsafeIntentStrictModeCallback
+{
+    void onImplicitIntentMatchedInternalComponent(in Intent intent);
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index df13a87..5b3b2a6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -1547,6 +1547,7 @@
                                 IAmbientContextManager.Stub.asInterface(iBinder);
                         return new AmbientContextManager(ctx.getOuterContext(), manager);
                     }});
+
         registerService(Context.WEARABLE_SENSING_SERVICE, WearableSensingManager.class,
                 new CachedServiceFetcher<WearableSensingManager>() {
                     @Override
@@ -1559,6 +1560,18 @@
                         return new WearableSensingManager(ctx.getOuterContext(), manager);
                     }});
 
+        registerService(Context.GRAMMATICAL_INFLECTION_SERVICE, GrammaticalInflectionManager.class,
+                new CachedServiceFetcher<GrammaticalInflectionManager>() {
+                    @Override
+                    public GrammaticalInflectionManager createService(ContextImpl ctx)
+                            throws ServiceNotFoundException {
+                        return new GrammaticalInflectionManager(ctx,
+                                IGrammaticalInflectionManager.Stub.asInterface(
+                                        ServiceManager.getServiceOrThrow(
+                                                Context.GRAMMATICAL_INFLECTION_SERVICE)));
+                    }});
+
+
         sInitializing = true;
         try {
             // Note: the following functions need to be @SystemApis, once they become mainline
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 84b404d..1187459 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -115,6 +115,9 @@
     private static final @NonNull RectF LOCAL_COLOR_BOUNDS =
             new RectF(0, 0, 1, 1);
 
+    /** Temporary feature flag for project b/197814683 */
+    private final boolean mLockscreenLiveWallpaper;
+
     /** {@hide} */
     private static final String PROP_WALLPAPER = "ro.config.wallpaper";
     /** {@hide} */
@@ -750,6 +753,8 @@
         mWcgEnabled = context.getResources().getConfiguration().isScreenWideColorGamut()
                 && context.getResources().getBoolean(R.bool.config_enableWcgMode);
         mCmProxy = new ColorManagementProxy(context);
+        mLockscreenLiveWallpaper = context.getResources()
+                .getBoolean(R.bool.config_independentLockscreenLiveWallpaper);
     }
 
     // no-op constructor called just by DisabledWallpaperManager
@@ -757,6 +762,7 @@
         mContext = null;
         mCmProxy = null;
         mWcgEnabled = false;
+        mLockscreenLiveWallpaper = false;
     }
 
     /**
@@ -774,6 +780,15 @@
     }
 
     /**
+     * Temporary method for project b/197814683.
+     * @return true if the lockscreen wallpaper always uses a wallpaperService, not a static image
+     * @hide
+     */
+    public boolean isLockscreenLiveWallpaperEnabled() {
+        return mLockscreenLiveWallpaper;
+    }
+
+    /**
      * Indicate whether wcg (Wide Color Gamut) should be enabled.
      * <p>
      * Some devices lack of capability of mixed color spaces composition,
@@ -1109,7 +1124,7 @@
     }
 
     /**
-     * Like {@link #getFastDrawable(int)}, but the returned Drawable has a number
+     * Like {@link #getDrawable(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
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8ebd329..e729e7d 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9878,11 +9878,20 @@
      * <p>
      * The calling device admin must be a profile owner. If it is not, a security exception will be
      * thrown.
+     * <p>
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, calling this function
+     * is similar to calling {@link #setManagedProfileCallerIdAccessPolicy(PackagePolicy)}
+     * with a {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST} policy type when {@code disabled} is
+     * false or a {@link PackagePolicy#PACKAGE_POLICY_ALLOWLIST} policy type when
+     * {@code disabled} is true.
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled If true caller-Id information in the managed profile is not displayed.
      * @throws SecurityException if {@code admin} is not a profile owner.
+     * @deprecated starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, use
+     * {@link #setManagedProfileCallerIdAccessPolicy(PackagePolicy)} instead
      */
+    @Deprecated
     public void setCrossProfileCallerIdDisabled(@NonNull ComponentName admin, boolean disabled) {
         throwIfParentInstance("setCrossProfileCallerIdDisabled");
         if (mService != null) {
@@ -9900,10 +9909,19 @@
      * <p>
      * The calling device admin must be a profile owner. If it is not, a security exception will be
      * thrown.
+     * <p>
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * this will return true when
+     * {@link #setManagedProfileCallerIdAccessPolicy(PackagePolicy)}
+     * has been set with a non-null policy whose policy type is NOT
+     * {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST}
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @throws SecurityException if {@code admin} is not a profile owner.
+     * @deprecated starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, use
+     * {@link #getManagedProfileCallerIdAccessPolicy()} instead
      */
+    @Deprecated
     public boolean getCrossProfileCallerIdDisabled(@NonNull ComponentName admin) {
         throwIfParentInstance("getCrossProfileCallerIdDisabled");
         if (mService != null) {
@@ -9917,11 +9935,20 @@
     }
 
     /**
-     * Determine whether or not caller-Id information has been disabled.
+     * Called by the system to determine whether or not caller-Id information has been disabled.
+     * <p>
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * this will return true when
+     * {@link #setManagedProfileCallerIdAccessPolicy(PackagePolicy)}
+     * has been set with a non-null policy whose policy type is NOT
+     * {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST}
      *
      * @param userHandle The user for whom to check the caller-id permission
+     * @deprecated use {@link #hasManagedProfileCallerIdAccess(UserHandle, String)} and provide the
+     * package name requesting access
      * @hide
      */
+    @Deprecated
     public boolean getCrossProfileCallerIdDisabled(UserHandle userHandle) {
         if (mService != null) {
             try {
@@ -9934,16 +9961,174 @@
     }
 
     /**
+     * Called by a profile owner of a managed profile to set the packages that are allowed to
+     * lookup contacts in the managed profile based on caller id information.
+     * <p>
+     * For example, the policy determines if a dialer app in the parent profile resolving
+     * an incoming call can search the caller id data, such as phone number,
+     * of managed contacts and return managed contacts that match.
+     * <p>
+     * The calling device admin must be a profile owner of a managed profile.
+     * If it is not, a {@link SecurityException} will be thrown.
+     * <p>
+     * A {@link PackagePolicy#PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM} policy type
+     * allows access from the OEM default packages for the Sms, Dialer and Contact roles,
+     * in addition to the packages specified in {@link PackagePolicy#getPackageNames()}
+     *
+     * @param policy the policy to set, setting this value to {@code null} will allow
+     *               all packages
+     * @throws SecurityException if caller is not a profile owner of a managed profile
+     */
+    public void setManagedProfileCallerIdAccessPolicy(@Nullable PackagePolicy policy) {
+        throwIfParentInstance("setManagedProfileCallerIdAccessPolicy");
+        if (mService != null) {
+            try {
+                mService.setManagedProfileCallerIdAccessPolicy(policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by a profile owner of a managed profile to retrieve the caller id policy.
+     * <p>
+     * The calling device admin must be a profile owner of a managed profile.
+     * If it is not, a {@link SecurityException} will be thrown.
+     *
+     * @throws SecurityException if caller is not a profile owner of a managed profile.
+     * @return the current caller id policy
+     */
+    public @Nullable PackagePolicy getManagedProfileCallerIdAccessPolicy() {
+        throwIfParentInstance("getManagedProfileCallerIdAccessPolicy");
+        if (mService != null) {
+            try {
+                return mService.getManagedProfileCallerIdAccessPolicy();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Determine whether the given package is allowed to query the requested user to
+     * populate caller id information
+     *
+     * @param userHandle The user for whom to check the contacts search permission
+     * @param packageName the name of the package requesting access
+     * @return true if package should be granted access, false otherwise
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean hasManagedProfileCallerIdAccess(@NonNull UserHandle userHandle,
+            @NonNull String packageName) {
+        if (mService == null) {
+            return true;
+        }
+        try {
+            return mService.hasManagedProfileCallerIdAccess(userHandle.getIdentifier(),
+                    packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Called by a profile owner of a managed profile to set the packages that are allowed
+     * access to the managed profile contacts from the parent user.
+     * <p>
+     * For example, the system will enforce the provided policy and determine
+     * if contacts in the managed profile are shown when queried by an application
+     * in the parent user.
+     * <p>
+     * The calling device admin must be a profile owner of a managed profile.
+     * If it is not, a {@link SecurityException} will be thrown.
+     * <p>
+     * A {@link PackagePolicy#PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM} policy type
+     * allows access from the OEM default packages for the Sms, Dialer and Contact roles,
+     * in addition to the packages specified in {@link PackagePolicy#getPackageNames()}
+     *
+     * @param policy the policy to set, setting this value to {@code null} will allow
+     *               all packages
+     * @throws SecurityException if caller is not a profile owner of a managed profile
+     */
+    public void setManagedProfileContactsAccessPolicy(@Nullable PackagePolicy policy) {
+        throwIfParentInstance("setManagedProfileContactsAccessPolicy");
+        if (mService != null) {
+            try {
+                mService.setManagedProfileContactsAccessPolicy(policy);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Called by a profile owner of a managed profile to determine the current policy applied
+     * to managed profile contacts.
+     * <p>
+     * The calling device admin must be a profile owner of a managed profile.
+     * If it is not, a {@link SecurityException} will be thrown.
+     *
+     * @throws SecurityException if caller is not a profile owner of a managed profile.
+     * @return the current contacts search policy
+     */
+    public @Nullable PackagePolicy getManagedProfileContactsAccessPolicy() {
+        throwIfParentInstance("getManagedProfileContactsAccessPolicy");
+        if (mService == null) {
+            return null;
+        }
+        try {
+            return mService.getManagedProfileContactsAccessPolicy();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Determine whether requesting package has ability to access contacts of the requested user
+     *
+     * @param userHandle The user for whom to check the contacts search permission
+     * @param packageName packageName requesting access to contact search
+     * @return true when package is allowed access, false otherwise
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public boolean hasManagedProfileContactsAccess(@NonNull UserHandle userHandle,
+            @NonNull String packageName) {
+        if (mService != null) {
+            try {
+                return mService.hasManagedProfileContactsAccess(userHandle.getIdentifier(),
+                        packageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return false;
+    }
+
+    /**
      * Called by a profile owner of a managed profile to set whether contacts search from the
      * managed profile will be shown in the parent profile, for incoming calls.
      * <p>
      * The calling device admin must be a profile owner. If it is not, a security exception will be
      * thrown.
      *
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, calling this function
+     * is similar to calling {@link #setManagedProfileContactsAccessPolicy(PackagePolicy)} with a
+     * {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST} policy type when {@code disabled} is false
+     * or a {@link PackagePolicy#PACKAGE_POLICY_ALLOWLIST} policy type when {@code disabled}
+     * is true.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param disabled If true contacts search in the managed profile is not displayed.
      * @throws SecurityException if {@code admin} is not a profile owner.
+     *
+     * @deprecated From {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} use
+     * {@link #setManagedProfileContactsAccessPolicy(PackagePolicy)}
      */
+    @Deprecated
     public void setCrossProfileContactsSearchDisabled(@NonNull ComponentName admin,
             boolean disabled) {
         throwIfParentInstance("setCrossProfileContactsSearchDisabled");
@@ -9962,10 +10147,19 @@
      * <p>
      * The calling device admin must be a profile owner. If it is not, a security exception will be
      * thrown.
+     * <p>
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * this will return true when
+     * {@link #setManagedProfileContactsAccessPolicy(PackagePolicy)}
+     * has been set with a non-null policy whose policy type is NOT
+     * {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST}
      *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @throws SecurityException if {@code admin} is not a profile owner.
+     * @deprecated From {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} use
+     * {@link #getManagedProfileContactsAccessPolicy()}
      */
+    @Deprecated
     public boolean getCrossProfileContactsSearchDisabled(@NonNull ComponentName admin) {
         throwIfParentInstance("getCrossProfileContactsSearchDisabled");
         if (mService != null) {
@@ -9978,13 +10172,20 @@
         return false;
     }
 
-
     /**
      * Determine whether or not contacts search has been disabled.
-     *
+     * <p>
+     * Starting with {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+     * this will return true when
+     * {@link #setManagedProfileContactsAccessPolicy(PackagePolicy)}
+     * has been set with a non-null policy whose policy type is NOT
+     * {@link PackagePolicy#PACKAGE_POLICY_BLOCKLIST}
      * @param userHandle The user for whom to check the contacts search permission
+     * @deprecated use {@link #hasManagedProfileContactsAccess(UserHandle, String)} and provide the
+     * package name requesting access
      * @hide
      */
+    @Deprecated
     public boolean getCrossProfileContactsSearchDisabled(@NonNull UserHandle userHandle) {
         if (mService != null) {
             try {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 5383dca..26d7667 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -29,6 +29,7 @@
 import android.app.admin.StartInstallingUpdateCallback;
 import android.app.admin.SystemUpdateInfo;
 import android.app.admin.SystemUpdatePolicy;
+import android.app.admin.PackagePolicy;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.FactoryResetProtectionPolicy;
 import android.app.admin.ManagedProfileProvisioningParams;
@@ -330,6 +331,14 @@
     boolean getCrossProfileContactsSearchDisabledForUser(int userId);
     void startManagedQuickContact(String lookupKey, long contactId, boolean isContactIdIgnored, long directoryId, in Intent originalIntent);
 
+    void setManagedProfileCallerIdAccessPolicy(in PackagePolicy policy);
+    PackagePolicy getManagedProfileCallerIdAccessPolicy();
+    boolean hasManagedProfileCallerIdAccess(int userId, String packageName);
+
+    void setManagedProfileContactsAccessPolicy(in PackagePolicy policy);
+    PackagePolicy getManagedProfileContactsAccessPolicy();
+    boolean hasManagedProfileContactsAccess(int userId, String packageName);
+
     void setBluetoothContactSharingDisabled(in ComponentName who, boolean disabled);
     boolean getBluetoothContactSharingDisabled(in ComponentName who);
     boolean getBluetoothContactSharingDisabledForUser(int userId);
diff --git a/core/java/android/app/admin/PackagePolicy.aidl b/core/java/android/app/admin/PackagePolicy.aidl
new file mode 100644
index 0000000..33a67de
--- /dev/null
+++ b/core/java/android/app/admin/PackagePolicy.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+parcelable PackagePolicy;
\ No newline at end of file
diff --git a/core/java/android/app/admin/PackagePolicy.java b/core/java/android/app/admin/PackagePolicy.java
new file mode 100644
index 0000000..0d457ba
--- /dev/null
+++ b/core/java/android/app/admin/PackagePolicy.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.admin;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArraySet;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.Set;
+
+
+/**
+ * A generic class that defines which APK packages are in scope for some device policy.
+ * <p>
+ * The packages can be defined using either an allowlist or a blocklist.
+ * In allowlist mode, it could optionally include all system packages
+ * that meet the specific criteria of the device policy in question.
+ */
+public final class PackagePolicy implements Parcelable {
+
+    /**
+     * PackagePolicy type indicator for {@link PackagePolicy}
+     * <p>
+     * This constant indicates that all packages are allowed except for the packages returned by
+     * {@link PackagePolicy#getPackageNames()}, which acts as a denylist.
+     * @see #PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
+     * @see #PACKAGE_POLICY_ALLOWLIST
+     */
+    public static final int PACKAGE_POLICY_BLOCKLIST = 1;
+
+    /**
+     * PackagePolicy type indicator for {@link PackagePolicy}
+     * <p>
+     * This constant indicates system packages are allowed in addition to the packages returned by
+     * {@link PackagePolicy#getPackageNames()}, which acts as an allowlist.
+     *
+     * <p>Functions that accept {@link PackagePolicy} will further clarify
+     * how this policy is interpreted.
+     *
+     * @see #PACKAGE_POLICY_BLOCKLIST
+     * @see #PACKAGE_POLICY_ALLOWLIST
+     */
+    public static final int PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM = 2;
+
+    /**
+     * PackagePolicy type indicator for {@link PackagePolicy}
+     * <p>
+     * This constant indicates that all packages are denied except for the packages returned by
+     * {@link PackagePolicy#getPackageNames()}, which acts as an allowlist.
+     *
+     * @see #PACKAGE_POLICY_BLOCKLIST
+     * @see #PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
+     */
+    public static final int PACKAGE_POLICY_ALLOWLIST = 3;
+
+    /**
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"PACKAGE_POLICY_"}, value = {
+            PACKAGE_POLICY_BLOCKLIST,
+            PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM,
+            PACKAGE_POLICY_ALLOWLIST
+    })
+    public @interface PackagePolicyType {}
+
+    private @PackagePolicyType int mPolicyType;
+
+    private ArraySet<String> mPackageNames;
+
+    /**
+     * Create the package policy
+     * @param policyType indicates how to interpret this policy
+
+     * @see PackagePolicy#PackagePolicy(int, Set)
+     */
+    public PackagePolicy(@PackagePolicyType int policyType) {
+        this(policyType, Collections.emptySet());
+    }
+
+    /**
+     * Create the package policy
+     * @param policyType indicates how to interpret this policy
+     * @param packageNames allowlist or a denylist, based on policyType
+     */
+    public PackagePolicy(@PackagePolicyType int policyType, @NonNull Set<String> packageNames) {
+        if (policyType != PACKAGE_POLICY_BLOCKLIST
+                && policyType != PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
+                && policyType != PACKAGE_POLICY_ALLOWLIST) {
+            throw new IllegalArgumentException("Invalid policy type");
+        }
+        mPolicyType = policyType;
+        mPackageNames = new ArraySet<>(packageNames);
+    }
+
+    private PackagePolicy(Parcel in) {
+        mPolicyType = in.readInt();
+        mPackageNames = (ArraySet<String>) in.readArraySet(null);
+    }
+
+    /**
+     * Returns the current policy type
+     */
+    public @PackagePolicyType int getPolicyType() {
+        return mPolicyType;
+    }
+
+    /**
+     * Returns the list of packages to use as an allow/deny list based on policy type
+     */
+    @NonNull
+    public Set<String> getPackageNames() {
+        return Collections.unmodifiableSet(mPackageNames);
+    }
+
+    /**
+     * Evaluates the packageName provided against this policy to determine if the package should be
+     * allowed.
+     *
+     * If the policy type is {@link #PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM},
+     * the systemPackage will be used in addition to package names of this policy's
+     * {@link #getPackageNames()}
+     *
+     * @param packageName  the name of the package to test
+     * @param systemPackages list of packages identified as system packages
+     * @return true if the package is allowed, false if the package is denied
+     * @hide
+     */
+    public boolean isPackageAllowed(@NonNull String packageName,
+            @NonNull Set<String> systemPackages) {
+        if (mPolicyType == PACKAGE_POLICY_BLOCKLIST) {
+            return !mPackageNames.contains(packageName);
+        }
+        return mPackageNames.contains(packageName)
+                || (mPolicyType == PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM
+                      && systemPackages.contains(packageName));
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    @NonNull
+    public static final Creator<PackagePolicy> CREATOR = new Creator<PackagePolicy>() {
+        @Override
+        public PackagePolicy createFromParcel(Parcel in) {
+            return new PackagePolicy(in);
+        }
+
+        @Override
+        public PackagePolicy[] newArray(int size) {
+            return new PackagePolicy[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mPolicyType);
+        dest.writeArraySet(mPackageNames);
+    }
+
+    @Override
+    public boolean equals(Object thatObject) {
+        if (this == thatObject) {
+            return true;
+        }
+        if (!(thatObject instanceof PackagePolicy)) {
+            return false;
+        }
+        PackagePolicy that = (PackagePolicy) thatObject;
+        return mPolicyType == that.mPolicyType && mPackageNames.equals(that.mPackageNames);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPolicyType, mPackageNames);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+}
diff --git a/core/java/android/app/ambientcontext/AmbientContextEvent.java b/core/java/android/app/ambientcontext/AmbientContextEvent.java
index 865e1fb..a6595fe 100644
--- a/core/java/android/app/ambientcontext/AmbientContextEvent.java
+++ b/core/java/android/app/ambientcontext/AmbientContextEvent.java
@@ -20,6 +20,7 @@
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
@@ -66,12 +67,25 @@
      */
     public static final int EVENT_BACK_DOUBLE_TAP = 3;
 
+    /**
+     * Integer indicating the start of wearable vendor defined events that can be detected.
+     * These depend on the vendor implementation.
+     */
+    public static final int EVENT_VENDOR_WEARABLE_START = 100000;
+
+    /**
+     * Name for the mVendorData object for this AmbientContextEvent. The mVendorData must be present
+     * in the object, or it will be rejected.
+     */
+    public static final String KEY_VENDOR_WEARABLE_EVENT_NAME = "wearable_event_name";
+
     /** @hide */
     @IntDef(prefix = { "EVENT_" }, value = {
             EVENT_UNKNOWN,
             EVENT_COUGH,
             EVENT_SNORE,
             EVENT_BACK_DOUBLE_TAP,
+            EVENT_VENDOR_WEARABLE_START,
     }) public @interface EventCode {}
 
     /** The integer indicating an unknown level. */
@@ -139,6 +153,19 @@
         return LEVEL_UNKNOWN;
     }
 
+    /**
+     * Vendor defined specific values for vendor event types.
+     *
+     * <p> The use of this vendor data is discouraged. For data defined in the range above
+     * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the
+     * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase
+     * transparency of this data contents of this bundle will be logged to logcat.</p>
+     */
+    private final @NonNull PersistableBundle mVendorData;
+    private static PersistableBundle defaultVendorData() {
+        return new PersistableBundle();
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -159,7 +186,8 @@
         EVENT_UNKNOWN,
         EVENT_COUGH,
         EVENT_SNORE,
-        EVENT_BACK_DOUBLE_TAP
+        EVENT_BACK_DOUBLE_TAP,
+        EVENT_VENDOR_WEARABLE_START
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -177,6 +205,8 @@
                     return "EVENT_SNORE";
             case EVENT_BACK_DOUBLE_TAP:
                     return "EVENT_BACK_DOUBLE_TAP";
+            case EVENT_VENDOR_WEARABLE_START:
+                    return "EVENT_VENDOR_WEARABLE_START";
             default: return Integer.toHexString(value);
         }
     }
@@ -220,7 +250,8 @@
             @NonNull Instant startTime,
             @NonNull Instant endTime,
             @LevelValue int confidenceLevel,
-            @LevelValue int densityLevel) {
+            @LevelValue int densityLevel,
+            @NonNull PersistableBundle vendorData) {
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
                 EventCode.class, null, mEventType);
@@ -236,6 +267,9 @@
         this.mDensityLevel = densityLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 LevelValue.class, null, mDensityLevel);
+        this.mVendorData = vendorData;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mVendorData);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -279,6 +313,19 @@
         return mDensityLevel;
     }
 
+    /**
+     * Vendor defined specific values for vendor event types.
+     *
+     * <p> The use of this vendor data is discouraged. For data defined in the range above
+     * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the
+     * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase
+     * transparency of this data contents of this bundle will be logged to logcat.</p>
+     */
+    @DataClass.Generated.Member
+    public @NonNull PersistableBundle getVendorData() {
+        return mVendorData;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -290,7 +337,8 @@
                 "startTime = " + mStartTime + ", " +
                 "endTime = " + mEndTime + ", " +
                 "confidenceLevel = " + mConfidenceLevel + ", " +
-                "densityLevel = " + mDensityLevel +
+                "densityLevel = " + mDensityLevel + ", " +
+                "vendorData = " + mVendorData +
         " }";
     }
 
@@ -327,6 +375,7 @@
         sParcellingForEndTime.parcel(mEndTime, dest, flags);
         dest.writeInt(mConfidenceLevel);
         dest.writeInt(mDensityLevel);
+        dest.writeTypedObject(mVendorData, flags);
     }
 
     @Override
@@ -345,6 +394,7 @@
         Instant endTime = sParcellingForEndTime.unparcel(in);
         int confidenceLevel = in.readInt();
         int densityLevel = in.readInt();
+        PersistableBundle vendorData = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR);
 
         this.mEventType = eventType;
         com.android.internal.util.AnnotationValidations.validate(
@@ -361,6 +411,9 @@
         this.mDensityLevel = densityLevel;
         com.android.internal.util.AnnotationValidations.validate(
                 LevelValue.class, null, mDensityLevel);
+        this.mVendorData = vendorData;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mVendorData);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -391,6 +444,7 @@
         private @NonNull Instant mEndTime;
         private @LevelValue int mConfidenceLevel;
         private @LevelValue int mDensityLevel;
+        private @NonNull PersistableBundle mVendorData;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -451,10 +505,26 @@
             return this;
         }
 
+        /**
+         * Vendor defined specific values for vendor event types.
+         *
+         * <p> The use of this vendor data is discouraged. For data defined in the range above
+         * {@code EVENT_VENDOR_WEARABLE_START} this bundle must include the
+         * {@link KEY_VENDOR_WEARABLE_EVENT_NAME} field or it will be rejected. In addition, to increase
+         * transparency of this data contents of this bundle will be logged to logcat.</p>
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setVendorData(@NonNull PersistableBundle value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mVendorData = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull AmbientContextEvent build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20; // Mark builder used
+            mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mEventType = defaultEventType();
@@ -471,17 +541,21 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mDensityLevel = defaultDensityLevel();
             }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mVendorData = defaultVendorData();
+            }
             AmbientContextEvent o = new AmbientContextEvent(
                     mEventType,
                     mStartTime,
                     mEndTime,
                     mConfidenceLevel,
-                    mDensityLevel);
+                    mDensityLevel,
+                    mVendorData);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x20) != 0) {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -489,10 +563,10 @@
     }
 
     @DataClass.Generated(
-            time = 1659950304931L,
+            time = 1671217108067L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/ambientcontext/AmbientContextEvent.java",
-            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int EVENT_UNKNOWN\npublic static final  int EVENT_COUGH\npublic static final  int EVENT_SNORE\npublic static final  int EVENT_BACK_DOUBLE_TAP\npublic static final  int EVENT_VENDOR_WEARABLE_START\npublic static final  java.lang.String KEY_VENDOR_WEARABLE_EVENT_NAME\npublic static final  int LEVEL_UNKNOWN\npublic static final  int LEVEL_LOW\npublic static final  int LEVEL_MEDIUM_LOW\npublic static final  int LEVEL_MEDIUM\npublic static final  int LEVEL_MEDIUM_HIGH\npublic static final  int LEVEL_HIGH\nprivate final @android.app.ambientcontext.AmbientContextEvent.EventCode int mEventType\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mStartTime\nprivate final @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInstant.class) @android.annotation.NonNull java.time.Instant mEndTime\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mConfidenceLevel\nprivate final @android.app.ambientcontext.AmbientContextEvent.LevelValue int mDensityLevel\nprivate final @android.annotation.NonNull android.os.PersistableBundle mVendorData\nprivate static  int defaultEventType()\nprivate static @android.annotation.NonNull java.time.Instant defaultStartTime()\nprivate static @android.annotation.NonNull java.time.Instant defaultEndTime()\nprivate static  int defaultConfidenceLevel()\nprivate static  int defaultDensityLevel()\nprivate static  android.os.PersistableBundle defaultVendorData()\nclass AmbientContextEvent extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genConstructor=false, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/ambientcontext/AmbientContextManager.java b/core/java/android/app/ambientcontext/AmbientContextManager.java
index 9cb1a20..bf383f1 100644
--- a/core/java/android/app/ambientcontext/AmbientContextManager.java
+++ b/core/java/android/app/ambientcontext/AmbientContextManager.java
@@ -117,7 +117,8 @@
      */
     @NonNull public static List<AmbientContextEvent> getEventsFromIntent(@NonNull Intent intent) {
         if (intent.hasExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS)) {
-            return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS, android.app.ambientcontext.AmbientContextEvent.class);
+            return intent.getParcelableArrayListExtra(EXTRA_AMBIENT_CONTEXT_EVENTS,
+                    android.app.ambientcontext.AmbientContextEvent.class);
         } else {
             return new ArrayList<>();
         }
@@ -143,6 +144,9 @@
      * If any of the events are not consented by user, the response has
      * {@link AmbientContextManager#STATUS_ACCESS_DENIED}, and the app can
      * call {@link #startConsentActivity} to redirect the user to the consent screen.
+     * If the AmbientContextRequest contains a mixed set of events containing values both greater
+     * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request
+     * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}.
      * <p />
      *
      * Example:
@@ -197,6 +201,9 @@
 
     /**
      * Requests the consent data host to open an activity that allows users to modify consent.
+     * If the eventTypes contains a mixed set of events containing values both greater than and less
+     * than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request will be rejected
+     * with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}.
      *
      * @param eventTypes The set of event codes to be consented.
      */
@@ -226,6 +233,9 @@
      * observer receives a callback on the provided {@link PendingIntent} when the requested
      * event is detected. Registering another observer from the same package that has already been
      * registered will override the previous observer.
+     * If the AmbientContextRequest contains a mixed set of events containing values both greater
+     * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request
+     * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}.
      * <p />
      *
      * Example:
@@ -308,6 +318,9 @@
      * {@link #registerObserver(AmbientContextEventRequest, PendingIntent, Executor, Consumer)},
      * the previous observer will be replaced with the new observer with the PendingIntent callback.
      * Or vice versa.
+     * If the AmbientContextRequest contains a mixed set of events containing values both greater
+     * than and less than {@link AmbientContextEvent.EVENT_VENDOR_WEARABLE_START}, the request
+     * will be rejected with {@link AmbientContextManager#STATUS_NOT_SUPPORTED}.
      *
      * When the registration completes, a status will be returned to client through
      * {@link AmbientContextCallback#onRegistrationComplete(int)}.
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 7255c3e..bad282e 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
+import android.app.backup.BackupAnnotations.OperationType;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
@@ -1041,6 +1042,42 @@
         return backupAgent.getBackupRestoreEventLogger();
     }
 
+    /**
+     * Get an instance of {@link BackupRestoreEventLogger} to report B&R related events during a
+     * delayed restore operation.
+     *
+     * @return an instance of {@link BackupRestoreEventLogger}.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public BackupRestoreEventLogger getDelayedRestoreLogger() {
+        return new BackupRestoreEventLogger(OperationType.RESTORE);
+    }
+
+    /**
+     * Report B&R related events following a delayed restore operation.
+     *
+     * @param logger an instance of {@link BackupRestoreEventLogger} to which the corresponding
+     *               events have been logged.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public void reportDelayedRestoreResult(@NonNull BackupRestoreEventLogger logger) {
+        checkServiceBinder();
+        if (sService != null) {
+            try {
+                sService.reportDelayedRestoreResult(mContext.getPackageName(),
+                        logger.getLoggingResults());
+            } catch (RemoteException e) {
+                Log.w(TAG, "reportDelayedRestoreResult() couldn't connect");
+            }
+        }
+    }
+
     /*
      * We wrap incoming binder calls with a private class implementation that
      * redirects them into main-thread actions.  This serializes the backup
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index bf5be95..aeb4987 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,6 +16,7 @@
 
 package android.app.backup;
 
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupObserver;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IFullBackupRestoreObserver;
@@ -722,4 +723,6 @@
      * that have been excluded will be passed to the agent to make it aware of the exclusions.
      */
     void excludeKeysFromRestore(String packageName, in List<String> keys);
+
+    void reportDelayedRestoreResult(in String packageName, in List<DataTypeResult> results);
 }
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index d94f08b..b159f33 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -38,6 +38,9 @@
         return UNDEFINED;
     }
 
+    boolean shouldHaveDefinedPreExecutionState() {
+        return true;
+    }
 
     // Parcelable
 
diff --git a/core/java/android/app/servertransaction/ConfigurationChangeItem.java b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
index 49a1c66..a563bbc 100644
--- a/core/java/android/app/servertransaction/ConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ConfigurationChangeItem.java
@@ -32,6 +32,7 @@
 public class ConfigurationChangeItem extends ClientTransactionItem {
 
     private Configuration mConfiguration;
+    private int mDeviceId;
 
     @Override
     public void preExecute(android.app.ClientTransactionHandler client, IBinder token) {
@@ -42,7 +43,7 @@
     @Override
     public void execute(ClientTransactionHandler client, IBinder token,
             PendingTransactionActions pendingActions) {
-        client.handleConfigurationChanged(mConfiguration);
+        client.handleConfigurationChanged(mConfiguration, mDeviceId);
     }
 
 
@@ -51,12 +52,13 @@
     private ConfigurationChangeItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ConfigurationChangeItem obtain(Configuration config) {
+    public static ConfigurationChangeItem obtain(Configuration config, int deviceId) {
         ConfigurationChangeItem instance = ObjectPool.obtain(ConfigurationChangeItem.class);
         if (instance == null) {
             instance = new ConfigurationChangeItem();
         }
         instance.mConfiguration = config;
+        instance.mDeviceId = deviceId;
 
         return instance;
     }
@@ -64,6 +66,7 @@
     @Override
     public void recycle() {
         mConfiguration = null;
+        mDeviceId = 0;
         ObjectPool.recycle(this);
     }
 
@@ -74,11 +77,13 @@
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedObject(mConfiguration, flags);
+        dest.writeInt(mDeviceId);
     }
 
     /** Read from Parcel. */
     private ConfigurationChangeItem(Parcel in) {
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
+        mDeviceId = in.readInt();
     }
 
     public static final @android.annotation.NonNull Creator<ConfigurationChangeItem> CREATOR =
@@ -101,16 +106,20 @@
             return false;
         }
         final ConfigurationChangeItem other = (ConfigurationChangeItem) o;
-        return Objects.equals(mConfiguration, other.mConfiguration);
+        return Objects.equals(mConfiguration, other.mConfiguration)
+                && mDeviceId == other.mDeviceId;
     }
 
     @Override
     public int hashCode() {
-        return mConfiguration.hashCode();
+        int result = 17;
+        result = 31 * result + mDeviceId;
+        result = 31 * result + mConfiguration.hashCode();
+        return result;
     }
 
     @Override
     public String toString() {
-        return "ConfigurationChangeItem{config=" + mConfiguration + "}";
+        return "ConfigurationChangeItem{deviceId=" + mDeviceId + ", config" + mConfiguration + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 7e4db81..3d0aa25 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -58,6 +58,7 @@
     private ActivityInfo mInfo;
     private Configuration mCurConfig;
     private Configuration mOverrideConfig;
+    private int mDeviceId;
     private String mReferrer;
     private IVoiceInteractor mVoiceInteractor;
     private int mProcState;
@@ -95,7 +96,7 @@
             PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
         ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
-                mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
+                mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
                 client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
                 mTaskFragmentToken);
@@ -116,7 +117,7 @@
 
     /** Obtain an instance initialized with provided params. */
     public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
-            Configuration curConfig, Configuration overrideConfig,
+            Configuration curConfig, Configuration overrideConfig, int deviceId,
             String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
             PersistableBundle persistentState, List<ResultInfo> pendingResults,
             List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
@@ -127,7 +128,7 @@
         if (instance == null) {
             instance = new LaunchActivityItem();
         }
-        setValues(instance, intent, ident, info, curConfig, overrideConfig, referrer,
+        setValues(instance, intent, ident, info, curConfig, overrideConfig, deviceId, referrer,
                 voiceInteractor, procState, state, persistentState, pendingResults,
                 pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
                 activityClientController, shareableActivityToken,
@@ -138,7 +139,7 @@
 
     @Override
     public void recycle() {
-        setValues(this, null, 0, null, null, null, null, null, 0, null, null, null, null,
+        setValues(this, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
                 null, false, null, null, null, null, false, null);
         ObjectPool.recycle(this);
     }
@@ -154,6 +155,7 @@
         dest.writeTypedObject(mInfo, flags);
         dest.writeTypedObject(mCurConfig, flags);
         dest.writeTypedObject(mOverrideConfig, flags);
+        dest.writeInt(mDeviceId);
         dest.writeString(mReferrer);
         dest.writeStrongInterface(mVoiceInteractor);
         dest.writeInt(mProcState);
@@ -175,7 +177,7 @@
     private LaunchActivityItem(Parcel in) {
         setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
                 in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
-                in.readTypedObject(Configuration.CREATOR), in.readString(),
+                in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
                 IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
                 in.readBundle(getClass().getClassLoader()),
                 in.readPersistableBundle(getClass().getClassLoader()),
@@ -215,6 +217,7 @@
         return intentsEqual && mIdent == other.mIdent
                 && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
                 && Objects.equals(mOverrideConfig, other.mOverrideConfig)
+                && mDeviceId == other.mDeviceId
                 && Objects.equals(mReferrer, other.mReferrer)
                 && mProcState == other.mProcState && areBundlesEqualRoughly(mState, other.mState)
                 && areBundlesEqualRoughly(mPersistentState, other.mPersistentState)
@@ -235,6 +238,7 @@
         result = 31 * result + mIdent;
         result = 31 * result + Objects.hashCode(mCurConfig);
         result = 31 * result + Objects.hashCode(mOverrideConfig);
+        result = 31 * result + mDeviceId;
         result = 31 * result + Objects.hashCode(mReferrer);
         result = 31 * result + Objects.hashCode(mProcState);
         result = 31 * result + getRoughBundleHashCode(mState);
@@ -279,16 +283,17 @@
     public String toString() {
         return "LaunchActivityItem{intent=" + mIntent + ",ident=" + mIdent + ",info=" + mInfo
                 + ",curConfig=" + mCurConfig + ",overrideConfig=" + mOverrideConfig
-                + ",referrer=" + mReferrer + ",procState=" + mProcState + ",state=" + mState
-                + ",persistentState=" + mPersistentState + ",pendingResults=" + mPendingResults
-                + ",pendingNewIntents=" + mPendingNewIntents + ",options=" + mActivityOptions
-                + ",profilerInfo=" + mProfilerInfo + ",assistToken=" + mAssistToken
-                + ",shareableActivityToken=" + mShareableActivityToken + "}";
+                + ",deviceId=" + mDeviceId + ",referrer=" + mReferrer + ",procState=" + mProcState
+                + ",state=" + mState + ",persistentState=" + mPersistentState
+                + ",pendingResults=" + mPendingResults + ",pendingNewIntents=" + mPendingNewIntents
+                + ",options=" + mActivityOptions + ",profilerInfo=" + mProfilerInfo
+                + ",assistToken=" + mAssistToken + ",shareableActivityToken="
+                + mShareableActivityToken + "}";
     }
 
     // Using the same method to set and clear values to make sure we don't forget anything
     private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
-            ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
+            ActivityInfo info, Configuration curConfig, Configuration overrideConfig, int deviceId,
             String referrer, IVoiceInteractor voiceInteractor,
             int procState, Bundle state, PersistableBundle persistentState,
             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
@@ -300,6 +305,7 @@
         instance.mInfo = info;
         instance.mCurConfig = curConfig;
         instance.mOverrideConfig = overrideConfig;
+        instance.mDeviceId = deviceId;
         instance.mReferrer = referrer;
         instance.mVoiceInteractor = voiceInteractor;
         instance.mProcState = procState;
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
new file mode 100644
index 0000000..74abab2
--- /dev/null
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -0,0 +1,145 @@
+/*
+ * 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.servertransaction;
+
+import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread.ActivityClientRecord;
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+
+/**
+ * Callback that allows to {@link TransactionExecutor#cycleToPath} to {@link ON_PAUSE} or
+ * {@link ON_STOP} in {@link TransactionExecutor#executeCallbacks} for activity "refresh" flow
+ * that goes through "paused -> resumed" or "stopped -> resumed" cycle.
+ *
+ * <p>This is used in combination with {@link com.android.server.wm.DisplayRotationCompatPolicy}
+ * for camera compatibility treatment that handles orientation mismatch between camera buffers and
+ * an app window. This allows to clear cached values in apps (e.g. display or camera rotation) that
+ * influence camera preview and can lead to sideways or stretching issues.
+ *
+ * @hide
+ */
+public class RefreshCallbackItem extends ActivityTransactionItem {
+
+    // Whether refresh should happen using the "stopped -> resumed" cycle or
+    // "paused -> resumed" cycle.
+    @LifecycleState
+    private int mPostExecutionState;
+
+    @Override
+    public void execute(@NonNull ClientTransactionHandler client,
+            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions) {}
+
+    @Override
+    public void postExecute(ClientTransactionHandler client, IBinder token,
+            PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client, token);
+        client.reportRefresh(r);
+    }
+
+    @Override
+    public int getPostExecutionState() {
+        return mPostExecutionState;
+    }
+
+    @Override
+    boolean shouldHaveDefinedPreExecutionState() {
+        return false;
+    }
+
+    // ObjectPoolItem implementation
+
+    @Override
+    public void recycle() {
+        ObjectPool.recycle(this);
+    }
+
+    /**
+    * Obtain an instance initialized with provided params.
+    * @param postExecutionState indicating whether refresh should happen using the
+    *        "stopped -> resumed" cycle or "paused -> resumed" cycle.
+    */
+    public static RefreshCallbackItem obtain(@LifecycleState int postExecutionState) {
+        if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
+            throw new IllegalArgumentException(
+                    "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
+                            + "RefreshCallbackItem but got " + postExecutionState);
+        }
+        RefreshCallbackItem instance =
+                ObjectPool.obtain(RefreshCallbackItem.class);
+        if (instance == null) {
+            instance = new RefreshCallbackItem();
+        }
+        instance.mPostExecutionState = postExecutionState;
+        return instance;
+    }
+
+    private RefreshCallbackItem() {}
+
+    // Parcelable implementation
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mPostExecutionState);
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final RefreshCallbackItem other = (RefreshCallbackItem) o;
+        return mPostExecutionState == other.mPostExecutionState;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + mPostExecutionState;
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "RefreshCallbackItem{mPostExecutionState=" + mPostExecutionState + "}";
+    }
+
+    private RefreshCallbackItem(Parcel in) {
+        mPostExecutionState = in.readInt();
+    }
+
+    public static final @NonNull Creator<RefreshCallbackItem> CREATOR =
+            new Creator<RefreshCallbackItem>() {
+
+        public RefreshCallbackItem createFromParcel(Parcel in) {
+            return new RefreshCallbackItem(in);
+        }
+
+        public RefreshCallbackItem[] newArray(int size) {
+            return new RefreshCallbackItem[size];
+        }
+    };
+}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index de1d38a..1ff0b79 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -126,10 +126,13 @@
             final ClientTransactionItem item = callbacks.get(i);
             if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
             final int postExecutionState = item.getPostExecutionState();
-            final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
-                    item.getPostExecutionState());
-            if (closestPreExecutionState != UNDEFINED) {
-                cycleToPath(r, closestPreExecutionState, transaction);
+
+            if (item.shouldHaveDefinedPreExecutionState()) {
+                final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
+                        item.getPostExecutionState());
+                if (closestPreExecutionState != UNDEFINED) {
+                    cycleToPath(r, closestPreExecutionState, transaction);
+                }
             }
 
             item.execute(mTransactionHandler, token, mPendingActions);
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 22ea9f20..9ab7cf9 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -60,62 +60,73 @@
     /**
      * Closes the virtual device and frees all associated resources.
      */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void close();
 
     /**
      * Notifies of an audio session being started.
      */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void onAudioSessionStarting(
             int displayId,
             IAudioRoutingCallback routingCallback,
             IAudioConfigChangedCallback configChangedCallback);
 
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void onAudioSessionEnded();
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualDpad(
             in VirtualDpadConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualKeyboard(
             in VirtualKeyboardConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualMouse(
             in VirtualMouseConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualTouchscreen(
             in VirtualTouchscreenConfig config,
             IBinder token);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualNavigationTouchpad(
             in VirtualNavigationTouchpadConfig config,
             IBinder token);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterInputDevice(IBinder token);
     int getInputDeviceId(IBinder token);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendDpadKeyEvent(IBinder token, in VirtualKeyEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendKeyEvent(IBinder token, in VirtualKeyEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendButtonEvent(IBinder token, in VirtualMouseButtonEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendRelativeEvent(IBinder token, in VirtualMouseRelativeEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendScrollEvent(IBinder token, in VirtualMouseScrollEvent event);
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendTouchEvent(IBinder token, in VirtualTouchEvent event);
 
     /**
      * Creates a virtual sensor, capable of injecting sensor events into the system.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void createVirtualSensor(IBinder tokenm, in VirtualSensorConfig config);
 
     /**
      * Removes the sensor corresponding to the given token from the system.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterSensor(IBinder token);
 
     /**
      * Sends an event to the virtual sensor corresponding to the given token.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     boolean sendSensorEvent(IBinder token, in VirtualSensorEvent event);
 
     /**
@@ -126,6 +137,7 @@
     PointF getCursorPosition(IBinder token);
 
     /** Sets whether to show or hide the cursor while this virtual device is active. */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void setShowPointerIcon(boolean showPointerIcon);
 
     /**
@@ -133,9 +145,9 @@
      * when matching the provided IntentFilter and calls the callback with the intercepted
      * intent.
      */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void registerIntentInterceptor(
             in IVirtualDeviceIntentInterceptor intentInterceptor, in IntentFilter filter);
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)")
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
     void unregisterIntentInterceptor(in IVirtualDeviceIntentInterceptor intentInterceptor);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 088ac06..8561018 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -57,6 +57,7 @@
 import android.hardware.input.VirtualNavigationTouchpadConfig;
 import android.hardware.input.VirtualTouchscreen;
 import android.hardware.input.VirtualTouchscreenConfig;
+import android.media.AudioManager;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
@@ -303,6 +304,22 @@
     }
 
     /**
+     * Requests sound effect to be played on virtual device.
+     *
+     * @see android.media.AudioManager#playSoundEffect(int)
+     *
+     * @param deviceId - id of the virtual audio device
+     * @param effectType the type of sound effect
+     * @hide
+     */
+    public void playSoundEffect(int deviceId, @AudioManager.SystemSoundEffect int effectType) {
+        //TODO - handle requests to play sound effects by custom callbacks or SoundPool asociated
+        // with device session id.
+        // For now, this is intentionally left empty and effectively disables sound effects for
+        // virtual devices with custom device audio policy.
+    }
+
+    /**
      * 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 597b0f5..d4a0a08 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -758,7 +758,7 @@
          */
         @NonNull
         public Builder setAudioPlaybackSessionId(int playbackSessionId) {
-            if (playbackSessionId != AUDIO_SESSION_ID_GENERATE || playbackSessionId < 0) {
+            if (playbackSessionId < 0) {
                 throw new IllegalArgumentException("Invalid playback audio session id");
             }
             mAudioPlaybackSessionId = playbackSessionId;
@@ -782,7 +782,7 @@
          */
         @NonNull
         public Builder setAudioRecordingSessionId(int recordingSessionId) {
-            if (recordingSessionId != AUDIO_SESSION_ID_GENERATE || recordingSessionId < 0) {
+            if (recordingSessionId < 0) {
                 throw new IllegalArgumentException("Invalid recording audio session id");
             }
             mAudioRecordingSessionId = recordingSessionId;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index bb3bf5c..7d7232e 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -40,6 +40,7 @@
 import android.app.ActivityManager;
 import android.app.BroadcastOptions;
 import android.app.GameManager;
+import android.app.GrammaticalInflectionManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
@@ -109,6 +110,7 @@
 import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
+import java.util.function.IntConsumer;
 
 /**
  * Interface to global information about an application environment.  This is
@@ -276,7 +278,8 @@
             BIND_IMPORTANT,
             BIND_ADJUST_WITH_ACTIVITY,
             BIND_NOT_PERCEPTIBLE,
-            BIND_INCLUDE_CAPABILITIES
+            BIND_INCLUDE_CAPABILITIES,
+            BIND_SHARED_ISOLATED_PROCESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BindServiceFlags {}
@@ -393,6 +396,22 @@
      */
     public static final int BIND_INCLUDE_CAPABILITIES = 0x000001000;
 
+    /**
+     * Flag for {@link #bindIsolatedService}: Bind the service into a shared isolated process.
+     * Specifying this flag allows multiple isolated services to be running in a single shared
+     * isolated process.
+     *
+     * The shared isolated process instance is identified by the <var>instanceName</var>
+     * parameter in {@link #bindIsolatedService(Intent, int, String, Executor, ServiceConnection)}.
+     *
+     * Subsequent calls to {@link #bindIsolatedService} with the same <var>instanceName</var>
+     * will cause the isolated service to be co-located in the same shared isolated process.
+     *
+     * Note that the shared isolated process is scoped to the calling app; once created, only
+     * the calling app can bind additional isolated services into the shared process.
+     */
+    public static final int BIND_SHARED_ISOLATED_PROCESS = 0x00002000;
+
     /***********    Public flags above this line ***********/
     /***********    Hidden flags below this line ***********/
 
@@ -3955,6 +3974,8 @@
             CREDENTIAL_SERVICE,
             DEVICE_LOCK_SERVICE,
             VIRTUALIZATION_SERVICE,
+            GRAMMATICAL_INFLECTION_SERVICE,
+
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -5766,7 +5787,6 @@
      *
      * @see #getSystemService(String)
      * @see android.content.om.OverlayManager
-     * @hide
      */
     public static final String OVERLAY_SERVICE = "overlay";
 
@@ -6152,6 +6172,14 @@
     public static final String VIRTUALIZATION_SERVICE = "virtualization";
 
     /**
+     * Use with {@link #getSystemService(String)} to retrieve a
+     * {@link GrammaticalInflectionManager}.
+     *
+     * @see #getSystemService(String)
+     */
+    public static final String GRAMMATICAL_INFLECTION_SERVICE = "grammatical_inflection";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
@@ -6921,6 +6949,10 @@
      * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}. Similarly,
      * applications running on the default device may access the functionality of virtual devices.
      * </p>
+     * <p>
+     * Note that the newly created instance will be associated with the same display as the parent
+     * Context, regardless of the device ID passed here.
+     * </p>
      * @param deviceId The ID of the device to associate with this context.
      * @return A context associated with the given device ID.
      *
@@ -7256,20 +7288,118 @@
     public abstract void updateDisplay(int displayId);
 
     /**
-     * Get the device ID this context is associated with. Applications can use this method to
+     * Updates the device ID association of this Context. Since a Context created with
+     * {@link #createDeviceContext} cannot change its device association, this method must
+     * not be called for instances created with {@link #createDeviceContext}.
+     *<p>
+     * Note that updating the deviceId of the Context will not update its associated display.
+     *</p>
+     * @param deviceId The new device ID to assign to this Context.
+     * @throws UnsupportedOperationException if the method is called on an instance that was
+     *         created with {@link Context#createDeviceContext(int)}
+     * @throws IllegalArgumentException if the given device ID is not a valid ID of the default
+     *         device or a virtual device.
+     *
+     * @see #isDeviceContext()
+     * @see #createDeviceContext(int)
+     * @hide
+     */
+    public void updateDeviceId(int deviceId) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
+     * Gets the device ID this context is associated with. Applications can use this method to
      * determine whether they are running on a virtual device and identify that device.
      *
      * The device ID of the host device is
      * {@link android.companion.virtual.VirtualDeviceManager#DEVICE_ID_DEFAULT}
      *
+     * <p>
+     * If the underlying device ID is changed by the system, for example, when an
+     * {@link Activity} is moved to a different virtual device, applications can register to listen
+     * to changes by calling
+     * {@link Context#registerDeviceIdChangeListener(Executor, IntConsumer)}.
+     * </p>
+     *
+     * <p>
+     * This method will only return a reliable value for this instance if
+     * {@link Context#isDeviceContext()} is {@code true}. The system can assign an arbitrary device
+     * id value for Contexts not logically associated with a device.
+     * </p>
+     *
      * @return the ID of the device this context is associated with.
+     * @see #isDeviceContext()
      * @see #createDeviceContext(int)
+     * @see #registerDeviceIdChangeListener(Executor, IntConsumer)
      */
     public int getDeviceId() {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
     /**
+     * Indicates whether the value of {@link Context#getDeviceId()} can be relied upon for
+     * this instance. It will return {@code true} for Contexts created by
+     * {@link Context#createDeviceContext(int)}, as well as for UI and Display Contexts.
+     * <p>
+     * Contexts created with {@link Context#createDeviceContext(int)} will have an explicit
+     * device association, which will never change. UI Contexts and Display Contexts are
+     * already associated with a display, so if the device association is not explicitly
+     * given, {@link Context#getDeviceId()} will return the ID of the device associated with
+     * the associated display. The system can assign an arbitrary device id value for Contexts not
+     * logically associated with a device.
+     * </p>
+     *
+     * @return {@code true} if {@link Context#getDeviceId()} is reliable, {@code false} otherwise.
+     *
+     * @see #createDeviceContext(int)
+     * @see #getDeviceId()}
+     * @see #createDisplayContext(Display)
+     * @see #isUiContext()
+     */
+
+    public boolean isDeviceContext() {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
+     * Adds a new device ID changed listener to the {@code Context}, which will be called when
+     * the device association is changed by the system.
+     * <p>
+     * The callback can be called when an app is moved to a different device and the {@code Context}
+     * is not explicily associated with a specific device.
+     * </p>
+     * <p> When an application receives a device id update callback, this Context is guaranteed to
+     * also have an updated display ID(if any) and {@link Configuration}.
+     * <p/>
+     * @param executor The Executor on whose thread to execute the callbacks of the {@code listener}
+     *                 object.
+     * @param listener The listener {@code IntConsumer} to call which will receive the updated
+     *                 device ID.
+     *
+     * @see Context#isDeviceContext()
+     * @see Context#getDeviceId()
+     * @see Context#createDeviceContext(int)
+     */
+    public void registerDeviceIdChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer listener) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
+     * Removes a device ID changed listener from the Context. It's a no-op if
+     * the listener is not already registered.
+     *
+     * @param listener The {@code Consumer} to remove.
+     *
+     * @see #getDeviceId()
+     * @see #registerDeviceIdChangeListener(Executor, IntConsumer)
+     */
+    public void unregisterDeviceIdChangeListener(@NonNull IntConsumer listener) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Indicates whether this Context is restricted.
      *
      * @return {@code true} if this Context is restricted, {@code false} otherwise.
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a1646a1..0a32dd78 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -16,6 +16,7 @@
 
 package android.content;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -61,6 +62,7 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.function.IntConsumer;
 
 /**
  * Proxying implementation of Context that simply delegates all of its calls to
@@ -1171,12 +1173,36 @@
         mBase.updateDisplay(displayId);
     }
 
+    /**
+     * @hide
+     */
+    @Override
+    public void updateDeviceId(int deviceId) {
+        mBase.updateDeviceId(deviceId);
+    }
+
     @Override
     public int getDeviceId() {
         return mBase.getDeviceId();
     }
 
     @Override
+    public boolean isDeviceContext() {
+        return mBase.isDeviceContext();
+    }
+
+    @Override
+    public void registerDeviceIdChangeListener(@NonNull @CallbackExecutor Executor executor,
+            @NonNull IntConsumer listener) {
+        mBase.registerDeviceIdChangeListener(executor, listener);
+    }
+
+    @Override
+    public void unregisterDeviceIdChangeListener(@NonNull IntConsumer listener) {
+        mBase.unregisterDeviceIdChangeListener(listener);
+    }
+
+    @Override
     public Context createDeviceProtectedStorageContext() {
         return mBase.createDeviceProtectedStorageContext();
     }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 8aa0454..265e02a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1814,8 +1814,8 @@
      * Package manager install result code.  @hide because result codes are not
      * yet ready to be exposed.
      */
-    public static final String EXTRA_INSTALL_RESULT
-            = "android.intent.extra.INSTALL_RESULT";
+    @SystemApi
+    public static final String EXTRA_INSTALL_RESULT = "android.intent.extra.INSTALL_RESULT";
 
     /**
      * Activity Action: Launch application uninstaller.
@@ -1841,6 +1841,7 @@
      * Specify whether the package should be uninstalled for all users.
      * @hide because these should not be part of normal application flow.
      */
+    @SystemApi
     public static final String EXTRA_UNINSTALL_ALL_USERS
             = "android.intent.extra.UNINSTALL_ALL_USERS";
 
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 99fc5a3..7e787c9 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -35,29 +35,82 @@
 import java.util.Objects;
 
 /**
- * Fabricated Runtime Resource Overlays (FRROs) are overlays generated ar runtime.
+ * FabricatedOverlay describes the content of Fabricated Runtime Resource Overlay (FRRO) that is
+ * used to overlay the app's resources. The app should register the {@link FabricatedOverlay}
+ * instance in an {@link OverlayManagerTransaction} by calling {@link
+ * OverlayManagerTransaction#registerFabricatedOverlay(FabricatedOverlay)}. The FRRO is
+ * created once the transaction is committed successfully.
  *
- * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
- * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
- * overlay fulfill. For example, a fabricated overlay created by a platform signed package on the
- * system partition would fulfil the {@code system} and {@code signature} policies.
+ * <p>The app creates a FabricatedOverlay to describe the how to overlay string, integer, and file
+ * type resources. Before creating any frro, please define a target overlayable in {@code
+ * res/values/overlayable.xml} that describes what kind of resources can be overlaid, what kind of
+ * roles or applications can overlay the resources. Here is an example.
  *
- * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
- * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are wiped.
+ * <pre>{@code
+ * <overlayable name="SignatureOverlayable" actor="overlay://theme">
+ *     <!-- The app with the same signature can overlay the below resources -->
+ *     <policy type="signature">
+ *         <item type="color" name="mycolor" />
+ *         <item type="string" name="mystring" />
+ *     </policy>
+ * </overlayable>
+ * }</pre>
  *
- * Processes with {@link Android.Manifest.permission.CHANGE_OVERLAY_PACKAGES} can manage normal
- * overlays and fabricated overlays.
- * @hide
+ * <p>The overlay must assign the target overlayable name just like the above example by calling
+ * {@link #setTargetOverlayable(String)}. Here is an example:
+ *
+ * <pre>{@code
+ * FabricatedOverlay fabricatedOverlay = new FabricatedOverlay("overlay_name",
+ *                                                             context.getPackageName());
+ * fabricatedOverlay.setTargetOverlayable("SignatureOverlayable")
+ * fabricatedOverlay.setResourceValue("mycolor", TypedValue.TYPE_INT_COLOR_ARGB8, Color.White)
+ * fabricatedOverlay.setResourceValue("mystring", TypedValue.TYPE_STRING, "Hello")
+ * }</pre>
+ *
+ * <p>The app can create any {@link FabricatedOverlay} instance by calling the following APIs.
+ *
+ * <ul>
+ *   <li>{@link #setTargetOverlayable(String)}
+ *   <li>{@link #setResourceValue(String, int, int, String)}
+ *   <li>{@link #setResourceValue(String, int, String, String)}
+ *   <li>{@link #setResourceValue(String, ParcelFileDescriptor, String)}
+ * </ul>
+ *
+ * @see OverlayManager
+ * @see OverlayManagerTransaction
  */
 public class FabricatedOverlay {
 
-    /** Retrieves the identifier for this fabricated overlay. */
+    /**
+     * Retrieves the identifier for this fabricated overlay.
+     * @return the overlay identifier
+     */
+    @NonNull
     public OverlayIdentifier getIdentifier() {
         return new OverlayIdentifier(
                 mOverlay.packageName, TextUtils.nullIfEmpty(mOverlay.overlayName));
     }
 
-    public static class Builder {
+    /**
+     * The builder of Fabricated Runtime Resource Overlays(FRROs).
+     *
+     * Fabricated overlays are enabled, disabled, and reordered just like normal overlays. The
+     * overlayable policies a fabricated overlay fulfills are the same policies the creator of the
+     * overlay fulfill. For example, a fabricated overlay created by a platform signed package on
+     * the system partition would fulfil the {@code system} and {@code signature} policies.
+     *
+     * The owner of a fabricated overlay is the UID that created it. Overlays commit to the overlay
+     * manager persist across reboots. When the UID is uninstalled, its fabricated overlays are
+     * wiped.
+     *
+     * Processes with {@code android.Manifest.permission#CHANGE_OVERLAY_PACKAGES} can manage normal
+     * overlays and fabricated overlays.
+     *
+     * @see FabricatedOverlay
+     * @see OverlayManagerTransaction.Builder#registerFabricatedOverlay(FabricatedOverlay)
+     * @hide
+     */
+    public static final class Builder {
         private final String mOwningPackage;
         private final String mName;
         private final String mTargetPackage;
@@ -88,22 +141,16 @@
         }
 
         /**
-         * Constructs a builder for building a fabricated overlay.
+         * Sets the name of the target overlayable to be overlaid.
          *
-         * @param name a name used to uniquely identify the fabricated overlay owned by the caller
-         *             itself.
-         * @param targetPackage the name of the package to overlay
-         */
-        public Builder(@NonNull String name, @NonNull String targetPackage) {
-            mName = OverlayManagerImpl.checkOverlayNameValid(name);
-            mTargetPackage =
-                    Preconditions.checkStringNotEmpty(
-                            targetPackage, "'targetPackage' must not be empty nor null");
-            mOwningPackage = ""; // The package name is filled in OverlayManager.commit
-        }
-
-        /**
-         * Sets the name of the overlayable resources to overlay (can be null).
+         * <p>The target package defines may define several overlayables. The
+         * {@link FabricatedOverlay} should specify which overlayable to be overlaid.
+         *
+         * <p>The target overlayable should be defined in {@code <overlayable>} and pass the value
+         * of its {@code name} attribute as the parameter.
+         *
+         * @param targetOverlayable is a name of the overlayable resources set
+         * @hide
          */
         @NonNull
         public Builder setTargetOverlayable(@Nullable String targetOverlayable) {
@@ -112,27 +159,6 @@
         }
 
         /**
-         * Ensure the resource name is in the form [package]:type/entry.
-         *
-         * @param name name of the target resource to overlay (in the form [package]:type/entry)
-         * @return the valid name
-         */
-        private static String ensureValidResourceName(@NonNull String name) {
-            Objects.requireNonNull(name);
-            final int slashIndex = name.indexOf('/'); /* must contain '/' */
-            final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
-
-            // The minimum length of resource type is "id".
-            Preconditions.checkArgument(
-                    slashIndex >= 0 /* It must contain the type name */
-                    && colonIndex != 0 /* 0 means the package name is empty */
-                    && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
-                    "\"%s\" is invalid resource name",
-                    name);
-            return name;
-        }
-
-        /**
          * Sets the value of the fabricated overlay for the integer-like types.
          *
          * @param resourceName name of the target resource to overlay (in the form
@@ -141,8 +167,12 @@
          * @param value the unsigned 32 bit integer representing the new value
          * @return the builder itself
          * @see #setResourceValue(String, int, int, String)
-         * @see android.util.TypedValue#type
+         * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int,
+                       int, String)} instead.
+         * @hide
          */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
         @NonNull
         public Builder setResourceValue(
                 @NonNull String resourceName,
@@ -161,8 +191,13 @@
          * @param dataType the data type of the new value
          * @param value the unsigned 32 bit integer representing the new value
          * @param configuration The string representation of the config this overlay is enabled for
-         * @see android.util.TypedValue#type
+         * @return the builder itself
+         * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int,
+                       int, String)} instead.
+         * @hide
          */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
         @NonNull
         public Builder setResourceValue(
                 @NonNull String resourceName,
@@ -171,30 +206,11 @@
                 int value,
                 @Nullable String configuration) {
             ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType =
-                    Preconditions.checkArgumentInRange(
-                            dataType,
-                            TypedValue.TYPE_FIRST_INT,
-                            TypedValue.TYPE_LAST_INT,
-                            "dataType");
-            entry.data = value;
-            entry.configuration = configuration;
-            mEntries.add(entry);
+            mEntries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value,
+                    configuration));
             return this;
         }
 
-        /** @hide */
-        @IntDef(
-                prefix = {"OVERLAY_TYPE"},
-                value = {
-                    TypedValue.TYPE_STRING,
-                })
-        @Retention(RetentionPolicy.SOURCE)
-        public @interface StringTypeOverlayResource {}
-
         /**
          * Sets the value of the fabricated overlay for the string-like type.
          *
@@ -203,8 +219,12 @@
          * @param dataType the data type of the new value
          * @param value the string representing the new value
          * @return the builder itself
-         * @see android.util.TypedValue#type
+         * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int,
+                       String, String)} instead.
+         * @hide
          */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
         @NonNull
         public Builder setResourceValue(
                 @NonNull String resourceName,
@@ -221,8 +241,13 @@
          * @param dataType the data type of the new value
          * @param value the string representing the new value
          * @param configuration The string representation of the config this overlay is enabled for
-         * @see android.util.TypedValue#type
+         * @return the builder itself
+         * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String, int,
+                       String, String)} instead.
+         * @hide
          */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
         @NonNull
         public Builder setResourceValue(
                 @NonNull String resourceName,
@@ -230,39 +255,32 @@
                 @NonNull String value,
                 @Nullable String configuration) {
             ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.dataType =
-                    Preconditions.checkArgumentInRange(
-                            dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
-            entry.stringData = Objects.requireNonNull(value);
-            entry.configuration = configuration;
-            mEntries.add(entry);
+            mEntries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value,
+                    configuration));
             return this;
         }
 
         /**
-         * Sets the value of the fabricated overlay
+         * Sets the value of the fabricated overlay for the file descriptor type.
          *
          * @param resourceName name of the target resource to overlay (in the form
          *     [package]:type/entry)
          * @param value the file descriptor whose contents are the value of the frro
          * @param configuration The string representation of the config this overlay is enabled for
          * @return the builder itself
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String,
+                       ParcelFileDescriptor, String)} instead.
+         * @hide
          */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
         @NonNull
         public Builder setResourceValue(
                 @NonNull String resourceName,
                 @NonNull ParcelFileDescriptor value,
                 @Nullable String configuration) {
             ensureValidResourceName(resourceName);
-
-            final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
-            entry.resourceName = resourceName;
-            entry.binaryData = Objects.requireNonNull(value);
-            entry.configuration = configuration;
-            mEntries.add(entry);
+            mEntries.add(
+                    generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
             return this;
         }
 
@@ -270,22 +288,211 @@
          * Builds an immutable fabricated overlay.
          *
          * @return the fabricated overlay
+         * @hide
          */
         @NonNull
         public FabricatedOverlay build() {
-            final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
-            overlay.packageName = mOwningPackage;
-            overlay.overlayName = mName;
-            overlay.targetPackageName = mTargetPackage;
-            overlay.targetOverlayable = mTargetOverlayable;
-            overlay.entries = new ArrayList<>();
-            overlay.entries.addAll(mEntries);
-            return new FabricatedOverlay(overlay);
+            return new FabricatedOverlay(
+                    generateFabricatedOverlayInternal(mOwningPackage, mName, mTargetPackage,
+                            mTargetOverlayable, mEntries));
         }
     }
 
+    private static FabricatedOverlayInternal generateFabricatedOverlayInternal(
+            @NonNull String owningPackage, @NonNull String overlayName,
+            @NonNull String targetPackageName, @Nullable String targetOverlayable,
+            @NonNull ArrayList<FabricatedOverlayInternalEntry> entries) {
+        final FabricatedOverlayInternal overlay = new FabricatedOverlayInternal();
+        overlay.packageName = owningPackage;
+        overlay.overlayName = overlayName;
+        overlay.targetPackageName = targetPackageName;
+        overlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+        overlay.entries = new ArrayList<>();
+        overlay.entries.addAll(entries);
+        return overlay;
+    }
+
     final FabricatedOverlayInternal mOverlay;
     private FabricatedOverlay(FabricatedOverlayInternal overlay) {
         mOverlay = overlay;
     }
+
+    /**
+     * Create a fabricated overlay to overlay on the specified package.
+     *
+     * @param overlayName a name used to uniquely identify the fabricated overlay owned by the
+     *                   caller itself.
+     * @param targetPackage the name of the package to be overlaid
+     */
+    public FabricatedOverlay(@NonNull String overlayName, @NonNull String targetPackage) {
+        this(generateFabricatedOverlayInternal(
+                "" /* owningPackage, The package name is filled commitment */,
+                OverlayManagerImpl.checkOverlayNameValid(overlayName),
+                Preconditions.checkStringNotEmpty(targetPackage,
+                        "'targetPackage' must not be empty nor null"),
+                null /* targetOverlayable */,
+                new ArrayList<>()));
+    }
+
+    /**
+     * Set the target overlayable name of the overlay
+     *
+     * The target package defines may define several overlayables. The {@link FabricatedOverlay}
+     * should specify which overlayable to be overlaid.
+     *
+     * @param targetOverlayable the overlayable name defined in target package.
+     */
+    public void setTargetOverlayable(@Nullable String targetOverlayable) {
+        mOverlay.targetOverlayable = TextUtils.emptyIfNull(targetOverlayable);
+    }
+
+    /**
+     * Return the target overlayable name of the overlay
+     *
+     * The target package defines may define several overlayables. The {@link FabricatedOverlay}
+     * should specify which overlayable to be overlaid.
+     *
+     * @return the target overlayable name.
+     * @hide
+     */
+    @Nullable
+    public String getTargetOverlayable() {
+        return mOverlay.targetOverlayable;
+    }
+
+    /**
+     * Ensure the resource name is in the form [package]:type/entry.
+     *
+     * @param name name of the target resource to overlay (in the form [package]:type/entry)
+     * @return the valid name
+     */
+    private static String ensureValidResourceName(@NonNull String name) {
+        Objects.requireNonNull(name);
+        final int slashIndex = name.indexOf('/'); /* must contain '/' */
+        final int colonIndex = name.indexOf(':'); /* ':' should before '/' if ':' exist */
+
+        // The minimum length of resource type is "id".
+        Preconditions.checkArgument(
+                slashIndex >= 0 /* It must contain the type name */
+                        && colonIndex != 0 /* 0 means the package name is empty */
+                        && (slashIndex - colonIndex) > 2 /* The shortest length of type is "id" */,
+                "\"%s\" is invalid resource name",
+                name);
+        return name;
+    }
+
+    @NonNull
+    private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+            @NonNull String resourceName,
+            @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType,
+            int value, @Nullable String configuration) {
+        final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+        entry.resourceName = resourceName;
+        entry.dataType =
+                Preconditions.checkArgumentInRange(
+                        dataType,
+                        TypedValue.TYPE_FIRST_INT,
+                        TypedValue.TYPE_LAST_INT,
+                        "dataType");
+        entry.data = value;
+        entry.configuration = configuration;
+        return entry;
+    }
+
+    @NonNull
+    private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+            @NonNull String resourceName, @StringTypeOverlayResource int dataType,
+            @NonNull String value, @Nullable String configuration) {
+        final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+        entry.resourceName = resourceName;
+        entry.dataType =
+                Preconditions.checkArgumentInRange(
+                        dataType, TypedValue.TYPE_STRING, TypedValue.TYPE_FRACTION, "dataType");
+        entry.stringData = Objects.requireNonNull(value);
+        entry.configuration = configuration;
+        return entry;
+    }
+
+    @NonNull
+    private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+            @NonNull String resourceName, @NonNull ParcelFileDescriptor parcelFileDescriptor,
+            @Nullable String configuration) {
+        final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+        entry.resourceName = resourceName;
+        entry.binaryData = Objects.requireNonNull(parcelFileDescriptor);
+        entry.configuration = configuration;
+        return entry;
+    }
+
+    /**
+     * Sets the resource value in the fabricated overlay for the integer-like types with the
+     * configuration.
+     *
+     * @param resourceName name of the target resource to overlay (in the form
+     *     [package]:type/entry)
+     * @param dataType the data type of the new value
+     * @param value the integer representing the new value
+     * @param configuration The string representation of the config this overlay is enabled for
+     * @see android.util.TypedValue#TYPE_INT_COLOR_ARGB8 android.util.TypedValue#type
+     */
+    @NonNull
+    public void setResourceValue(
+            @NonNull String resourceName,
+            @IntRange(from = TypedValue.TYPE_FIRST_INT, to = TypedValue.TYPE_LAST_INT) int dataType,
+            int value,
+            @Nullable String configuration) {
+        ensureValidResourceName(resourceName);
+        mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value,
+                configuration));
+    }
+
+    /** @hide */
+    @IntDef(
+            prefix = {"OVERLAY_TYPE"},
+            value = {
+                    TypedValue.TYPE_STRING,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StringTypeOverlayResource {}
+
+    /**
+     * Sets the resource value in the fabricated overlay for the string-like type with the
+     * configuration.
+     *
+     * @param resourceName name of the target resource to overlay (in the form
+     *     [package]:type/entry)
+     * @param dataType the data type of the new value
+     * @param value the string representing the new value
+     * @param configuration The string representation of the config this overlay is enabled for
+     * @see android.util.TypedValue#TYPE_STRING android.util.TypedValue#type
+     */
+    @NonNull
+    public void setResourceValue(
+            @NonNull String resourceName,
+            @StringTypeOverlayResource int dataType,
+            @NonNull String value,
+            @Nullable String configuration) {
+        ensureValidResourceName(resourceName);
+        mOverlay.entries.add(generateFabricatedOverlayInternalEntry(resourceName, dataType, value,
+                configuration));
+    }
+
+    /**
+     * Sets the resource value in the fabricated overlay for the file descriptor type with the
+     * configuration.
+     *
+     * @param resourceName name of the target resource to overlay (in the form
+     *     [package]:type/entry)
+     * @param value the file descriptor whose contents are the value of the frro
+     * @param configuration The string representation of the config this overlay is enabled for
+     */
+    @NonNull
+    public void setResourceValue(
+            @NonNull String resourceName,
+            @NonNull ParcelFileDescriptor value,
+            @Nullable String configuration) {
+        ensureValidResourceName(resourceName);
+        mOverlay.entries.add(
+                generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+    }
 }
diff --git a/core/java/android/content/om/OverlayIdentifier.java b/core/java/android/content/om/OverlayIdentifier.java
index 454d0d1..f256372 100644
--- a/core/java/android/content/om/OverlayIdentifier.java
+++ b/core/java/android/content/om/OverlayIdentifier.java
@@ -27,32 +27,43 @@
 
 /**
  * A key used to uniquely identify a Runtime Resource Overlay (RRO).
+ * <!-- For applications -->
  *
- * An overlay always belongs to a package and may optionally have a name associated with it.
- * The name helps uniquely identify a particular overlay within a package.
- * @hide
+ * <p>An overlay always belongs to a package and have a mandatory name associated with it. The name
+ * helps uniquely identify a particular overlay within a package.
+ *
+ * <!-- For OverlayManagerService, it isn't public part and hidden by HTML comment. -->
+ * <!--
+ * <p>An overlay always belongs to a package and may optionally have a name associated with it. The
+ * name helps uniquely identify a particular overlay within a package.
+ * -->
+ *
+ * @see OverlayInfo#getOverlayIdentifier()
+ * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
  */
-/** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false,
         genEqualsHashCode = true, genToString = false)
-public class OverlayIdentifier implements Parcelable  {
+public final class OverlayIdentifier implements Parcelable {
     /**
      * The package name containing or owning the overlay.
+     *
+     * @hide
      */
-    @Nullable
-    private final String mPackageName;
+    @Nullable private final String mPackageName;
 
     /**
      * The unique name within the package of the overlay.
+     *
+     * @hide
      */
-    @Nullable
-    private final String mOverlayName;
+    @Nullable private final String mOverlayName;
 
     /**
      * Creates an identifier from a package and unique name within the package.
      *
      * @param packageName the package containing or owning the overlay
      * @param overlayName the unique name of the overlay within the package
+     * @hide
      */
     public OverlayIdentifier(@NonNull String packageName, @Nullable String overlayName) {
         mPackageName = packageName;
@@ -63,18 +74,24 @@
      * Creates an identifier for an overlay without a name.
      *
      * @param packageName the package containing or owning the overlay
+     * @hide
      */
     public OverlayIdentifier(@NonNull String packageName) {
         mPackageName = packageName;
         mOverlayName = null;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     public String toString() {
         return mOverlayName == null ? mPackageName : mPackageName + ":" + mOverlayName;
     }
 
     /** @hide */
+    @NonNull
     public static OverlayIdentifier fromString(@NonNull String text) {
         final String[] parts = text.split(":", 2);
         if (parts.length == 2) {
@@ -86,7 +103,7 @@
 
 
 
-    // Code below generated by codegen v1.0.22.
+    // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
     // CHECKSTYLE:OFF Generated code
@@ -101,6 +118,8 @@
 
     /**
      * Retrieves the package name containing or owning the overlay.
+     *
+     * @hide
      */
     @DataClass.Generated.Member
     public @Nullable String getPackageName() {
@@ -109,12 +128,18 @@
 
     /**
      * Retrieves the unique name within the package of the overlay.
+     *
+     * @hide
      */
     @DataClass.Generated.Member
     public @Nullable String getOverlayName() {
         return mOverlayName;
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public boolean equals(@Nullable Object o) {
@@ -132,6 +157,10 @@
                 && Objects.equals(mOverlayName, that.mOverlayName);
     }
 
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
     @Override
     @DataClass.Generated.Member
     public int hashCode() {
@@ -144,6 +173,9 @@
         return _hash;
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     @DataClass.Generated.Member
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -158,6 +190,9 @@
         if (mOverlayName != null) dest.writeString(mOverlayName);
     }
 
+    /**
+     * {@inheritDoc}
+     */
     @Override
     @DataClass.Generated.Member
     public int describeContents() { return 0; }
@@ -165,7 +200,7 @@
     /** @hide */
     @SuppressWarnings({"unchecked", "RedundantCast"})
     @DataClass.Generated.Member
-    protected OverlayIdentifier(@NonNull Parcel in) {
+    /* package-private */ OverlayIdentifier(@NonNull Parcel in) {
         // You can override field unparcelling by defining methods like:
         // static FieldType unparcelFieldName(Parcel in) { ... }
 
@@ -194,10 +229,10 @@
     };
 
     @DataClass.Generated(
-            time = 1612482438728L,
-            codegenVersion = "1.0.22",
+            time = 1670404485646L,
+            codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/content/om/OverlayIdentifier.java",
-            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static  android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
+            inputSignatures = "private final @android.annotation.Nullable java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mOverlayName\npublic @java.lang.Override java.lang.String toString()\npublic static @android.annotation.NonNull android.content.om.OverlayIdentifier fromString(java.lang.String)\nclass OverlayIdentifier extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index a470de2..ff1c088 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -33,12 +33,20 @@
 import java.util.Objects;
 
 /**
+ * An immutable information about an overlay.
+ *
+ * <p>Applications calling {@link OverlayManager#getOverlayInfosForTarget(String)} get the
+ * information list of the registered overlays. Each element in the list presents the information of
+ * the particular overlay.
+ *
+ * <!-- For OverlayManagerService, it isn't public part and hidden by HTML comment. -->
+ * <!--
  * Immutable overlay information about a package. All PackageInfos that
  * represent an overlay package will have a corresponding OverlayInfo.
+ * -->
  *
- * @hide
+ * @see OverlayManager#getOverlayInfosForTarget(String)
  */
-@SystemApi
 public final class OverlayInfo implements CriticalOverlayInfo, Parcelable {
 
     /** @hide */
@@ -49,7 +57,6 @@
             STATE_DISABLED,
             STATE_ENABLED,
             STATE_ENABLED_IMMUTABLE,
-            // @Deprecated STATE_TARGET_IS_BEING_REPLACED,
             STATE_OVERLAY_IS_BEING_REPLACED,
             STATE_SYSTEM_UPDATE_UNINSTALL,
     })
@@ -174,14 +181,14 @@
      *
      * @hide
      */
-    public final String targetOverlayableName;
+    @Nullable public final String targetOverlayableName;
 
     /**
      * Category of the overlay package
      *
      * @hide
      */
-    public final String category;
+    @Nullable public final String category;
 
     /**
      * Full path to the base APK for this overlay package
@@ -272,7 +279,7 @@
     }
 
     /** @hide */
-    public OverlayInfo(Parcel source) {
+    public OverlayInfo(@NonNull Parcel source) {
         packageName = source.readString();
         overlayName = source.readString();
         targetPackageName = source.readString();
@@ -299,8 +306,9 @@
     }
 
     /**
-     * {@inheritDoc}
-     * @hide
+     * Get the overlay name from the registered fabricated overlay.
+     *
+     * @return the overlay name
      */
     @Override
     @Nullable
@@ -309,11 +317,11 @@
     }
 
     /**
-     * {@inheritDoc}
-     * @hide
+     * Returns the name of the target overlaid package.
+     *
+     * @return the target package name
      */
     @Override
-    @SystemApi
     @NonNull
     public String getTargetPackageName() {
         return targetPackageName;
@@ -342,11 +350,11 @@
     }
 
     /**
-     * {@inheritDoc}
-     * @hide
+     * Return the target overlayable name.
+     *
+     * @return the name of the target overlayable resources set
      */
     @Override
-    @SystemApi
     @Nullable
     public String getTargetOverlayableName() {
         return targetOverlayableName;
@@ -366,13 +374,18 @@
      *
      * @hide
      */
+    @NonNull
     public String getBaseCodePath() {
         return baseCodePath;
     }
 
     /**
-     * {@inheritDoc}
-     * @hide
+     * Get the unique identifier from the overlay information.
+     *
+     * <p>The return value of this function can be used to unregister the related overlay.
+     *
+     * @return an identifier representing the current overlay.
+     * @see OverlayManagerTransaction.Builder#unregisterFabricatedOverlay(OverlayIdentifier)
      */
     @Override
     @NonNull
@@ -415,7 +428,7 @@
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(packageName);
         dest.writeString(overlayName);
         dest.writeString(targetPackageName);
@@ -429,7 +442,7 @@
         dest.writeBoolean(isFabricated);
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<OverlayInfo> CREATOR =
+    public static final @NonNull Parcelable.Creator<OverlayInfo> CREATOR =
             new Parcelable.Creator<OverlayInfo>() {
         @Override
         public OverlayInfo createFromParcel(Parcel source) {
@@ -492,6 +505,11 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @Override
     public int hashCode() {
         final int prime = 31;
@@ -508,6 +526,11 @@
         return result;
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @Override
     public boolean equals(@Nullable Object obj) {
         if (this == obj) {
@@ -547,6 +570,11 @@
         return true;
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
     @NonNull
     @Override
     public String toString() {
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
index 7803cb8..96b7603 100644
--- a/core/java/android/content/om/OverlayManager.java
+++ b/core/java/android/content/om/OverlayManager.java
@@ -54,9 +54,7 @@
  * </ul>
  *
  * @see OverlayManagerTransaction
- * @hide
  */
-@SystemApi
 @SystemService(Context.OVERLAY_SERVICE)
 public class OverlayManager {
 
@@ -392,7 +390,6 @@
      *
      * @param targetPackageName the target package name
      * @return a list of overlay information
-     * @hide
      */
     @NonNull
     @NonUiContext
diff --git a/core/java/android/content/om/OverlayManagerTransaction.java b/core/java/android/content/om/OverlayManagerTransaction.java
index c7c605d..5fd695b 100644
--- a/core/java/android/content/om/OverlayManagerTransaction.java
+++ b/core/java/android/content/om/OverlayManagerTransaction.java
@@ -58,7 +58,6 @@
  *
  * @see OverlayManager
  * @see FabricatedOverlay
- * @hide
  */
 public final class OverlayManagerTransaction implements Parcelable {
     // TODO: remove @hide from this class when OverlayManager is added to the
@@ -92,8 +91,6 @@
     /**
      * Get an overlay manager transaction with the specified handler.
      * @param overlayManager handles this transaction.
-     *
-     * @hide
      */
     public OverlayManagerTransaction(@NonNull OverlayManager overlayManager) {
         this(new ArrayList<>(), Objects.requireNonNull(overlayManager));
@@ -291,8 +288,6 @@
 
     /**
      * {@inheritDoc}
-     *
-     * @hide
      */
     @Override
     public int describeContents() {
@@ -301,8 +296,6 @@
 
     /**
      * {@inheritDoc}
-     *
-     * @hide
      */
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
@@ -340,7 +333,6 @@
      *
      * @throws IOException if there is a file operation error.
      * @throws PackageManager.NameNotFoundException if the package name is not found.
-     * @hide
      */
     @NonUiContext
     public void commit() throws PackageManager.NameNotFoundException, IOException {
@@ -374,8 +366,6 @@
      * package or target overlayable is changed.
      *
      * @param overlay the overlay to register with the overlay manager
-     *
-     * @hide
      */
     @NonNull
     public void registerFabricatedOverlay(@NonNull FabricatedOverlay overlay) {
@@ -389,7 +379,6 @@
      *
      * @see OverlayManager#getOverlayInfosForTarget(String)
      * @see OverlayInfo#getOverlayIdentifier()
-     * @hide
      */
     @NonNull
     public void unregisterFabricatedOverlay(@NonNull OverlayIdentifier overlay) {
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index dab57fd..68a84e8 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -807,6 +807,7 @@
             CONFIG_LAYOUT_DIRECTION,
             CONFIG_COLOR_MODE,
             CONFIG_FONT_SCALE,
+            CONFIG_GRAMMATICAL_GENDER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Config {}
@@ -917,6 +918,12 @@
     public static final int CONFIG_COLOR_MODE = 0x4000;
     /**
      * Bit in {@link #configChanges} that indicates that the activity
+     * can itself handle the change to gender. Set from the
+     * {@link android.R.attr#configChanges} attribute.
+     */
+    public static final int CONFIG_GRAMMATICAL_GENDER = 0x8000;
+    /**
+     * Bit in {@link #configChanges} that indicates that the activity
      * can itself handle asset path changes.  Set from the {@link android.R.attr#configChanges}
      * attribute. This is not a core resource configuration, but a higher-level value, so its
      * constant starts at the high bits.
@@ -946,7 +953,6 @@
      * not a core resource configuration, but a higher-level value, so its
      * constant starts at the high bits.
      */
-
     public static final int CONFIG_FONT_WEIGHT_ADJUSTMENT = 0x10000000;
 
     /** @hide
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 84811ea..94c5e25 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -18,9 +18,11 @@
 
 import static android.os.Build.VERSION_CODES.DONUT;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -2253,6 +2255,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.DELETE_PACKAGES)
     public boolean hasFragileUserData() {
         return (privateFlags & PRIVATE_FLAG_HAS_FRAGILE_USER_DATA) != 0;
     }
@@ -2487,8 +2491,13 @@
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY) != 0;
     }
 
-    /** @hide */
+    /**
+     * @return {@code true} if the application is permitted to hold privileged permissions.
+     *
+     * @hide */
     @TestApi
+    @SystemApi
+    @RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
     public boolean isPrivilegedApp() {
         return (privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index febdaed..703a9252 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -26,6 +26,9 @@
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA1;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA256;
 import static android.content.pm.Checksum.TYPE_WHOLE_SHA512;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_AUTO;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
+import static android.content.pm.PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
@@ -48,6 +51,10 @@
 import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.PackageManager.InstallReason;
 import android.content.pm.PackageManager.InstallScenario;
+import android.content.pm.parsing.ApkLiteParseUtils;
+import android.content.pm.parsing.PackageLite;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.graphics.Bitmap;
 import android.icu.util.ULocale;
 import android.net.Uri;
@@ -70,12 +77,14 @@
 import android.util.ArraySet;
 import android.util.ExceptionUtils;
 
+import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.Closeable;
+import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
@@ -172,10 +181,22 @@
     public static final String ACTION_SESSION_UPDATED =
             "android.content.pm.action.SESSION_UPDATED";
 
-    /** {@hide} */
+    /**
+     * Intent action to indicate that user action is required for current install. This action can
+     * be used only by system apps.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
 
-    /** @hide */
+    /**
+     * Activity Action: Intent sent to the installer when a session for requesting
+     * user pre-approval, and user needs to confirm the installation.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String ACTION_CONFIRM_PRE_APPROVAL =
             "android.content.pm.action.CONFIRM_PRE_APPROVAL";
 
@@ -272,11 +293,23 @@
     @Deprecated
     public static final String EXTRA_PACKAGE_NAMES = "android.content.pm.extra.PACKAGE_NAMES";
 
-    /** {@hide} */
+    /**
+     * The status as used internally in the package manager. Refer to {@link PackageManager} for
+     * a list of all valid legacy statuses.
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     /** {@hide} */
     public static final String EXTRA_LEGACY_BUNDLE = "android.content.pm.extra.LEGACY_BUNDLE";
-    /** {@hide} */
+    /**
+     * The callback to execute once an uninstall is completed (used for both successful and
+     * unsuccessful uninstalls).
+     *
+     * @hide
+     */
+    @SystemApi
     public static final String EXTRA_CALLBACK = "android.content.pm.extra.CALLBACK";
 
     /**
@@ -293,6 +326,17 @@
     public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
 
     /**
+     * Path to the validated base APK for this session, which may point at an
+     * APK inside the session (when the session defines the base), or it may
+     * point at the existing base APK (when adding splits to an existing app).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String EXTRA_RESOLVED_BASE_PATH =
+            "android.content.pm.extra.RESOLVED_BASE_PATH";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
@@ -796,8 +840,6 @@
      * @param statusReceiver Where to deliver the result of the operation indicated by the extra
      *                       {@link #EXTRA_STATUS}. Refer to the individual status codes
      *                       on how to handle them.
-     *
-     * @hide
      */
     @RequiresPermission(anyOf = {
             Manifest.permission.DELETE_PACKAGES,
@@ -1871,6 +1913,101 @@
     }
 
     /**
+     * Parse a single APK or a directory of APKs to get install relevant information about
+     * the package wrapped in {@link InstallInfo}.
+     * @throws PackageParsingException if the package source file(s) provided is(are) not valid,
+     * or the parser isn't able to parse the supplied source(s).
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public InstallInfo getInstallInfo(@NonNull File file, int flags)
+            throws PackageParsingException {
+        final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+        final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
+                input.reset(), file, flags);
+        if (result.isError()) {
+            throw new PackageParsingException(result.getErrorCode(), result.getErrorMessage());
+        }
+        return new InstallInfo(result);
+    }
+
+    // (b/239722738) This class serves as a bridge between the PackageLite class, which
+    // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
+    // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
+    // public APIs.
+    /**
+     * Install related details from an APK or a folder of APK(s).
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class InstallInfo {
+
+        /** @hide */
+        @IntDef(prefix = { "INSTALL_LOCATION_" }, value = {
+                INSTALL_LOCATION_AUTO,
+                INSTALL_LOCATION_INTERNAL_ONLY,
+                INSTALL_LOCATION_PREFER_EXTERNAL
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface InstallLocation{}
+
+        private PackageLite mPkg;
+
+        InstallInfo(ParseResult<PackageLite> result) {
+            mPkg = result.getResult();
+        }
+
+        /**
+         * See {@link PackageLite#getPackageName()}
+         */
+        @NonNull
+        public String getPackageName() {
+            return mPkg.getPackageName();
+        }
+
+        /**
+         * @return The default install location defined by an application in
+         * {@link android.R.attr#installLocation} attribute.
+         */
+        public @InstallLocation int getInstallLocation() {
+            return mPkg.getInstallLocation();
+        }
+
+        /**
+         * @param params {@link SessionParams} of the installation
+         * @return Total disk space occupied by an application after installation.
+         * Includes the size of the raw APKs, possibly unpacked resources, raw dex metadata files,
+         * and all relevant native code.
+         * @throws IOException when size of native binaries cannot be calculated.
+         */
+        public long calculateInstalledSize(@NonNull SessionParams params) throws IOException {
+            return InstallLocationUtils.calculateInstalledSize(mPkg, params.abiOverride);
+        }
+    }
+
+    /**
+     * Generic exception class for using with parsing operations.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class PackageParsingException extends Exception {
+        private final int mErrorCode;
+
+        /** {@hide} */
+        public PackageParsingException(int errorCode, @Nullable String detailedMessage) {
+            super(detailedMessage);
+            mErrorCode = errorCode;
+        }
+
+        public int getErrorCode() {
+            return mErrorCode;
+        }
+    }
+
+    /**
      * Parameters for creating a new {@link PackageInstaller.Session}.
      */
     public static class SessionParams implements Parcelable {
@@ -2431,9 +2568,7 @@
          * By default this is the app that created the {@link PackageInstaller} object.
          *
          * @param installerPackageName name of the installer package
-         * {@hide}
          */
-        @TestApi
         public void setInstallerPackageName(@Nullable String installerPackageName) {
             this.installerPackageName = installerPackageName;
         }
@@ -3430,8 +3565,6 @@
 
         /**
          * Returns the Uid of the owner of the session.
-         *
-         * @hide
          */
         public int getInstallerUid() {
             return installerUid;
@@ -3445,6 +3578,13 @@
             return keepApplicationEnabledSetting;
         }
 
+        /**
+         * Returns whether this session has requested user pre-approval.
+         */
+        public @NonNull boolean getIsPreApprovalRequested() {
+            return isPreapprovalRequested;
+        }
+
         @Override
         public int describeContents() {
             return 0;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index be19203..4ad657e 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2250,6 +2250,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_KEEP_DATA = 0x00000001;
 
     /**
@@ -2258,6 +2259,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_ALL_USERS = 0x00000002;
 
     /**
@@ -2295,6 +2297,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_SUCCEEDED = 1;
 
     /**
@@ -2304,6 +2307,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_INTERNAL_ERROR = -1;
 
     /**
@@ -2313,6 +2317,7 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_DEVICE_POLICY_MANAGER = -2;
 
     /**
@@ -2332,9 +2337,11 @@
      *
      * @hide
      */
+    @SystemApi
     public static final int DELETE_FAILED_OWNER_BLOCKED = -4;
 
     /** {@hide} */
+    @SystemApi
     public static final int DELETE_FAILED_ABORTED = -5;
 
     /**
@@ -4090,6 +4097,17 @@
     public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+     * the requisite kernel support for migrating IPsec tunnels to new source/destination addresses.
+     *
+     * <p>This feature implies that the device supports XFRM Migration (CONFIG_XFRM_MIGRATE) and has
+     * the kernel fixes to support cross-address-family IPsec tunnel migration
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_IPSEC_TUNNEL_MIGRATION =
+            "android.software.ipsec_tunnel_migration";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device supports a system interface for the user to select
      * and bind device control services provided by applications.
@@ -5336,17 +5354,7 @@
             throws NameNotFoundException;
 
     /**
-     * Return the UID associated with the given package name.
-     * <p>
-     * Note that the same package will have different UIDs under different
-     * {@link UserHandle} on the same device.
-     *
-     * @param packageName The full name (i.e. com.google.apps.contacts) of the
-     *            desired package.
-     * @param userId The user handle identifier to look up the package under.
-     * @return Returns an integer UID who owns the given package name.
-     * @throws NameNotFoundException if no such package is available to the
-     *             caller.
+     * See {@link #getPackageUidAsUser(String, PackageInfoFlags, int)}.
      * @deprecated Use {@link #getPackageUidAsUser(String, PackageInfoFlags, int)} instead.
      * @hide
      */
@@ -5357,9 +5365,22 @@
             int flags, @UserIdInt int userId) throws NameNotFoundException;
 
     /**
-     * See {@link #getPackageUidAsUser(String, int, int)}.
+     * Return the UID associated with the given package name.
+     * <p>
+     * Note that the same package will have different UIDs under different
+     * {@link UserHandle} on the same device.
+     *
+     * @param packageName The full name (i.e. com.google.apps.contacts) of the
+     *            desired package.
+     * @param flags Additional option flags to modify the data returned.
+     * @param userId The user handle identifier to look up the package under.
+     * @return Returns an integer UID who owns the given package name.
+     * @throws NameNotFoundException if no such package is available to the
+     *             caller.
      * @hide
      */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
     public int getPackageUidAsUser(@NonNull String packageName, @NonNull PackageInfoFlags flags,
             @UserIdInt int userId) throws NameNotFoundException {
         throw new UnsupportedOperationException(
@@ -9805,6 +9826,83 @@
     }
 
     /**
+     * A parcelable class to pass as an intent extra to the PackageInstaller. When an uninstall is
+     * completed (both successfully or unsuccessfully), the result is sent to the uninstall
+     * initiators.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final class UninstallCompleteCallback implements Parcelable {
+        private IPackageDeleteObserver2 mBinder;
+
+        /** @hide */
+        @IntDef(prefix = { "DELETE_" }, value = {
+                DELETE_SUCCEEDED,
+                DELETE_FAILED_INTERNAL_ERROR,
+                DELETE_FAILED_DEVICE_POLICY_MANAGER,
+                DELETE_FAILED_USER_RESTRICTED,
+                DELETE_FAILED_OWNER_BLOCKED,
+                DELETE_FAILED_ABORTED,
+                DELETE_FAILED_USED_SHARED_LIBRARY,
+                DELETE_FAILED_APP_PINNED,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface DeleteStatus{}
+
+        /** @hide */
+        public UninstallCompleteCallback(@NonNull IBinder binder) {
+            mBinder = IPackageDeleteObserver2.Stub.asInterface(binder);
+        }
+
+        /** @hide */
+        private UninstallCompleteCallback(Parcel in) {
+            mBinder = IPackageDeleteObserver2.Stub.asInterface(in.readStrongBinder());
+        }
+
+        public static final @NonNull Parcelable.Creator<UninstallCompleteCallback> CREATOR =
+                new Parcelable.Creator<>() {
+                    public UninstallCompleteCallback createFromParcel(Parcel source) {
+                        return new UninstallCompleteCallback(source);
+                    }
+
+                    public UninstallCompleteCallback[] newArray(int size) {
+                        return new UninstallCompleteCallback[size];
+                    }
+                };
+
+        /**
+         * Called when an uninstallation is completed successfully or unsuccessfully.
+         *
+         * @param packageName The name of the package being uninstalled.
+         * @param resultCode Result code of the operation.
+         * @param errorMessage Error message if any.
+         *
+         * @hide */
+        @SystemApi
+        public void onUninstallComplete(@NonNull String packageName, @DeleteStatus int resultCode,
+                @Nullable String errorMessage) {
+            try {
+                mBinder.onPackageDeleted(packageName, resultCode, errorMessage);
+            } catch (RemoteException e) {
+                // no-op
+            }
+        }
+
+        /** @hide */
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        /** @hide */
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeStrongBinder(mBinder.asBinder());
+        }
+    }
+
+    /**
      * Return the install reason that was recorded when a package was first
      * installed for a specific user. Requesting the install reason for another
      * user will require the permission INTERACT_ACROSS_USERS_FULL.
@@ -10427,9 +10525,7 @@
      * @throws NameNotFoundException if either a given package can not be found on the
      * system, or if the caller is not able to query for details about the source or
      * target packages.
-     * @hide
      */
-    @SystemApi
     @NonNull
     public boolean[] canPackageQuery(@NonNull String sourcePackageName,
             @NonNull String[] targetPackageNames) throws NameNotFoundException {
@@ -10725,4 +10821,33 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Checks if a package is blocked from uninstall for a particular user. A package can be
+     * blocked from being uninstalled by a device owner or profile owner.
+     * See {@link DevicePolicyManager#setUninstallBlocked(ComponentName, String, boolean)}.
+     *
+     * @param packageName Name of the package being uninstalled.
+     * @param user UserHandle who's ability to uninstall a package is being checked.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public boolean canUserUninstall(@NonNull String packageName, @NonNull UserHandle user){
+        throw new UnsupportedOperationException(
+                "canUserUninstall not implemented in subclass");
+    }
+
+    /**
+     * See {@link android.provider.Settings.Global#SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @NonNull
+    public boolean shouldShowNewAppInstalledNotification() {
+        throw new UnsupportedOperationException(
+                "isShowNewAppInstalledNotificationEnabled not implemented in subclass");
+    }
 }
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 6453ab0..4ade8a8 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -79,6 +79,20 @@
     public static final int FLAG_USE_APP_ZYGOTE = 0x0008;
 
     /**
+     * Bit in {@link #flags}: If set, and this is an {@link android.R.attr#isolatedProcess}
+     * service, the service is allowed to be bound in a shared isolated process with other
+     * isolated services. Note that these other isolated services can also belong to other
+     * apps from different vendors.
+     *
+     * Shared isolated processes are created when using the
+     * {@link android.content.Context#BIND_SHARED_ISOLATED_PROCESS) during service binding.
+     *
+     * Note that when this flag is used, the {@link android.R.attr#process} attribute is
+     * ignored when the process is bound into a shared isolated process by a client.
+     */
+    public static final int FLAG_ALLOW_SHARED_ISOLATED_PROCESS = 0x0010;
+
+    /**
      * Bit in {@link #flags} indicating if the service is visible to ephemeral applications.
      * @hide
      */
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index f47c1e0..96aa624 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -46,6 +46,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.TestApi;
+import android.app.GrammaticalInflectionManager;
 import android.app.WindowConfiguration;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.LocaleProto;
@@ -141,6 +142,44 @@
     @UnsupportedAppUsage
     public boolean userSetLocale;
 
+    /**
+     * Current user preference for the grammatical gender.
+     */
+    @GrammaticalGender
+    private int mGrammaticalGender;
+
+    /** @hide */
+    @IntDef(prefix = { "GRAMMATICAL_GENDER_" }, value = {
+            GRAMMATICAL_GENDER_NOT_SPECIFIED,
+            GRAMMATICAL_GENDER_NEUTRAL,
+            GRAMMATICAL_GENDER_FEMININE,
+            GRAMMATICAL_GENDER_MASCULINE,
+    })
+    public @interface GrammaticalGender {}
+
+    /**
+     * Constant for grammatical gender: to indicate the user has not specified the terms
+     * of address for the application.
+     */
+    public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+     * preferred in an application is neuter.
+     */
+    public static final int GRAMMATICAL_GENDER_NEUTRAL = 2;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+         * preferred in an application is feminine.
+     */
+    public static final int GRAMMATICAL_GENDER_FEMININE = 3;
+
+    /**
+     * Constant for grammatical gender: to indicate the terms of address the user
+     * preferred in an application is masculine.
+     */
+    public static final int GRAMMATICAL_GENDER_MASCULINE = 4;
 
     /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
     public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
@@ -1024,6 +1063,7 @@
         }
         o.fixUpLocaleList();
         mLocaleList = o.mLocaleList;
+        mGrammaticalGender = o.mGrammaticalGender;
         userSetLocale = o.userSetLocale;
         touchscreen = o.touchscreen;
         keyboard = o.keyboard;
@@ -1510,6 +1550,7 @@
         seq = 0;
         windowConfiguration.setToDefaults();
         fontWeightAdjustment = FONT_WEIGHT_ADJUSTMENT_UNDEFINED;
+        mGrammaticalGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
     }
 
     /**
@@ -1712,6 +1753,10 @@
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
             fontWeightAdjustment = delta.fontWeightAdjustment;
         }
+        if (delta.mGrammaticalGender != mGrammaticalGender) {
+            changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+            mGrammaticalGender = delta.mGrammaticalGender;
+        }
 
         return changed;
     }
@@ -1929,6 +1974,10 @@
                 && fontWeightAdjustment != delta.fontWeightAdjustment) {
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
         }
+
+        if (!publicOnly&& mGrammaticalGender != delta.mGrammaticalGender) {
+            changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
+        }
         return changed;
     }
 
@@ -2023,6 +2072,7 @@
         dest.writeInt(assetsSeq);
         dest.writeInt(seq);
         dest.writeInt(fontWeightAdjustment);
+        dest.writeInt(mGrammaticalGender);
     }
 
     public void readFromParcel(Parcel source) {
@@ -2055,6 +2105,7 @@
         assetsSeq = source.readInt();
         seq = source.readInt();
         fontWeightAdjustment = source.readInt();
+        mGrammaticalGender = source.readInt();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<Configuration> CREATOR
@@ -2155,6 +2206,8 @@
         if (n != 0) return n;
         n = this.fontWeightAdjustment - that.fontWeightAdjustment;
         if (n != 0) return n;
+        n = this.mGrammaticalGender - that.mGrammaticalGender;
+        if (n != 0) return n;
 
         // if (n != 0) return n;
         return n;
@@ -2196,10 +2249,37 @@
         result = 31 * result + densityDpi;
         result = 31 * result + assetsSeq;
         result = 31 * result + fontWeightAdjustment;
+        result = 31 * result + mGrammaticalGender;
         return result;
     }
 
     /**
+     * Returns the user preference for the grammatical gender. Will be
+     * {@link #GRAMMATICAL_GENDER_NOT_SPECIFIED} or
+     * {@link #GRAMMATICAL_GENDER_NEUTRAL} or
+     * {@link #GRAMMATICAL_GENDER_FEMININE} or
+     * {@link #GRAMMATICAL_GENDER_MASCULINE}.
+     *
+     * @return The preferred grammatical gender.
+     */
+    @GrammaticalGender
+    public int getGrammaticalGender() {
+        return mGrammaticalGender;
+    }
+
+    /**
+     * Sets the user preference for the grammatical gender. This is only for frameworks to easily
+     * override the gender in the configuration. To update the grammatical gender for an application
+     * use {@link GrammaticalInflectionManager#setRequestedApplicationGrammaticalGender(int)}.
+     *
+     * @param grammaticalGender The preferred grammatical gender.
+     * @hide
+     */
+    public void setGrammaticalGender(@GrammaticalGender int grammaticalGender) {
+        mGrammaticalGender = grammaticalGender;
+    }
+
+    /**
      * Get the locale list. This is the preferred way for getting the locales (instead of using
      * the direct accessor to {@link #locale}, which would only provide the primary locale).
      *
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index a5a1fa689..b097bc0 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -83,7 +83,6 @@
      * @return the resources provider instance for the {@code overlayInfo}
      * @throws IOException when the files can't be loaded.
      * @see OverlayManager#getOverlayInfosForTarget(String) to get the list of overlay info.
-     * @hide
      */
     @SuppressLint("WrongConstant") // TODO(b/238713267): ApkAssets blocks PROPERTY_LOADER
     @NonNull
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index 23bb22f..d4daf364 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -276,9 +276,22 @@
      *
      * @hide
      */
-    public static boolean isServiceEnabled() {
+    public static boolean isServiceEnabled(Context context) {
+        if (context == null) {
+	    return false;
+        }
+        CredentialManager credentialManager =
+                (CredentialManager) context.getSystemService(Context.CREDENTIAL_SERVICE);
+        if (credentialManager != null) {
+            return credentialManager.isServiceEnabled();
+        }
+        return false;
+    }
+
+    private boolean isServiceEnabled() {
         return DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER, true);
+                DeviceConfig.NAMESPACE_CREDENTIAL, DEVICE_CONFIG_ENABLE_CREDENTIAL_MANAGER,
+                true);
     }
 
     private static class GetCredentialTransport extends IGetCredentialCallback.Stub {
diff --git a/core/java/android/credentials/GetCredentialRequest.java b/core/java/android/credentials/GetCredentialRequest.java
index 96a0ce1..85b4468 100644
--- a/core/java/android/credentials/GetCredentialRequest.java
+++ b/core/java/android/credentials/GetCredentialRequest.java
@@ -19,6 +19,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.NonNull;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -41,6 +42,12 @@
     private final List<GetCredentialOption> mGetCredentialOptions;
 
     /**
+     * The top request level data.
+     */
+    @NonNull
+    private final Bundle mData;
+
+    /**
      * Returns the list of credential options to be requested.
      */
     @NonNull
@@ -48,9 +55,18 @@
         return mGetCredentialOptions;
     }
 
+    /**
+     * Returns the top request level data.
+     */
+    @NonNull
+    public Bundle getData() {
+        return mData;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedList(mGetCredentialOptions, flags);
+        dest.writeBundle(mData);
     }
 
     @Override
@@ -60,10 +76,13 @@
 
     @Override
     public String toString() {
-        return "GetCredentialRequest {getCredentialOption=" + mGetCredentialOptions + "}";
+        return "GetCredentialRequest {getCredentialOption=" + mGetCredentialOptions
+                + ", data=" + mData
+                + "}";
     }
 
-    private GetCredentialRequest(@NonNull List<GetCredentialOption> getCredentialOptions) {
+    private GetCredentialRequest(@NonNull List<GetCredentialOption> getCredentialOptions,
+            @NonNull Bundle data) {
         Preconditions.checkCollectionNotEmpty(
                 getCredentialOptions,
                 /*valueName=*/ "getCredentialOptions");
@@ -71,6 +90,8 @@
                 getCredentialOptions,
                 /*valueName=*/ "getCredentialOptions");
         mGetCredentialOptions = getCredentialOptions;
+        mData = requireNonNull(data,
+                "data must not be null");
     }
 
     private GetCredentialRequest(@NonNull Parcel in) {
@@ -78,30 +99,47 @@
         in.readTypedList(getCredentialOptions, GetCredentialOption.CREATOR);
         mGetCredentialOptions = getCredentialOptions;
         AnnotationValidations.validate(NonNull.class, null, mGetCredentialOptions);
+
+
+        Bundle data = in.readBundle();
+        mData = data;
+        AnnotationValidations.validate(NonNull.class, null, mData);
     }
 
     public static final @NonNull Parcelable.Creator<GetCredentialRequest> CREATOR =
             new Parcelable.Creator<GetCredentialRequest>() {
-        @Override
-        public GetCredentialRequest[] newArray(int size) {
-            return new GetCredentialRequest[size];
-        }
+                @Override
+                public GetCredentialRequest[] newArray(int size) {
+                    return new GetCredentialRequest[size];
+                }
 
-        @Override
-        public GetCredentialRequest createFromParcel(@NonNull Parcel in) {
-            return new GetCredentialRequest(in);
-        }
-    };
+                @Override
+                public GetCredentialRequest createFromParcel(@NonNull Parcel in) {
+                    return new GetCredentialRequest(in);
+                }
+            };
 
     /** A builder for {@link GetCredentialRequest}. */
     public static final class Builder {
 
-        private @NonNull List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+        @NonNull
+        private List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+
+        @NonNull
+        private final Bundle mData;
+
+        /**
+         * @param data the top request level data
+         */
+        public Builder(@NonNull Bundle data) {
+            mData = requireNonNull(data, "data must not be null");
+        }
 
         /**
          * Adds a specific type of {@link GetCredentialOption}.
          */
-        public @NonNull Builder addGetCredentialOption(
+        @NonNull
+        public Builder addGetCredentialOption(
                 @NonNull GetCredentialOption getCredentialOption) {
             mGetCredentialOptions.add(requireNonNull(
                     getCredentialOption, "getCredentialOption must not be null"));
@@ -111,7 +149,8 @@
         /**
          * Sets the list of {@link GetCredentialOption}.
          */
-        public @NonNull Builder setGetCredentialOptions(
+        @NonNull
+        public Builder setGetCredentialOptions(
                 @NonNull List<GetCredentialOption> getCredentialOptions) {
             Preconditions.checkCollectionElementsNotNull(
                     getCredentialOptions,
@@ -125,14 +164,15 @@
          *
          * @throws IllegalArgumentException If getCredentialOptions is empty.
          */
-        public @NonNull GetCredentialRequest build() {
+        @NonNull
+        public GetCredentialRequest build() {
             Preconditions.checkCollectionNotEmpty(
                     mGetCredentialOptions,
                     /*valueName=*/ "getCredentialOptions");
             Preconditions.checkCollectionElementsNotNull(
                     mGetCredentialOptions,
                     /*valueName=*/ "getCredentialOptions");
-            return new GetCredentialRequest(mGetCredentialOptions);
+            return new GetCredentialRequest(mGetCredentialOptions, mData);
         }
     }
 }
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index c3937b6..5eaf2a0 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -44,6 +44,8 @@
     public static final @NonNull String EXTRA_REQUEST_INFO =
             "android.credentials.ui.extra.REQUEST_INFO";
 
+    /** Type value for any request that does not require UI. */
+    public static final @NonNull String TYPE_UNDEFINED = "android.credentials.ui.TYPE_UNDEFINED";
     /** Type value for an executeGetCredential request. */
     public static final @NonNull String TYPE_GET = "android.credentials.ui.TYPE_GET";
     /** Type value for an executeCreateCredential request. */
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 0a14574..b8b1eaa 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -521,9 +521,7 @@
     public static final int DATASPACE_BT2020_HLG = 168165376;
 
     /**
-     * ITU-R Recommendation 2020 (BT.2020)
-     *
-     * Ultra High-definition television.
+     * Perceptual Quantizer encoding.
      *
      * <p>Composed of the following -</p>
      * <pre>
diff --git a/core/java/android/hardware/camera2/CameraCaptureSession.java b/core/java/android/hardware/camera2/CameraCaptureSession.java
index 8e4a108..ff6e897 100644
--- a/core/java/android/hardware/camera2/CameraCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraCaptureSession.java
@@ -1245,10 +1245,28 @@
          * the start of exposure, particularly when autoexposure is changing exposure duration
          * between frames.</p>
          *
-         * <p>This timestamp may not match {@link CaptureResult#SENSOR_TIMESTAMP the result
-         * timestamp field}. It will, however, match the timestamp of buffers sent to the
-         * output surfaces with {@link OutputConfiguration#TIMESTAMP_BASE_READOUT_SENSOR}
-         * timestamp base.</p>
+         * <p>The timestamps match the timestamps of the output surfaces with readout timestamp
+         * enabled (via {@link OutputConfiguration#useReadoutTimestamp}) if:</p>
+         * <ul>
+         * <li> Timestamp base is {@link OutputConfiguration#TIMESTAMP_BASE_DEFAULT} and the
+         * output
+         *   <ul>
+         *   <li> is not a SurfaceView surface, and </li>
+         *   <li> is not a MediaRecoder, MediaCodec, or ImageReader surface with {@link
+         *   android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usage flag or the device's {@link
+         *   CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is {@code UNKNOWN}</li>
+         *   </ul>
+         * </li>
+         * <li> Timestamp base is {@link OutputConfiguration#TIMESTAMP_BASE_SENSOR},</li>
+         * <li> Timestamp base is {@link OutputConfiguration#TIMESTAMP_BASE_MONOTONIC} and the
+         *  device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is {@code
+         *  UNKNOWN},</li>
+         * <li> Timestamp base is {@link OutputConfiguration#TIMESTAMP_BASE_REALTIME} and the
+         *  device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is {@code REALTIME}
+         * </li>
+         * </ul>
+         * <p>Otherwise, the timestamps won't match the timestamp of the output surfaces. See
+         * the possible parameters for {@link OutputConfiguration#setTimestampBase} for details.</p>
          *
          * <p>This callback will be called only if {@link
          * CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is
@@ -1261,8 +1279,6 @@
          *                  the timestamp at the input image's start of readout for a
          *                  reprocess request, in nanoseconds.
          * @param frameNumber the frame number for this capture
-         *
-         * @hide
          */
         public void onReadoutStarted(@NonNull CameraCaptureSession session,
                 @NonNull CaptureRequest request, long timestamp, long frameNumber) {
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a6f7e94..11b80cc 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -3756,6 +3756,7 @@
      *   <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD VIDEO_RECORD}</li>
      *   <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL PREVIEW_VIDEO_STILL}</li>
      *   <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL VIDEO_CALL}</li>
+     *   <li>{@link #SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW CROPPED_RAW}</li>
      * </ul>
      *
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
@@ -3765,6 +3766,7 @@
      * @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD
      * @see #SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL
      * @see #SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL
+     * @see #SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW
      */
     @PublicKey
     @NonNull
@@ -4588,22 +4590,26 @@
 
     /**
      * <p>Whether or not the camera device supports readout timestamp and
-     * onReadoutStarted callback.</p>
-     * <p>If this tag is HARDWARE, the camera device calls onReadoutStarted in addition to the
-     * onCaptureStarted callback for each capture. The timestamp passed into the callback
-     * is the start of camera image readout rather than the start of the exposure. In
-     * addition, the application can configure an
-     * {@link android.hardware.camera2.params.OutputConfiguration } with
-     * TIMESTAMP_BASE_READOUT_SENSOR timestamp base, in which case, the timestamp of the
-     * output surface matches the timestamp from the corresponding onReadoutStarted callback.</p>
+     * {@code onReadoutStarted} callback.</p>
+     * <p>If this tag is {@code HARDWARE}, the camera device calls
+     * {@link CameraCaptureSession.CaptureCallback#onReadoutStarted } in addition to the
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted } callback for each capture.
+     * The timestamp passed into the callback is the start of camera image readout rather than
+     * the start of the exposure. The timestamp source of
+     * {@link CameraCaptureSession.CaptureCallback#onReadoutStarted } is the same as that of
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted }.</p>
+     * <p>In addition, the application can switch an output surface's timestamp from start of
+     * exposure to start of readout by calling
+     * {@link android.hardware.camera2.params.OutputConfiguration#useReadoutTimestamp }.</p>
      * <p>The readout timestamp is beneficial for video recording, because the encoder favors
      * uniform timestamps, and the readout timestamps better reflect the cadence camera sensors
      * output data.</p>
-     * <p>If this tag is HARDWARE, the camera device produces the start-of-exposure and
-     * start-of-readout together. As a result, the onReadoutStarted is called right after
-     * onCaptureStarted. The difference in start-of-readout and start-of-exposure is the sensor
-     * exposure time, plus certain constant offset. The offset is usually due to camera sensor
-     * level crop, and it remains constant for a given camera sensor mode.</p>
+     * <p>Note that the camera device produces the start-of-exposure and start-of-readout callbacks
+     * together. As a result, the {@link CameraCaptureSession.CaptureCallback#onReadoutStarted }
+     * is called right after {@link CameraCaptureSession.CaptureCallback#onCaptureStarted }. The
+     * difference in start-of-readout and start-of-exposure is the sensor exposure time, plus
+     * certain constant offset. The offset is usually due to camera sensor level crop, and it is
+     * generally constant over time for the same set of output resolutions and capture settings.</p>
      * <p><b>Possible values:</b></p>
      * <ul>
      *   <li>{@link #SENSOR_READOUT_TIMESTAMP_NOT_SUPPORTED NOT_SUPPORTED}</li>
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index bf2e563..577c8a3 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -900,13 +900,27 @@
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td colspan="3" id="rb"></td> <td>Preview with video recording or in-app video processing</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td colspan="3" id="rb"></td> <td>Preview with in-application image processing</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code VIDEO_CALL}</td> <td colspan="3" id="rb"></td> <td>Preview with video call</td> </tr>
-     * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>Multi-purpose stream with JPEG or YUV still capture</td> </tr>
+     * <tr> <td>{@code YUV / PRIV}</td><td id="rb">{@code s1440p}</td><td id="rb">{@code PREVIEW_VIDEO_STILL}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>MultI-purpose stream with JPEG or YUV still capture</td> </tr>
      * <tr> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td colspan="3" id="rb"></td> <td>YUV and JPEG concurrent still image capture (for testing)</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / PRIV}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code VIDEO_RECORD}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code RECORD}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, video record and JPEG or YUV video snapshot</td> </tr>
      * <tr> <td>{@code PRIV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>Preview, in-application image processing, and JPEG or YUV still image capture</td> </tr>
      * </table><br>
      * </p>
      *
+     * <p>Devices that include the {@link CameraMetadata#SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW}
+     * stream use-case in {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES},
+     * support the additional stream combinations below:
+     *
+     * <table>
+     * <tr><th colspan="10">STREAM_USE_CASE_CROPPED_RAW capability additional guaranteed configurations</th></tr>
+     * <tr><th colspan="3" id="rb">Target 1</th><th colspan="3" id="rb">Target 2</th><th colspan="3" id="rb">Target 3</th> <th rowspan="2">Sample use case(s)</th> </tr>
+     * <tr><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th><th>Type</th><th id="rb">Max size</th><th>Usecase</th> </tr>
+     * <tr> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td colspan="3" id="rb"></td> <td colspan="3" id="rb"></td> <td>Cropped RAW still capture without preview</td> </tr>
+     * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td colspan="3" id="rb"></td> <td>Preview with cropped RAW still capture</td> </tr>
+     * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code YUV / JPEG}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code STILL_CAPTURE}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td>Preview with YUV / JPEG and cropped RAW still capture</td> </tr>
+     * <tr> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code PREVIEW}</td> <td>{@code PRIV / YUV}</td><td id="rb">{@code PREVIEW}</td><td id="rb">{@code VIDEO_RECORD / PREVIEW}</td> <td>{@code RAW}</td><td id="rb">{@code MAXIMUM}</td><td id="rb">{@code CROPPED_RAW}</td> <td>Video recording with preview and cropped RAW still capture</td> </tr>
+     *
+     *
      *<p> For devices where {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES}
      * includes {@link CameraMetadata#CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION},
      * the following stream combinations are guaranteed,
diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
index bbdb626..cca900a 100644
--- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java
@@ -574,6 +574,147 @@
     }
 
     /**
+     * Checks for postview support of still capture.
+     *
+     * <p>A postview is a preview version of the still capture that is available before the final
+     * image. For example, it can be used as a temporary placeholder for the requested capture
+     * while the final image is being processed. The supported sizes for a still capture's postview
+     * can be retrieved using
+     * {@link CameraExtensionCharacteristics#getPostviewSupportedSizes(int, Size, int)}.
+     * The formats of the still capture and postview should be equivalent upon capture request.</p>
+     *
+     * @param extension the extension type
+     * @return {@code true} in case postview is supported, {@code false} otherwise
+     *
+     * @throws IllegalArgumentException in case the extension type is not a
+     * supported device-specific extension
+     */
+    public boolean isPostviewAvailable(@Extension int extension) {
+        long clientId = registerClient(mContext);
+        if (clientId < 0) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mChars)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            if (areAdvancedExtensionsSupported()) {
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId);
+                return extender.isPostviewAvailable();
+            } else {
+                Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+                        initializeExtension(extension);
+                extenders.second.init(mCameraId, mChars.getNativeMetadata());
+                return extenders.second.isPostviewAvailable();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension for postview availability! Extension "
+                    + "service does not respond!");
+        } finally {
+            unregisterClient(clientId);
+        }
+
+        return false;
+    }
+
+    /**
+     * Get a list of the postview sizes supported for a still capture, using its
+     * capture size {@code captureSize}, to use as an output for the postview request.
+     *
+     * <p>Available postview sizes will always be either equal to or less than the still
+     * capture size. When choosing the most applicable postview size for a usecase, it should
+     * be noted that lower resolution postviews will generally be available more quickly
+     * than larger resolution postviews. For example, when choosing a size for an optimized
+     * postview that will be displayed as a placeholder while the final image is processed,
+     * the resolution closest to the preview size may be most suitable.</p>
+     *
+     * <p>Note that device-specific extensions are allowed to support only a subset
+     * of the camera resolutions advertised by
+     * {@link StreamConfigurationMap#getOutputSizes}.</p>
+     *
+     * @param extension the extension type
+     * @param captureSize size of the still capture for which the postview is requested
+     * @param format device-specific extension output format of the still capture and
+     * postview
+     * @return non-modifiable list of available sizes or an empty list if the format and
+     * size is not supported.
+     * @throws IllegalArgumentException in case of unsupported extension or if postview
+     * feature is not supported by extension.
+     */
+    @NonNull
+    public List<Size> getPostviewSupportedSizes(@Extension int extension,
+            @NonNull Size captureSize, int format) {
+
+        long clientId = registerClient(mContext);
+        if (clientId < 0) {
+            throw new IllegalArgumentException("Unsupported extensions");
+        }
+
+        try {
+            if (!isExtensionSupported(mCameraId, extension, mChars)) {
+                throw new IllegalArgumentException("Unsupported extension");
+            }
+
+            android.hardware.camera2.extension.Size sz =
+                    new android.hardware.camera2.extension.Size();
+            sz.width = captureSize.getWidth();
+            sz.height = captureSize.getHeight();
+
+            StreamConfigurationMap streamMap = mChars.get(
+                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+            if (areAdvancedExtensionsSupported()) {
+                switch(format) {
+                    case ImageFormat.YUV_420_888:
+                    case ImageFormat.JPEG:
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unsupported format: " + format);
+                }
+                IAdvancedExtenderImpl extender = initializeAdvancedExtension(extension);
+                extender.init(mCameraId);
+                return generateSupportedSizes(extender.getSupportedPostviewResolutions(
+                    sz), format, streamMap);
+            } else {
+                Pair<IPreviewExtenderImpl, IImageCaptureExtenderImpl> extenders =
+                        initializeExtension(extension);
+                extenders.second.init(mCameraId, mChars.getNativeMetadata());
+                if ((extenders.second.getCaptureProcessor() == null) ||
+                        !isPostviewAvailable(extension)) {
+                    // Extensions that don't implement any capture processor
+                    // and have processing occur in the HAL don't currently support the
+                    // postview feature
+                    throw new IllegalArgumentException("Extension does not support "
+                            + "postview feature");
+                }
+
+                if (format == ImageFormat.YUV_420_888) {
+                    return generateSupportedSizes(
+                            extenders.second.getSupportedPostviewResolutions(sz),
+                            format, streamMap);
+                } else if (format == ImageFormat.JPEG) {
+                    // The framework will perform the additional encoding pass on the
+                    // processed YUV_420 buffers.
+                    return generateJpegSupportedSizes(
+                            extenders.second.getSupportedPostviewResolutions(sz),
+                                    streamMap);
+                } else {
+                    throw new IllegalArgumentException("Unsupported format: " + format);
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to query the extension postview supported sizes! Extension "
+                    + "service does not respond!");
+            return Collections.emptyList();
+        } finally {
+            unregisterClient(clientId);
+        }
+    }
+
+    /**
      * Get a list of sizes compatible with {@code klass} to use as an output for the
      * repeating request
      * {@link CameraExtensionSession#setRepeatingRequest}.
diff --git a/core/java/android/hardware/camera2/CameraExtensionSession.java b/core/java/android/hardware/camera2/CameraExtensionSession.java
index b0fafea..1542d61 100644
--- a/core/java/android/hardware/camera2/CameraExtensionSession.java
+++ b/core/java/android/hardware/camera2/CameraExtensionSession.java
@@ -311,10 +311,15 @@
      * The rest of the settings included in the request will be entirely overridden by
      * the device-specific extension. </p>
      *
-     * <p>The {@link CaptureRequest.Builder#addTarget} supports only one
+     * <p> If {@link CameraExtensionCharacteristics#isPostviewAvailable} returns
+     * false, the {@link CaptureRequest.Builder#addTarget} will support only one
      * ImageFormat.YUV_420_888 or ImageFormat.JPEG target surface. {@link CaptureRequest}
-     * arguments that include further targets will cause
-     * IllegalArgumentException to be thrown. </p>
+     * arguments that include further targets will cause IllegalArgumentException to be thrown.
+     * If postview is available, {@link CaptureRequest.Builder#addTarget} will support up to two
+     * ImageFormat.YUV_420_888 or ImageFormat.JPEG target surfaces for the still capture and
+     * postview. IllegalArgumentException will be thrown if a postview target is added without
+     * a still capture target, if more than two target surfaces are added, or if the surface
+     * formats for postview and capture are not equivalent.
      *
      * <p>Starting with Android {@link android.os.Build.VERSION_CODES#TIRAMISU} single capture
      * requests will also support the preview {@link android.graphics.ImageFormat#PRIVATE} target
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index ce191e8..ed1e9e5 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -114,7 +114,7 @@
      */
     @ChangeId
     @Overridable
-    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
+    @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.BASE)
     @TestApi
     public static final long OVERRIDE_FRONT_CAMERA_APP_COMPAT = 250678880L;
 
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index b2428b1..788302b 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1489,6 +1489,31 @@
     public static final int SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL = 0x5;
 
     /**
+     * <p>Cropped RAW stream when the client chooses to crop the field of view.</p>
+     * <p>Certain types of image sensors can run in binned modes in order to improve signal to
+     * noise ratio while capturing frames. However, at certain zoom levels and / or when
+     * other scene conditions are deemed fit, the camera sub-system may choose to un-bin and
+     * remosaic the sensor's output. This results in a RAW frame which is cropped in field
+     * of view and yet has the same number of pixels as full field of view RAW, thereby
+     * improving image detail.</p>
+     * <p>The resultant field of view of the RAW stream will be greater than or equal to
+     * croppable non-RAW streams. The effective crop region for this RAW stream will be
+     * reflected in the CaptureResult key {@link CaptureResult#SCALER_RAW_CROP_REGION android.scaler.rawCropRegion}.</p>
+     * <p>If this stream use case is set on a non-RAW stream, i.e. not one of :</p>
+     * <ul>
+     * <li>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</li>
+     * <li>{@link android.graphics.ImageFormat#RAW10 RAW10}</li>
+     * <li>{@link android.graphics.ImageFormat#RAW12 RAW12}</li>
+     * </ul>
+     * <p>session configuration is not guaranteed to succeed.</p>
+     * <p>This stream use case may not be supported on some devices.</p>
+     *
+     * @see CaptureResult#SCALER_RAW_CROP_REGION
+     * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
+     */
+    public static final int SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW = 0x6;
+
+    /**
      * <p>Vendor defined use cases. These depend on the vendor implementation.</p>
      * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
      * @hide
@@ -1700,9 +1725,8 @@
 
     /**
      * <p>This camera device supports the onReadoutStarted callback as well as outputting
-     * readout timestamp for streams with TIMESTAMP_BASE_READOUT_SENSOR timestamp base. The
-     * readout timestamp is generated by the camera hardware and it has the same accuracy
-     * and timing characteristics of the start-of-exposure time.</p>
+     * readout timestamps. The 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
      */
     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 43bfdcc..3d83009 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -3086,9 +3086,9 @@
      * <p>Output streams use this rectangle to produce their output, cropping to a smaller region
      * if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
      * match the output's configured resolution.</p>
-     * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
-     * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
-     * croppable. The crop region will be ignored by raw streams.</p>
+     * <p>The crop region is usually applied after the RAW to other color space (e.g. YUV)
+     * conversion. As a result RAW streams are not croppable unless supported by the
+     * camera device. See {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES android.scaler.availableStreamUseCases}#CROPPED_RAW for details.</p>
      * <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
      * final pixel area of the stream.</p>
      * <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
@@ -3183,6 +3183,7 @@
      * @see CaptureRequest#CONTROL_ZOOM_RATIO
      * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+     * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
      * @see CameraCharacteristics#SCALER_CROPPING_TYPE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index fb52cc6..dad7d3e 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -3748,9 +3748,9 @@
      * <p>Output streams use this rectangle to produce their output, cropping to a smaller region
      * if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
      * match the output's configured resolution.</p>
-     * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
-     * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
-     * croppable. The crop region will be ignored by raw streams.</p>
+     * <p>The crop region is usually applied after the RAW to other color space (e.g. YUV)
+     * conversion. As a result RAW streams are not croppable unless supported by the
+     * camera device. See {@link CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES android.scaler.availableStreamUseCases}#CROPPED_RAW for details.</p>
      * <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
      * final pixel area of the stream.</p>
      * <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
@@ -3845,6 +3845,7 @@
      * @see CaptureRequest#CONTROL_ZOOM_RATIO
      * @see CaptureRequest#DISTORTION_CORRECTION_MODE
      * @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+     * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES
      * @see CameraCharacteristics#SCALER_CROPPING_TYPE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
      * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE_MAXIMUM_RESOLUTION
@@ -3952,6 +3953,60 @@
             new Key<Integer>("android.scaler.rotateAndCrop", int.class);
 
     /**
+     * <p>The region of the sensor that corresponds to the RAW read out for this
+     * capture when the stream use case of a RAW stream is set to CROPPED_RAW.</p>
+     * <p>The coordinate system follows that of {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}.</p>
+     * <p>This CaptureResult key will be set when the corresponding CaptureRequest has a RAW target
+     * with stream use case set to
+     * {@link android.hardware.camera2.CameraMetadata#SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW },
+     * otherwise it will be {@code null}.
+     * The value of this key specifies the region of the sensor used for the RAW capture and can
+     * be used to calculate the corresponding field of view of RAW streams.
+     * This field of view will always be &gt;= field of view for (processed) non-RAW streams for the
+     * capture. Note: The region specified may not necessarily be centered.</p>
+     * <p>For example: Assume a camera device has a pre correction active array size of
+     * {@code {0, 0, 1500, 2000}}. If the RAW_CROP_REGION is {@code {500, 375, 1500, 1125}}, that
+     * corresponds to a centered crop of 1/4th of the full field of view RAW stream.</p>
+     * <p>The metadata keys which describe properties of RAW frames:</p>
+     * <ul>
+     * <li>{@link CaptureResult#STATISTICS_HOT_PIXEL_MAP android.statistics.hotPixelMap}</li>
+     * <li>{@link CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP android.statistics.lensShadingCorrectionMap}</li>
+     * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}</li>
+     * <li>{@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}</li>
+     * <li>{@link CameraCharacteristics#LENS_DISTORTION android.lens.distortion}</li>
+     * <li>{@link CameraCharacteristics#LENS_INTRINSIC_CALIBRATION android.lens.intrinsicCalibration}</li>
+     * </ul>
+     * <p>should be interpreted in the effective after raw crop field-of-view coordinate system.
+     * In this coordinate system,
+     * {preCorrectionActiveArraySize.left, preCorrectionActiveArraySize.top} corresponds to the
+     * the top left corner of the cropped RAW frame and
+     * {preCorrectionActiveArraySize.right, preCorrectionActiveArraySize.bottom} corresponds to
+     * the bottom right corner. Client applications must use the values of the keys
+     * in the CaptureResult metadata if present.</p>
+     * <p>Crop regions (android.scaler.CropRegion), AE/AWB/AF regions and face coordinates still
+     * use the {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} coordinate system as usual.</p>
+     * <p><b>Units</b>: Pixel coordinates relative to
+     * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
+     * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
+     * capability and mode</p>
+     * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+     *
+     * @see CameraCharacteristics#LENS_DISTORTION
+     * @see CameraCharacteristics#LENS_INTRINSIC_CALIBRATION
+     * @see CameraCharacteristics#LENS_POSE_ROTATION
+     * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+     * @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
+     * @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
+     * @see CaptureResult#STATISTICS_HOT_PIXEL_MAP
+     * @see CaptureResult#STATISTICS_LENS_SHADING_CORRECTION_MAP
+     */
+    @PublicKey
+    @NonNull
+    public static final Key<android.graphics.Rect> SCALER_RAW_CROP_REGION =
+            new Key<android.graphics.Rect>("android.scaler.rawCropRegion", android.graphics.Rect.class);
+
+    /**
      * <p>Duration each pixel is exposed to
      * light.</p>
      * <p>If the sensor can't expose this exact duration, it will shorten the
@@ -5643,4 +5698,5 @@
 
 
 
+
 }
diff --git a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
index fa2cbe7..c9b7ea1 100644
--- a/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IAdvancedExtenderImpl.aidl
@@ -30,8 +30,10 @@
             int format);
     @nullable List<SizeList> getSupportedPreviewOutputResolutions(in String cameraId);
     @nullable List<SizeList> getSupportedCaptureOutputResolutions(in String cameraId);
+    @nullable List<SizeList> getSupportedPostviewResolutions(in Size captureSize);
     ISessionProcessorImpl getSessionProcessor();
     CameraMetadataNative getAvailableCaptureRequestKeys(in String cameraId);
     CameraMetadataNative getAvailableCaptureResultKeys(in String cameraId);
     boolean isCaptureProcessProgressAvailable();
+    boolean isPostviewAvailable();
 }
diff --git a/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl
index 3c5f5ff..f83d407 100644
--- a/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ICaptureProcessorImpl.aidl
@@ -24,7 +24,9 @@
 interface ICaptureProcessorImpl
 {
     void onOutputSurface(in Surface surface, int imageFormat);
-    void onResolutionUpdate(in Size size);
+    void onPostviewOutputSurface(in Surface surface);
+    void onResolutionUpdate(in Size size, in Size postviewSize);
     void onImageFormatUpdate(int imageFormat);
-    void process(in List<CaptureBundle> capturelist, in IProcessResultImpl resultCallback);
+    void process(in List<CaptureBundle> capturelist,
+            in IProcessResultImpl resultCallback, boolean isPostviewRequested);
 }
diff --git a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
index 360f809..754f8f6 100644
--- a/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/IImageCaptureExtenderImpl.aidl
@@ -40,9 +40,11 @@
     @nullable List<CaptureStageImpl> getCaptureStages();
     int getMaxCaptureStage();
     @nullable List<SizeList> getSupportedResolutions();
+    @nullable List<SizeList> getSupportedPostviewResolutions(in Size captureSize);
     LatencyRange getEstimatedCaptureLatencyRange(in Size outputSize);
     CameraMetadataNative getAvailableCaptureRequestKeys();
     CameraMetadataNative getAvailableCaptureResultKeys();
     boolean isCaptureProcessProgressAvailable();
     @nullable LatencyPair getRealtimeCaptureLatency();
+    boolean isPostviewAvailable();
 }
diff --git a/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl b/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl
index 70b096f..ab650c7 100644
--- a/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl
+++ b/core/java/android/hardware/camera2/extension/IOutputSurfaceConfiguration.aidl
@@ -23,4 +23,5 @@
     OutputSurface getPreviewOutputSurface();
     OutputSurface getImageCaptureOutputSurface();
     OutputSurface getImageAnalysisOutputSurface();
+    OutputSurface getPostviewOutputSurface();
 }
diff --git a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
index e0f1b64..2af1df9 100644
--- a/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
+++ b/core/java/android/hardware/camera2/extension/ISessionProcessorImpl.aidl
@@ -27,13 +27,13 @@
 interface ISessionProcessorImpl
 {
     CameraSessionConfig initSession(in String cameraId, in OutputSurface previewSurface,
-            in OutputSurface imageCaptureSurface);
+            in OutputSurface imageCaptureSurface, in OutputSurface postviewSurface);
     void deInitSession();
     void onCaptureSessionStart(IRequestProcessorImpl requestProcessor);
     void onCaptureSessionEnd();
     int startRepeating(in ICaptureCallback callback);
     void stopRepeating();
-    int startCapture(in ICaptureCallback callback);
+    int startCapture(in ICaptureCallback callback, in boolean isPostviewRequested);
     void setParameters(in CaptureRequest captureRequest);
     int startTrigger(in CaptureRequest captureRequest, in ICaptureCallback callback);
     @nullable LatencyPair getRealtimeCaptureLatency();
diff --git a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
index 42c4411..2d591c3 100644
--- a/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraAdvancedExtensionSessionImpl.java
@@ -69,6 +69,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -93,6 +94,7 @@
 
     private Surface mClientRepeatingRequestSurface;
     private Surface mClientCaptureSurface;
+    private Surface mClientPostviewSurface;
     private CameraCaptureSession mCaptureSession = null;
     private ISessionProcessorImpl mSessionProcessor = null;
     private final InitializeSessionHandler mInitializeHandler;
@@ -173,13 +175,37 @@
             throw new IllegalArgumentException("One or more unsupported output surfaces found!");
         }
 
+        Surface postviewSurface = null;
+        if (burstCaptureSurface != null) {
+            CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                    CameraExtensionUtils.querySurface(burstCaptureSurface);
+            Size burstCaptureSurfaceSize =
+                    new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
+            HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
+            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+                List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
+                        config.getExtension(), burstCaptureSurfaceSize, format);
+                if (supportedSizesPostview != null) {
+                    supportedPostviewSizes.put(format, supportedSizesPostview);
+                }
+            }
+
+            postviewSurface = CameraExtensionUtils.getPostviewSurface(
+                        config.getPostviewOutputConfiguration(), supportedPostviewSizes,
+                        burstCaptureSurfaceInfo.mFormat);
+
+            if ((config.getPostviewOutputConfiguration() != null) && (postviewSurface == null)) {
+                throw new IllegalArgumentException("Unsupported output surface for postview!");
+            }
+        }
+
         IAdvancedExtenderImpl extender = CameraExtensionCharacteristics.initializeAdvancedExtension(
                 config.getExtension());
         extender.init(cameraId);
 
         CameraAdvancedExtensionSessionImpl ret = new CameraAdvancedExtensionSessionImpl(clientId,
                 extender, cameraDevice, repeatingRequestSurface, burstCaptureSurface,
-                config.getStateCallback(), config.getExecutor(), sessionId);
+                postviewSurface, config.getStateCallback(), config.getExecutor(), sessionId);
         ret.initialize();
 
         return ret;
@@ -188,6 +214,7 @@
     private CameraAdvancedExtensionSessionImpl(long extensionClientId,
             @NonNull IAdvancedExtenderImpl extender, @NonNull CameraDevice cameraDevice,
             @Nullable Surface repeatingRequestSurface, @Nullable Surface burstCaptureSurface,
+            @Nullable Surface postviewSurface,
             @NonNull CameraExtensionSession.StateCallback callback, @NonNull Executor executor,
             int sessionId) {
         mExtensionClientId = extensionClientId;
@@ -197,6 +224,7 @@
         mExecutor = executor;
         mClientRepeatingRequestSurface = repeatingRequestSurface;
         mClientCaptureSurface = burstCaptureSurface;
+        mClientPostviewSurface = postviewSurface;
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -216,9 +244,11 @@
 
         OutputSurface previewSurface = initializeParcelable(mClientRepeatingRequestSurface);
         OutputSurface captureSurface = initializeParcelable(mClientCaptureSurface);
+        OutputSurface postviewSurface = initializeParcelable(mClientPostviewSurface);
+
         mSessionProcessor = mAdvancedExtender.getSessionProcessor();
         CameraSessionConfig sessionConfig = mSessionProcessor.initSession(mCameraDevice.getId(),
-                previewSurface, captureSurface);
+                previewSurface, captureSurface, postviewSurface);
         List<CameraOutputConfig> outputConfigs = sessionConfig.outputConfigs;
         ArrayList<OutputConfiguration> outputList = new ArrayList<>();
         for (CameraOutputConfig output : outputConfigs) {
@@ -395,17 +425,15 @@
                 throw new IllegalStateException("Uninitialized component");
             }
 
-            if (request.getTargets().size() != 1) {
-                throw new IllegalArgumentException("Single capture to both preview & still"  +
-                        " capture outputs target is not supported!");
-            }
+            validateCaptureRequestTargets(request);
 
             if ((mClientCaptureSurface != null)  && request.containsTarget(mClientCaptureSurface)) {
                 try {
+                    boolean isPostviewRequested = request.containsTarget(mClientPostviewSurface);
                     mSessionProcessor.setParameters(request);
 
                     seqId = mSessionProcessor.startCapture(new RequestCallbackHandler(request,
-                            executor, listener));
+                            executor, listener), isPostviewRequested);
                 } catch (RemoteException e) {
                     throw new CameraAccessException(CameraAccessException.CAMERA_ERROR, "Failed " +
                             " to submit capture request, extension service failed to respond!");
@@ -427,6 +455,27 @@
         return seqId;
     }
 
+    private void validateCaptureRequestTargets(@NonNull CaptureRequest request) {
+        if ((request.getTargets().size() == 1) &&
+                (!request.containsTarget(mClientRepeatingRequestSurface) ||
+                !request.containsTarget(mClientCaptureSurface))) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+
+        if ((request.getTargets().size() == 2) &&
+                (!request.getTargets().containsAll(Arrays.asList(mClientCaptureSurface,
+                mClientPostviewSurface)))) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+
+        if (request.getTargets().size() > 2) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+    }
+
     @Override
     public void stopRepeating() throws CameraAccessException {
         synchronized (mInterfaceLock) {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
index aee20db..7701125 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionJpegProcessor.java
@@ -52,10 +52,14 @@
     private final ICaptureProcessorImpl mProcessor;
 
     private ImageReader mYuvReader = null;
+    private ImageReader mPostviewYuvReader = null;
     private android.hardware.camera2.extension.Size mResolution = null;
+    private android.hardware.camera2.extension.Size mPostviewResolution = null;
     private int mFormat = -1;
     private Surface mOutputSurface = null;
     private ImageWriter mOutputWriter = null;
+    private Surface mPostviewOutputSurface = null;
+    private ImageWriter mPostviewOutputWriter = null;
 
     private static final class JpegParameters {
         public HashSet<Long> mTimeStamps = new HashSet<>();
@@ -185,12 +189,13 @@
             int rot90);
 
     @Override
-    public void process(List<CaptureBundle> captureBundle, IProcessResultImpl captureCallback)
+    public void process(List<CaptureBundle> captureBundle, IProcessResultImpl captureCallback,
+            boolean isPostviewRequested)
             throws RemoteException {
         JpegParameters jpegParams = getJpegParameters(captureBundle);
         try {
             mJpegParameters.add(jpegParams);
-            mProcessor.process(captureBundle, captureCallback);
+            mProcessor.process(captureBundle, captureCallback, isPostviewRequested);
         } catch (Exception e) {
             mJpegParameters.remove(jpegParams);
             throw e;
@@ -206,10 +211,23 @@
         initializePipeline();
     }
 
+    public void onPostviewOutputSurface(Surface surface) throws RemoteException {
+        CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+                CameraExtensionUtils.querySurface(surface);
+        if (postviewSurfaceInfo.mFormat != ImageFormat.JPEG) {
+            Log.e(TAG, "Unsupported output format: " + postviewSurfaceInfo.mFormat);
+            return;
+        }
+        mPostviewOutputSurface = surface;
+        initializePostviewPipeline();
+    }
+
     @Override
-    public void onResolutionUpdate(android.hardware.camera2.extension.Size size)
+    public void onResolutionUpdate(android.hardware.camera2.extension.Size size,
+            android.hardware.camera2.extension.Size postviewSize)
             throws RemoteException {
         mResolution = size;
+        mPostviewResolution = postviewSize;
         initializePipeline();
     }
 
@@ -230,9 +248,26 @@
                     ImageFormat.JPEG, mResolution.width * mResolution.height, 1);
             mYuvReader = ImageReader.newInstance(mResolution.width, mResolution.height, mFormat,
                     JPEG_QUEUE_SIZE);
-            mYuvReader.setOnImageAvailableListener(new YuvCallback(), mHandler);
+            mYuvReader.setOnImageAvailableListener(
+                    new YuvCallback(mYuvReader, mOutputWriter), mHandler);
             mProcessor.onOutputSurface(mYuvReader.getSurface(), mFormat);
-            mProcessor.onResolutionUpdate(mResolution);
+            mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
+            mProcessor.onImageFormatUpdate(mFormat);
+        }
+    }
+
+    private void initializePostviewPipeline() throws RemoteException {
+        if ((mFormat != -1) && (mPostviewOutputSurface != null) && (mPostviewResolution != null)
+                && (mPostviewYuvReader == null)) {
+            // Jpeg/blobs are expected to be configured with (w*h)x1
+            mPostviewOutputWriter = ImageWriter.newInstance(mPostviewOutputSurface, 1/*maxImages*/,
+                    ImageFormat.JPEG, mPostviewResolution.width * mPostviewResolution.height, 1);
+            mPostviewYuvReader = ImageReader.newInstance(mPostviewResolution.width,
+                    mPostviewResolution.height, mFormat, JPEG_QUEUE_SIZE);
+            mPostviewYuvReader.setOnImageAvailableListener(
+                    new YuvCallback(mPostviewYuvReader, mPostviewOutputWriter), mHandler);
+            mProcessor.onPostviewOutputSurface(mPostviewYuvReader.getSurface());
+            mProcessor.onResolutionUpdate(mResolution, mPostviewResolution);
             mProcessor.onImageFormatUpdate(mFormat);
         }
     }
@@ -243,13 +278,21 @@
     }
 
     private class YuvCallback implements ImageReader.OnImageAvailableListener {
+        private ImageReader mImageReader;
+        private ImageWriter mImageWriter;
+
+        public YuvCallback(ImageReader imageReader, ImageWriter imageWriter) {
+            mImageReader = imageReader;
+            mImageWriter = imageWriter;
+        }
+
         @Override
         public void onImageAvailable(ImageReader reader) {
             Image yuvImage = null;
             Image jpegImage = null;
             try {
-                yuvImage = mYuvReader.acquireNextImage();
-                jpegImage = mOutputWriter.dequeueInputImage();
+                yuvImage = mImageReader.acquireNextImage();
+                jpegImage = mImageWriter.dequeueInputImage();
             } catch (IllegalStateException e) {
                 if (yuvImage != null) {
                     yuvImage.close();
@@ -270,7 +313,9 @@
             Plane crPlane = yuvImage.getPlanes()[1];
             Plane cbPlane = yuvImage.getPlanes()[2];
 
-            Iterator<JpegParameters> jpegIter = mJpegParameters.iterator();
+            ConcurrentLinkedQueue<JpegParameters> jpegParameters =
+                    new ConcurrentLinkedQueue(mJpegParameters);
+            Iterator<JpegParameters> jpegIter = jpegParameters.iterator();
             JpegParameters jpegParams = null;
             while(jpegIter.hasNext()) {
                 JpegParameters currentParams = jpegIter.next();
@@ -281,7 +326,7 @@
                 }
             }
             if (jpegParams == null) {
-                if (mJpegParameters.isEmpty()) {
+                if (jpegParameters.isEmpty()) {
                     Log.w(TAG, "Empty jpeg settings queue! Using default jpeg orientation"
                             + " and quality!");
                     jpegParams = new JpegParameters();
@@ -291,7 +336,7 @@
                     Log.w(TAG, "No jpeg settings found with matching timestamp for current"
                             + " processed input!");
                     Log.w(TAG, "Using values from the top of the queue!");
-                    jpegParams = mJpegParameters.poll();
+                    jpegParams = jpegParameters.poll();
                 }
             }
 
@@ -307,7 +352,7 @@
             yuvImage.close();
 
             try {
-                mOutputWriter.queueInputImage(jpegImage);
+                mImageWriter.queueInputImage(jpegImage);
             } catch (IllegalStateException e) {
                 Log.e(TAG, "Failed to queue encoded result!");
             } finally {
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
index 259bd7b..5b84ec0 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java
@@ -67,6 +67,7 @@
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -95,6 +96,7 @@
     private CameraCaptureSession mCaptureSession = null;
     private Surface mCameraRepeatingSurface, mClientRepeatingRequestSurface;
     private Surface mCameraBurstSurface, mClientCaptureSurface;
+    private Surface mClientPostviewSurface;
     private ImageReader mRepeatingRequestImageReader = null;
     private ImageReader mBurstCaptureImageReader = null;
     private ImageReader mStubCaptureImageReader = null;
@@ -197,6 +199,30 @@
             throw new IllegalArgumentException("One or more unsupported output surfaces found!");
         }
 
+        Surface postviewSurface = null;
+        if (burstCaptureSurface != null) {
+            CameraExtensionUtils.SurfaceInfo burstCaptureSurfaceInfo =
+                    CameraExtensionUtils.querySurface(burstCaptureSurface);
+            Size burstCaptureSurfaceSize =
+                    new Size(burstCaptureSurfaceInfo.mWidth, burstCaptureSurfaceInfo.mHeight);
+            HashMap<Integer, List<Size>> supportedPostviewSizes = new HashMap<>();
+            for (int format : CameraExtensionUtils.SUPPORTED_CAPTURE_OUTPUT_FORMATS) {
+                List<Size> supportedSizesPostview = extensionChars.getPostviewSupportedSizes(
+                        config.getExtension(), burstCaptureSurfaceSize, format);
+                if (supportedSizesPostview != null) {
+                    supportedPostviewSizes.put(format, supportedSizesPostview);
+                }
+            }
+
+            postviewSurface = CameraExtensionUtils.getPostviewSurface(
+                        config.getPostviewOutputConfiguration(), supportedPostviewSizes,
+                        burstCaptureSurfaceInfo.mFormat);
+
+            if ((config.getPostviewOutputConfiguration() != null) && (postviewSurface == null)) {
+                throw new IllegalArgumentException("Unsupported output surface for postview!");
+            }
+        }
+
         extenders.first.init(cameraId, chars.getNativeMetadata());
         extenders.first.onInit(cameraId, chars.getNativeMetadata());
         extenders.second.init(cameraId, chars.getNativeMetadata());
@@ -210,6 +236,7 @@
                 cameraDevice,
                 repeatingRequestSurface,
                 burstCaptureSurface,
+                postviewSurface,
                 config.getStateCallback(),
                 config.getExecutor(),
                 sessionId,
@@ -228,6 +255,7 @@
             @NonNull CameraDevice cameraDevice,
             @Nullable Surface repeatingRequestSurface,
             @Nullable Surface burstCaptureSurface,
+            @Nullable Surface postviewSurface,
             @NonNull StateCallback callback,
             @NonNull Executor executor,
             int sessionId,
@@ -241,6 +269,7 @@
         mExecutor = executor;
         mClientRepeatingRequestSurface = repeatingRequestSurface;
         mClientCaptureSurface = burstCaptureSurface;
+        mClientPostviewSurface = postviewSurface;
         mSupportedPreviewSizes = previewSizes;
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
@@ -365,7 +394,19 @@
                     new android.hardware.camera2.extension.Size();
             sz.width = mBurstCaptureImageReader.getWidth();
             sz.height = mBurstCaptureImageReader.getHeight();
-            mImageProcessor.onResolutionUpdate(sz);
+
+            if (mClientPostviewSurface != null) {
+                CameraExtensionUtils.SurfaceInfo postviewSurfaceInfo =
+                        CameraExtensionUtils.querySurface(mClientPostviewSurface);
+                android.hardware.camera2.extension.Size postviewSize =
+                        new android.hardware.camera2.extension.Size();
+                postviewSize.width = postviewSurfaceInfo.mWidth;
+                postviewSize.height = postviewSurfaceInfo.mHeight;
+                mImageProcessor.onResolutionUpdate(sz, postviewSize);
+            } else {
+                mImageProcessor.onResolutionUpdate(sz, null);
+            }
+
             mImageProcessor.onImageFormatUpdate(mBurstCaptureImageReader.getImageFormat());
         } else {
             if (mClientCaptureSurface != null) {
@@ -402,6 +443,10 @@
             }
         }
         if ((mImageProcessor != null) && (mClientCaptureSurface != null)) {
+            if (mClientPostviewSurface != null) {
+                mImageProcessor.onPostviewOutputSurface(mClientPostviewSurface);
+            }
+
             CameraExtensionUtils.SurfaceInfo surfaceInfo = CameraExtensionUtils.querySurface(
                     mClientCaptureSurface);
             mImageProcessor.onOutputSurface(mClientCaptureSurface, surfaceInfo.mFormat);
@@ -621,10 +666,7 @@
             throw new IllegalStateException("Uninitialized component");
         }
 
-        if (request.getTargets().size() != 1) {
-            throw new IllegalArgumentException("Single capture to both preview & still capture " +
-                    "outputs target is not supported!");
-        }
+        validateCaptureRequestTargets(request);
 
         int seqId = -1;
         if ((mClientCaptureSurface != null) && request.containsTarget(mClientCaptureSurface)) {
@@ -674,6 +716,27 @@
         return seqId;
     }
 
+    private void validateCaptureRequestTargets(@NonNull CaptureRequest request) {
+        if ((request.getTargets().size() == 1) &&
+                (!request.containsTarget(mClientRepeatingRequestSurface) ||
+                !request.containsTarget(mClientCaptureSurface))) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+
+        if ((request.getTargets().size() == 2) &&
+                (!request.getTargets().containsAll(Arrays.asList(mClientCaptureSurface,
+                mClientPostviewSurface)))) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+
+        if (request.getTargets().size() > 2) {
+            throw new IllegalArgumentException("Target output combination requested is " +
+                    "not supported!");
+        }
+    }
+
     @Override
     public void stopRepeating() throws CameraAccessException {
         synchronized (mInterfaceLock) {
@@ -814,6 +877,7 @@
             mImageProcessor = null;
             mCameraRepeatingSurface = mClientRepeatingRequestSurface = null;
             mCameraBurstSurface = mClientCaptureSurface = null;
+            mClientPostviewSurface = null;
         }
 
         if (notifyClose && !skipCloseNotification) {
@@ -1123,7 +1187,10 @@
                 List<CaptureBundle> captureList = initializeParcelable(mCaptureStageMap,
                         jpegOrientation, jpegQuality);
                 try {
-                    mImageProcessor.process(captureList, mCaptureResultHandler);
+                    boolean isPostviewRequested =
+                            mClientRequest.containsTarget(mClientPostviewSurface);
+                    mImageProcessor.process(captureList, mCaptureResultHandler,
+                            isPostviewRequested);
                 } catch (RemoteException e) {
                     Log.e(TAG, "Failed to process multi-frame request! Extension service "
                             + "does not respond!");
diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
index afefcbe..5222408 100644
--- a/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
+++ b/core/java/android/hardware/camera2/impl/CameraExtensionUtils.java
@@ -94,6 +94,31 @@
         return surfaceInfo;
     }
 
+    public static @Nullable Surface getPostviewSurface(
+            @Nullable OutputConfiguration outputConfig,
+            @NonNull HashMap<Integer, List<Size>> supportedPostviewSizes,
+            @NonNull int captureFormat) {
+        if (outputConfig == null) return null;
+
+        SurfaceInfo surfaceInfo = querySurface(outputConfig.getSurface());
+        if (surfaceInfo.mFormat == captureFormat) {
+            if (supportedPostviewSizes.containsKey(captureFormat)) {
+                Size postviewSize = new Size(surfaceInfo.mWidth, surfaceInfo.mHeight);
+                if (supportedPostviewSizes.get(surfaceInfo.mFormat)
+                        .contains(postviewSize)) {
+                    return outputConfig.getSurface();
+                } else {
+                    throw new IllegalArgumentException("Postview size not supported!");
+                }
+            }
+        } else {
+            throw new IllegalArgumentException("Postview format should be equivalent to " +
+                    " the capture format!");
+        }
+
+        return null;
+    }
+
     public static Surface getBurstCaptureSurface(
             @NonNull List<OutputConfiguration> outputConfigs,
             @NonNull HashMap<Integer, List<Size>> supportedCaptureSizes) {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 9a16474..8d742b5 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1461,6 +1461,23 @@
         return ret;
     }
 
+    private boolean isCroppedRawSupported() {
+        boolean ret = false;
+
+        long[] streamUseCases =
+                getBase(CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES);
+        if (streamUseCases == null) {
+            return false;
+        }
+        for (long useCase : streamUseCases) {
+            if (useCase == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW) {
+                return true;
+            }
+        }
+
+        return ret;
+    }
+
     private MandatoryStreamCombination[] getMandatoryStreamCombinationsHelper(
             int mandatoryStreamsType) {
         int[] capabilities = getBase(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
@@ -1472,7 +1489,8 @@
         int hwLevel = getBase(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
         MandatoryStreamCombination.Builder build = new MandatoryStreamCombination.Builder(
                 mCameraId, hwLevel, mDisplaySize, caps, getStreamConfigurationMap(),
-                getStreamConfigurationMapMaximumResolution(), isPreviewStabilizationSupported());
+                getStreamConfigurationMapMaximumResolution(), isPreviewStabilizationSupported(),
+                isCroppedRawSupported());
 
         List<MandatoryStreamCombination> combs = null;
         switch (mandatoryStreamsType) {
diff --git a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
index c81d339..0e6c1b3 100644
--- a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java
@@ -16,6 +16,7 @@
 package android.hardware.camera2.params;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 
 import android.hardware.camera2.CameraExtensionCharacteristics.Extension;
 import android.hardware.camera2.CameraExtensionSession;
@@ -32,6 +33,7 @@
 
     private int mExtensionType;
     private List<OutputConfiguration> mOutputs;
+    private OutputConfiguration mPostviewOutput = null;
     private Executor mExecutor = null;
     private CameraExtensionSession.StateCallback mCallback = null;
 
@@ -64,6 +66,28 @@
     }
 
     /**
+     * Set the postview for still capture output configuration.
+     *
+     * @param postviewOutput output configuration for postview
+     * @see android.hardware.camera2.CameraExtensionCharacteristics#isPostviewAvailable
+     */
+    public
+    void setPostviewOutputConfiguration(@Nullable OutputConfiguration postviewOutput) {
+        mPostviewOutput = postviewOutput;
+    }
+
+    /**
+     * Get the postview for still capture output configuration.
+     *
+     * @return output configuration for postview
+     * @see android.hardware.camera2.CameraExtensionCharacteristics#isPostviewAvailable
+     */
+    public @Nullable // Postview output is optional
+    OutputConfiguration getPostviewOutputConfiguration() {
+        return mPostviewOutput;
+    }
+
+    /**
      * Retrieve the {@link OutputConfiguration} list for the capture
      * session.
      *
diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
index 0905e1b..6f77d12 100644
--- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
+++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java
@@ -375,6 +375,8 @@
             CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL;
     private static final long STREAM_USE_CASE_VIDEO_CALL =
             CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL;
+    private static final long STREAM_USE_CASE_CROPPED_RAW =
+            CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW;
 
     /**
      * Create a new {@link MandatoryStreamCombination}.
@@ -1262,6 +1264,86 @@
                 "Preview, in-application image processing, and YUV still image capture"),
     };
 
+    private static StreamCombinationTemplate sCroppedRawStreamUseCaseCombinations[] = {
+        // Single stream combination
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Cropped RAW still image capture without preview"),
+
+        // 2 Stream combinations
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Cropped RAW still image capture with preview"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "In-app image processing with cropped RAW still image capture"),
+
+        // 3 stream combinations
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_STILL_CAPTURE),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Preview with YUV and RAW still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_STILL_CAPTURE),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "In-app image processing with YUV and RAW still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_STILL_CAPTURE),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Preview with JPEG and RAW still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.JPEG, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_STILL_CAPTURE),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "In-app image processing with JPEG and RAW still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_RECORD),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Preview with video recording and RAW snapshot"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.PRIVATE, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Preview with in-app image processing and RAW still image capture"),
+        new StreamCombinationTemplate(new StreamTemplate [] {
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.YUV_420_888, SizeThreshold.PREVIEW,
+                        STREAM_USE_CASE_PREVIEW),
+                new StreamTemplate(ImageFormat.RAW_SENSOR, SizeThreshold.MAXIMUM,
+                        STREAM_USE_CASE_CROPPED_RAW)},
+                "Two input in-app processing and RAW still image capture"),
+    };
+
     private static StreamCombinationTemplate sPreviewStabilizedStreamCombinations[] = {
         // 1 stream combinations
         new StreamCombinationTemplate(new StreamTemplate [] {
@@ -1317,7 +1399,8 @@
         private StreamConfigurationMap mStreamConfigMap;
         private StreamConfigurationMap mStreamConfigMapMaximumResolution;
         private boolean mIsHiddenPhysicalCamera;
-        private boolean mIsPreviewStabilizationSupported;
+        private boolean mIsPreviewStabilizationSupported = false;
+        private boolean mIsCroppedRawSupported = false;
 
         private final Size kPreviewSizeBound = new Size(1920, 1088);
 
@@ -1331,10 +1414,13 @@
          * @param sm The camera device stream configuration map.
          * @param smMaxResolution The camera device stream configuration map when it runs in max
          *                        resolution mode.
+         * @param previewStabilization The camera device supports preview stabilization.
+         * @param croppedRaw The camera device supports the cropped raw stream use case.
          */
         public Builder(int cameraId, int hwLevel, @NonNull Size displaySize,
                 @NonNull List<Integer> capabilities, @NonNull StreamConfigurationMap sm,
-                StreamConfigurationMap smMaxResolution, boolean previewStabilization) {
+                StreamConfigurationMap smMaxResolution, boolean previewStabilization,
+                boolean isCroppedRawSupported) {
             mCameraId = cameraId;
             mDisplaySize = displaySize;
             mCapabilities = capabilities;
@@ -1344,6 +1430,7 @@
             mIsHiddenPhysicalCamera =
                     CameraManager.isHiddenPhysicalCamera(Integer.toString(mCameraId));
             mIsPreviewStabilizationSupported = previewStabilization;
+            mIsCroppedRawSupported = isCroppedRawSupported;
         }
 
         private @Nullable List<MandatoryStreamCombination>
@@ -1483,10 +1570,23 @@
                 Log.e(TAG, "Available size enumeration failed!");
                 return null;
             }
+            ArrayList<StreamCombinationTemplate> availableTemplates =
+                    new ArrayList<StreamCombinationTemplate> ();
+            availableTemplates.addAll(Arrays.asList(sStreamUseCaseCombinations));
 
             ArrayList<MandatoryStreamCombination> availableStreamCombinations = new ArrayList<>();
-            availableStreamCombinations.ensureCapacity(sStreamUseCaseCombinations.length);
-            for (StreamCombinationTemplate combTemplate : sStreamUseCaseCombinations) {
+            int capacity = sStreamUseCaseCombinations.length;
+            if (mIsCroppedRawSupported) {
+                capacity += sCroppedRawStreamUseCaseCombinations.length;
+                availableStreamCombinations.ensureCapacity(capacity);
+                availableTemplates.addAll(Arrays.asList(sCroppedRawStreamUseCaseCombinations));
+            }
+             else {
+                availableStreamCombinations.ensureCapacity(capacity);
+             }
+
+
+            for (StreamCombinationTemplate combTemplate : availableTemplates) {
                 ArrayList<MandatoryStreamInformation> streamsInfo =
                         new ArrayList<MandatoryStreamInformation>();
                 streamsInfo.ensureCapacity(combTemplate.mStreamTemplates.length);
@@ -2012,6 +2112,7 @@
         private @Nullable HashMap<Pair<SizeThreshold, Integer>, List<Size>>
             enumerateAvailableSizes() {
             final int[] formats = {
+                ImageFormat.RAW_SENSOR,
                 ImageFormat.PRIVATE,
                 ImageFormat.YUV_420_888,
                 ImageFormat.JPEG,
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index f4b87b9..8b7c5ec 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -191,7 +191,9 @@
      *
      * <p>The timestamps of the output images are in the time base as specified by {@link
      * CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE}. The application can look up the
-     * corresponding result metadata for a particular output image using this timestamp.</p>
+     * corresponding result metadata by matching the timestamp with a {@link
+     * CameraCaptureSession.CaptureCallback#onCaptureStarted}, or with a {@link
+     * CameraCaptureSession.CaptureCallback#onReadoutStarted} if readout timestamp is used.</p>
      */
     public static final int TIMESTAMP_BASE_SENSOR = 1;
 
@@ -204,7 +206,8 @@
      *
      * <p>If the camera device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is
      * REALTIME, timestamps with this time base cannot directly match the timestamps in
-     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted}, {@link
+     * CameraCaptureSession.CaptureCallback#onReadoutStarted}, or the sensor timestamps in
      * {@link android.hardware.camera2.CaptureResult}.</p>
      */
     public static final int TIMESTAMP_BASE_MONOTONIC = 2;
@@ -218,7 +221,8 @@
      *
      * <p>If the camera device's {@link CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE} is
      * UNKNOWN, timestamps with this time base cannot directly match the timestamps in
-     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted}, {@link
+     * CameraCaptureSession.CaptureCallback#onReadoutStarted}, or the sensor timestamps in
      * {@link android.hardware.camera2.CaptureResult}.</p>
      *
      * <p>If using a REALTIME timestamp base on a device that supports only
@@ -243,7 +247,8 @@
      * displayed right away.</p>
      *
      * <p>Timestamps with this time base cannot directly match the timestamps in
-     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted} or the sensor timestamps in
+     * {@link CameraCaptureSession.CaptureCallback#onCaptureStarted}, {@link
+     * CameraCaptureSession.CaptureCallback#onReadoutStarted}, or the sensor timestamps in
      * {@link android.hardware.camera2.CaptureResult}. This timestamp base shouldn't be used if the
      * timestamp needs to be used for audio-video synchronization.</p>
      */
@@ -252,18 +257,7 @@
     /**
      * Timestamp is the start of readout in the same time domain as TIMESTAMP_BASE_SENSOR.
      *
-     * <p>The start of the camera sensor readout after exposure. For a rolling shutter camera
-     * sensor, the timestamp is typically equal to the start of exposure time +
-     * exposure time + certain fixed offset. The fixed offset could be due to camera sensor
-     * level crop. The benefit of using readout time is that when camera runs in a fixed
-     * frame rate, the timestamp intervals between frames are constant.</p>
-     *
-     * <p>This timestamp is in the same time domain as in TIMESTAMP_BASE_SENSOR, with the exception
-     * that one is start of exposure, and the other is start of readout.</p>
-     *
-     * <p>This timestamp base is supported only if {@link
-     * CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is
-     * {@link CameraMetadata#SENSOR_READOUT_TIMESTAMP_HARDWARE}.</p>
+     * <p>NOTE: do not use! Use useReadoutTimestamp instead.</p>
      *
      * @hide
      */
@@ -295,7 +289,8 @@
          CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE,
          CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD,
          CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL,
-         CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL})
+         CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL,
+         CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW})
     public @interface StreamUseCase {};
 
     /**
@@ -579,6 +574,8 @@
         mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
         mTimestampBase = TIMESTAMP_BASE_DEFAULT;
         mMirrorMode = MIRROR_MODE_AUTO;
+        mUseReadoutTimestamp = false;
+        mIsReadoutSensorTimestampBase = false;
     }
 
     /**
@@ -679,6 +676,8 @@
         mDynamicRangeProfile = DynamicRangeProfiles.STANDARD;
         mColorSpace = ColorSpaceProfiles.UNSPECIFIED;
         mStreamUseCase = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_DEFAULT;
+        mUseReadoutTimestamp = false;
+        mIsReadoutSensorTimestampBase = false;
     }
 
     /**
@@ -1000,7 +999,7 @@
      */
     public void setStreamUseCase(@StreamUseCase long streamUseCase) {
         // Verify that the value is in range
-        long maxUseCaseValue = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL;
+        long maxUseCaseValue = CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_CROPPED_RAW;
         if (streamUseCase > maxUseCaseValue &&
                 streamUseCase < CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VENDOR_START) {
             throw new IllegalArgumentException("Not a valid stream use case value " +
@@ -1048,7 +1047,15 @@
             throw new IllegalArgumentException("Not a valid timestamp base value " +
                     timestampBase);
         }
-        mTimestampBase = timestampBase;
+
+        if (timestampBase == TIMESTAMP_BASE_READOUT_SENSOR) {
+            mTimestampBase = TIMESTAMP_BASE_SENSOR;
+            mUseReadoutTimestamp = true;
+            mIsReadoutSensorTimestampBase = true;
+        } else {
+            mTimestampBase = timestampBase;
+            mIsReadoutSensorTimestampBase = false;
+        }
     }
 
     /**
@@ -1060,7 +1067,11 @@
      * @return The currently set timestamp base
      */
     public @TimestampBase int getTimestampBase() {
-        return mTimestampBase;
+        if (mIsReadoutSensorTimestampBase) {
+            return TIMESTAMP_BASE_READOUT_SENSOR;
+        } else {
+            return mTimestampBase;
+        }
     }
 
     /**
@@ -1100,6 +1111,37 @@
     }
 
     /**
+     * Use the camera sensor's readout time for the image timestamp.
+     *
+     * <p>The start of the camera sensor readout after exposure. For a rolling shutter camera
+     * sensor, the timestamp is typically equal to {@code (the start of exposure time) +
+     * (exposure time) + (certain fixed offset)}. The fixed offset can vary per session, depending
+     * on the underlying sensor configuration. The benefit of using readout time is that when
+     * camera runs in a fixed frame rate, the timestamp intervals between frames are constant.</p>
+     *
+     * <p>Readout timestamp is supported only if {@link
+     * CameraCharacteristics#SENSOR_READOUT_TIMESTAMP} is
+     * {@link CameraMetadata#SENSOR_READOUT_TIMESTAMP_HARDWARE}.</p>
+     *
+     * <p>As long as readout timestamp is supported, if the timestamp base isi
+     * {@link #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}, or if the timestamp base is DEFAULT for a
+     * SurfaceView output, the image timestamps for the output are always readout time regardless
+     * of whether this function is called.</p>
+     *
+     * @param on The output image timestamp is the start of exposure time if false, and
+     *           the start of readout time if true.
+     */
+    public void useReadoutTimestamp(boolean on) {
+        mUseReadoutTimestamp = on;
+    }
+
+    /** Whether readout timestamp is used for this OutputConfiguration.
+     */
+    public boolean isReadoutTimestampUsed() {
+        return mUseReadoutTimestamp;
+    }
+
+    /**
      * Create a new {@link OutputConfiguration} instance with another {@link OutputConfiguration}
      * instance.
      *
@@ -1130,6 +1172,7 @@
         this.mStreamUseCase = other.mStreamUseCase;
         this.mTimestampBase = other.mTimestampBase;
         this.mMirrorMode = other.mMirrorMode;
+        this.mUseReadoutTimestamp = other.mUseReadoutTimestamp;
     }
 
     /**
@@ -1157,6 +1200,7 @@
 
         int timestampBase = source.readInt();
         int mirrorMode = source.readInt();
+        boolean useReadoutTimestamp = source.readInt() == 1;
 
         mSurfaceGroupId = surfaceSetId;
         mRotation = rotation;
@@ -1185,6 +1229,7 @@
         mStreamUseCase = streamUseCase;
         mTimestampBase = timestampBase;
         mMirrorMode = mirrorMode;
+        mUseReadoutTimestamp = useReadoutTimestamp;
     }
 
     /**
@@ -1305,6 +1350,7 @@
         dest.writeLong(mStreamUseCase);
         dest.writeInt(mTimestampBase);
         dest.writeInt(mMirrorMode);
+        dest.writeInt(mUseReadoutTimestamp ? 1 : 0);
     }
 
     /**
@@ -1338,7 +1384,8 @@
                     mIsMultiResolution != other.mIsMultiResolution ||
                     mStreamUseCase != other.mStreamUseCase ||
                     mTimestampBase != other.mTimestampBase ||
-                    mMirrorMode != other.mMirrorMode)
+                    mMirrorMode != other.mMirrorMode ||
+                    mUseReadoutTimestamp != other.mUseReadoutTimestamp)
                 return false;
             if (mSensorPixelModesUsed.size() != other.mSensorPixelModesUsed.size()) {
                 return false;
@@ -1381,7 +1428,7 @@
                     mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                     mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
                     mDynamicRangeProfile, mColorSpace, mStreamUseCase,
-                    mTimestampBase, mMirrorMode);
+                    mTimestampBase, mMirrorMode, mUseReadoutTimestamp ? 1 : 0);
         }
 
         return HashCodeHelpers.hashCode(
@@ -1391,7 +1438,7 @@
                 mPhysicalCameraId == null ? 0 : mPhysicalCameraId.hashCode(),
                 mIsMultiResolution ? 1 : 0, mSensorPixelModesUsed.hashCode(),
                 mDynamicRangeProfile, mColorSpace, mStreamUseCase, mTimestampBase,
-                mMirrorMode);
+                mMirrorMode, mUseReadoutTimestamp ? 1 : 0);
     }
 
     private static final String TAG = "OutputConfiguration";
@@ -1433,4 +1480,8 @@
     private int mTimestampBase;
     // Mirroring mode
     private int mMirrorMode;
+    // Use readout timestamp
+    private boolean mUseReadoutTimestamp;
+    // Whether the timestamp base is set to READOUT_SENSOR
+    private boolean mIsReadoutSensorTimestampBase;
 }
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 23d108f..42803a1 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -139,6 +139,7 @@
             VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED,
             VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED,
             VIRTUAL_DISPLAY_FLAG_OWN_FOCUS,
+            VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface VirtualDisplayFlag {}
@@ -429,6 +430,18 @@
     public static final int VIRTUAL_DISPLAY_FLAG_DEVICE_DISPLAY_GROUP = 1 << 15;
 
 
+    /**
+     * Virtual display flags: Indicates that the display should not become the top focused display
+     * by stealing the top focus from another display.
+     *
+     * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+     * @see #createVirtualDisplay
+     * @see #VIRTUAL_DISPLAY_FLAG_OWN_FOCUS
+     * @hide
+     */
+    @SystemApi
+    public static final int VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 16;
+
     /** @hide */
     @IntDef(prefix = {"MATCH_CONTENT_FRAMERATE_"}, value = {
             MATCH_CONTENT_FRAMERATE_UNKNOWN,
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index 891ba36..abd647f 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -23,7 +23,6 @@
 import android.annotation.Nullable;
 import android.hardware.display.DisplayManager.VirtualDisplayFlag;
 import android.media.projection.MediaProjection;
-import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.Surface;
@@ -35,7 +34,8 @@
 
 /**
  * Holds configuration used to create {@link VirtualDisplay} instances. See
- * {@link MediaProjection#createVirtualDisplay(VirtualDisplayConfig, VirtualDisplay.Callback, Handler)}.
+ * {@link MediaProjection#createVirtualDisplay} and
+ * {@link android.companion.virtual.VirtualDeviceManager.VirtualDevice#createVirtualDisplay}.
  *
  * @hide
  */
diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java
index 625a6a5..5bb1f03 100644
--- a/core/java/android/hardware/hdmi/HdmiPortInfo.java
+++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java
@@ -23,7 +23,7 @@
 
 /**
  * A class to encapsulate HDMI port information. Contains the capability of the ports such as
- * HDMI-CEC, MHL, ARC(Audio Return Channel), and physical address assigned to each port.
+ * HDMI-CEC, MHL, ARC(Audio Return Channel), eARC and physical address assigned to each port.
  *
  * @hide
  */
@@ -40,6 +40,7 @@
     private final int mAddress;
     private final boolean mCecSupported;
     private final boolean mArcSupported;
+    private final boolean mEarcSupported;
     private final boolean mMhlSupported;
 
     /**
@@ -53,11 +54,28 @@
      * @param arc {@code true} if audio return channel is supported on the port
      */
     public HdmiPortInfo(int id, int type, int address, boolean cec, boolean mhl, boolean arc) {
+        this(id, type, address, cec, mhl, arc, false);
+    }
+
+    /**
+     * Constructor.
+     *
+     * @param id identifier assigned to each port. 1 for HDMI port 1
+     * @param type HDMI port input/output type
+     * @param address physical address of the port
+     * @param cec {@code true} if HDMI-CEC is supported on the port
+     * @param mhl {@code true} if MHL is supported on the port
+     * @param arc {@code true} if audio return channel is supported on the port
+     * @param earc {@code true} if eARC is supported on the port
+     */
+    public HdmiPortInfo(int id, int type, int address,
+            boolean cec, boolean mhl, boolean arc, boolean earc) {
         mId = id;
         mType = type;
         mAddress = address;
         mCecSupported = cec;
         mArcSupported = arc;
+        mEarcSupported = earc;
         mMhlSupported = mhl;
     }
 
@@ -116,6 +134,15 @@
     }
 
     /**
+     * Returns {@code true} if the port supports eARC.
+     *
+     * @return {@code true} if the port supports eARC.
+     */
+    public boolean isEarcSupported() {
+        return mEarcSupported;
+    }
+
+    /**
      * Describes the kinds of special objects contained in this Parcelable's
      * marshalled representation.
      */
@@ -138,7 +165,8 @@
                     boolean cec = (source.readInt() == 1);
                     boolean arc = (source.readInt() == 1);
                     boolean mhl = (source.readInt() == 1);
-                    return new HdmiPortInfo(id, type, address, cec, mhl, arc);
+                    boolean earc = (source.readInt() == 1);
+                    return new HdmiPortInfo(id, type, address, cec, mhl, arc, earc);
                 }
 
                 @Override
@@ -164,6 +192,7 @@
         dest.writeInt(mCecSupported ? 1 : 0);
         dest.writeInt(mArcSupported ? 1 : 0);
         dest.writeInt(mMhlSupported ? 1 : 0);
+        dest.writeInt(mEarcSupported ? 1 : 0);
     }
 
     @NonNull
@@ -175,7 +204,8 @@
         s.append("address: ").append(String.format("0x%04x", mAddress)).append(", ");
         s.append("cec: ").append(mCecSupported).append(", ");
         s.append("arc: ").append(mArcSupported).append(", ");
-        s.append("mhl: ").append(mMhlSupported);
+        s.append("mhl: ").append(mMhlSupported).append(", ");
+        s.append("earc: ").append(mEarcSupported);
         return s.toString();
     }
 
@@ -187,12 +217,12 @@
         final HdmiPortInfo other = (HdmiPortInfo) o;
         return mId == other.mId && mType == other.mType && mAddress == other.mAddress
                 && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported
-                && mMhlSupported == other.mMhlSupported;
+                && mMhlSupported == other.mMhlSupported && mEarcSupported == other.mEarcSupported;
     }
 
     @Override
     public int hashCode() {
         return java.util.Objects.hash(
-                mId, mType, mAddress, mCecSupported, mArcSupported, mMhlSupported);
+                mId, mType, mAddress, mCecSupported, mArcSupported, mMhlSupported, mEarcSupported);
     }
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9c42160..655e598 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -166,6 +166,14 @@
      * The <code>android:keyboardLayout</code> attribute refers to a
      * <a href="http://source.android.com/tech/input/key-character-map-files.html">
      * key character map</a> resource that defines the keyboard layout.
+     * The <code>android:keyboardLocale</code> attribute specifies a comma separated list of BCP 47
+     * language tags depicting the locales supported by the keyboard layout. This attribute is
+     * optional and will be used for auto layout selection for external physical keyboards.
+     * The <code>android:keyboardLayoutType</code> attribute specifies the layoutType for the
+     * keyboard layout. This can be either empty or one of the following supported layout types:
+     * qwerty, qwertz, azerty, dvorak, colemak, workman, extended, turkish_q, turkish_f. This
+     * attribute is optional and will be used for auto layout selection for external physical
+     * keyboards.
      * </p>
      */
     @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@@ -705,6 +713,30 @@
     }
 
     /**
+     * Returns the layout type of the queried layout
+     * <p>
+     * The input manager consults the built-in keyboard layouts as well as all keyboard layouts
+     * advertised by applications using a {@link #ACTION_QUERY_KEYBOARD_LAYOUTS} broadcast receiver.
+     * </p>
+     *
+     * @param layoutDescriptor The layout descriptor of the queried layout
+     * @return layout type of the queried layout
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public String getKeyboardLayoutTypeForLayoutDescriptor(@NonNull String layoutDescriptor) {
+        KeyboardLayout[] layouts = getKeyboardLayouts();
+        for (KeyboardLayout kl : layouts) {
+            if (layoutDescriptor.equals(kl.getDescriptor())) {
+                return kl.getLayoutType();
+            }
+        }
+        return "";
+    }
+
+    /**
      * Gets information about all supported keyboard layouts appropriate
      * for a specific input device.
      * <p>
diff --git a/core/java/android/hardware/input/KeyboardLayout.java b/core/java/android/hardware/input/KeyboardLayout.java
index 52c1551..58f7759 100644
--- a/core/java/android/hardware/input/KeyboardLayout.java
+++ b/core/java/android/hardware/input/KeyboardLayout.java
@@ -21,24 +21,74 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * Describes a keyboard layout.
  *
  * @hide
  */
-public final class KeyboardLayout implements Parcelable,
-        Comparable<KeyboardLayout> {
+public final class KeyboardLayout implements Parcelable, Comparable<KeyboardLayout> {
     private final String mDescriptor;
     private final String mLabel;
     private final String mCollection;
     private final int mPriority;
     @NonNull
     private final LocaleList mLocales;
+    private final LayoutType mLayoutType;
     private final int mVendorId;
     private final int mProductId;
 
-    public static final @android.annotation.NonNull Parcelable.Creator<KeyboardLayout> CREATOR =
-            new Parcelable.Creator<KeyboardLayout>() {
+    /** Currently supported Layout types in the KCM files */
+    private enum LayoutType {
+        UNDEFINED(0, "undefined"),
+        QWERTY(1, "qwerty"),
+        QWERTZ(2, "qwertz"),
+        AZERTY(3, "azerty"),
+        DVORAK(4, "dvorak"),
+        COLEMAK(5, "colemak"),
+        WORKMAN(6, "workman"),
+        TURKISH_F(7, "turkish_f"),
+        TURKISH_Q(8, "turkish_q"),
+        EXTENDED(9, "extended");
+
+        private final int mValue;
+        private final String mName;
+        private static final Map<Integer, LayoutType> VALUE_TO_ENUM_MAP = new HashMap<>();
+        static {
+            VALUE_TO_ENUM_MAP.put(UNDEFINED.mValue, UNDEFINED);
+            VALUE_TO_ENUM_MAP.put(QWERTY.mValue, QWERTY);
+            VALUE_TO_ENUM_MAP.put(QWERTZ.mValue, QWERTZ);
+            VALUE_TO_ENUM_MAP.put(AZERTY.mValue, AZERTY);
+            VALUE_TO_ENUM_MAP.put(DVORAK.mValue, DVORAK);
+            VALUE_TO_ENUM_MAP.put(COLEMAK.mValue, COLEMAK);
+            VALUE_TO_ENUM_MAP.put(WORKMAN.mValue, WORKMAN);
+            VALUE_TO_ENUM_MAP.put(TURKISH_F.mValue, TURKISH_F);
+            VALUE_TO_ENUM_MAP.put(TURKISH_Q.mValue, TURKISH_Q);
+            VALUE_TO_ENUM_MAP.put(EXTENDED.mValue, EXTENDED);
+        }
+
+        private static LayoutType of(int value) {
+            return VALUE_TO_ENUM_MAP.getOrDefault(value, UNDEFINED);
+        }
+
+        LayoutType(int value, String name) {
+            this.mValue = value;
+            this.mName = name;
+        }
+
+        private int getValue() {
+            return mValue;
+        }
+
+        private String getName() {
+            return mName;
+        }
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<KeyboardLayout> CREATOR = new Parcelable.Creator<>() {
         public KeyboardLayout createFromParcel(Parcel source) {
             return new KeyboardLayout(source);
         }
@@ -48,12 +98,13 @@
     };
 
     public KeyboardLayout(String descriptor, String label, String collection, int priority,
-            LocaleList locales, int vid, int pid) {
+            LocaleList locales, int layoutValue, int vid, int pid) {
         mDescriptor = descriptor;
         mLabel = label;
         mCollection = collection;
         mPriority = priority;
         mLocales = locales;
+        mLayoutType = LayoutType.of(layoutValue);
         mVendorId = vid;
         mProductId = pid;
     }
@@ -64,6 +115,7 @@
         mCollection = source.readString();
         mPriority = source.readInt();
         mLocales = LocaleList.CREATOR.createFromParcel(source);
+        mLayoutType = LayoutType.of(source.readInt());
         mVendorId = source.readInt();
         mProductId = source.readInt();
     }
@@ -106,6 +158,15 @@
     }
 
     /**
+     * Gets the layout type that this keyboard layout is intended for.
+     * This may be "undefined" if a layoutType has not been assigned to this keyboard layout.
+     * @return The keyboard layout's intended layout type.
+     */
+    public String getLayoutType() {
+        return mLayoutType.getName();
+    }
+
+    /**
      * Gets the vendor ID of the hardware device this keyboard layout is intended for.
      * Returns -1 if this is not specific to any piece of hardware.
      * @return The hardware vendor ID of the keyboard layout's intended device.
@@ -135,6 +196,7 @@
         dest.writeString(mCollection);
         dest.writeInt(mPriority);
         mLocales.writeToParcel(dest, 0);
+        dest.writeInt(mLayoutType.getValue());
         dest.writeInt(mVendorId);
         dest.writeInt(mProductId);
     }
@@ -160,6 +222,7 @@
                 + ", descriptor: " + mDescriptor
                 + ", priority: " + mPriority
                 + ", locales: " + mLocales.toString()
+                + ", layout type: " + mLayoutType.getName()
                 + ", vendorId: " + mVendorId
                 + ", productId: " + mProductId;
     }
diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java
index 28d8a0f..d788df4 100644
--- a/core/java/android/hardware/input/VirtualKeyboardConfig.java
+++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java
@@ -124,6 +124,10 @@
          * Note that the preferred layout is not guaranteed. If the specified language is
          * well-formed but not supported, the keyboard will be using English US QWERTY layout.
          *
+         * In case where the owning Virtual Device has created multiple virtual keyboards, only the
+         * {@code languageTag} of the most recent virtual keyboard will be kept to hint the locale
+         * of the Virtual Device.
+         *
          *  @throws IllegalArgumentException if either of the language or country is not present in
          *  the language tag.
          */
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 7faa285..727716e 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -65,12 +65,12 @@
      */
     @Deprecated
     public static final int PROGRAM_TYPE_INVALID = 0;
-    /** Analogue AM radio (with or without RDS).
+    /** Analog AM radio (with or without RDS).
      * @deprecated use {@link ProgramIdentifier} instead
      */
     @Deprecated
     public static final int PROGRAM_TYPE_AM = 1;
-    /** analogue FM radio (with or without RDS).
+    /** analog FM radio (with or without RDS).
      * @deprecated use {@link ProgramIdentifier} instead
      */
     @Deprecated
@@ -125,25 +125,50 @@
     public @interface ProgramType {}
 
     public static final int IDENTIFIER_TYPE_INVALID = 0;
-    /** kHz */
+    /**
+     * Primary identifier for analog (without RDS) AM/FM stations:
+     * frequency in kHz.
+     *
+     * <p>This identifier also contains band information:
+     * <li>
+     *     <ul><500kHz: AM LW.
+     *     <ul>500kHz - 1705kHz: AM MW.
+     *     <ul>1.71MHz - 30MHz: AM SW.
+     *     <ul>>60MHz: FM.
+     * </li>
+     */
     public static final int IDENTIFIER_TYPE_AMFM_FREQUENCY = 1;
-    /** 16bit */
+    /**
+     * 16bit primary identifier for FM RDS station.
+     */
     public static final int IDENTIFIER_TYPE_RDS_PI = 2;
     /**
      * 64bit compound primary identifier for HD Radio.
      *
-     * Consists of (from the LSB):
-     * - 32bit: Station ID number;
-     * - 4bit: HD_SUBCHANNEL;
-     * - 18bit: AMFM_FREQUENCY.
-     * The remaining bits should be set to zeros when writing on the chip side
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>132bit: Station ID number.
+     *     <ul>14bit: HD_SUBCHANNEL.
+     *     <ul>18bit: AMFM_FREQUENCY.
+     * </li>
+     *
+     * <p>While station ID number should be unique globally, it sometimes gets
+     * abused by broadcasters (i.e. not being set at all). To ensure local
+     * uniqueness, AMFM_FREQUENCY_KHZ was added here. Global uniqueness is
+     * a best-effort - see {@link IDENTIFIER_TYPE_HD_STATION_NAME}.
+     *
+     * <p>HD Radio subchannel is a value in range of 0-7.
+     * This index is 0-based (where 0 is MPS and 1..7 are SPS),
+     * as opposed to HD Radio standard (where it's 1-based).
+     *
+     * <p>The remaining bits should be set to zeros when writing on the chip side
      * and ignored when read.
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_ID_EXT = 3;
     /**
-     * HD Radio subchannel - a value of range 0-7.
+     * HD Radio subchannel - a value in range of 0-7.
      *
-     * The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
+     * <p>The subchannel index is 0-based (where 0 is MPS and 1..7 are SPS),
      * as opposed to HD Radio standard (where it's 1-based).
      *
      * @deprecated use IDENTIFIER_TYPE_HD_STATION_ID_EXT instead
@@ -153,16 +178,16 @@
     /**
      * 64bit additional identifier for HD Radio.
      *
-     * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+     * <p>Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
      * globally unique. To provide a best-effort solution, a short version of
      * station name may be carried as additional identifier and may be used
      * by the tuner hardware to double-check tuning.
      *
-     * The name is limited to the first 8 A-Z0-9 characters (lowercase letters
-     * must be converted to uppercase). Encoded in little-endian ASCII:
-     * the first character of the name is the LSB.
+     * <p>The name is limited to the first 8 A-Z0-9 characters (lowercase
+     * letters must be converted to uppercase). Encoded in little-endian
+     * ASCII: the first character of the name is the LSB.
      *
-     * For example: "Abc" is encoded as 0x434241.
+     * <p>For example: "Abc" is encoded as 0x434241.
      */
     public static final int IDENTIFIER_TYPE_HD_STATION_NAME = 10004;
     /**
@@ -175,17 +200,19 @@
     /**
      * 28bit compound primary identifier for Digital Audio Broadcasting.
      *
-     * Consists of (from the LSB):
-     * - 16bit: SId;
-     * - 8bit: ECC code;
-     * - 4bit: SCIdS.
+     * <p>Consists of (from the LSB):
+     * <li>
+     *     <ul>16bit: SId.
+     *     <ul>8bit: ECC code.
+     *     <ul>4bit: SCIdS.
+     * </li>
      *
-     * SCIdS (Service Component Identifier within the Service) value
+     * <p>SCIdS (Service Component Identifier within the Service) value
      * of 0 represents the main service, while 1 and above represents
      * secondary services.
      *
-     * The remaining bits should be set to zeros when writing on the chip side
-     * and ignored when read.
+     * <p>The remaining bits should be set to zeros when writing on the chip
+     * side and ignored when read.
      *
      * @deprecated use {@link #IDENTIFIER_TYPE_DAB_DMB_SID_EXT} instead
      */
@@ -197,7 +224,9 @@
     public static final int IDENTIFIER_TYPE_DAB_SCID = 7;
     /** kHz */
     public static final int IDENTIFIER_TYPE_DAB_FREQUENCY = 8;
-    /** 24bit */
+    /**
+     * 24bit primary identifier for Digital Radio Mondiale.
+     */
     public static final int IDENTIFIER_TYPE_DRMO_SERVICE_ID = 9;
     /** kHz */
     public static final int IDENTIFIER_TYPE_DRMO_FREQUENCY = 10;
@@ -207,7 +236,9 @@
      */
     @Deprecated
     public static final int IDENTIFIER_TYPE_DRMO_MODULATION = 11;
-    /** 32bit */
+    /**
+     * 32bit primary identifier for SiriusXM Satellite Radio.
+     */
     public static final int IDENTIFIER_TYPE_SXM_SERVICE_ID = 12;
     /** 0-999 range */
     public static final int IDENTIFIER_TYPE_SXM_CHANNEL = 13;
@@ -224,15 +255,15 @@
      * of 0 represents the main service, while 1 and above represents
      * secondary services.
      *
-     * The remaining bits should be set to zeros when writing on the chip side
-     * and ignored when read.
+     * <p>The remaining bits should be set to zeros when writing on the chip
+     * side and ignored when read.
      */
     public static final int IDENTIFIER_TYPE_DAB_DMB_SID_EXT = 14;
     /**
      * Primary identifier for vendor-specific radio technology.
      * The value format is determined by a vendor.
      *
-     * It must not be used in any other programType than corresponding VENDOR
+     * <p>It must not be used in any other programType than corresponding VENDOR
      * type between VENDOR_START and VENDOR_END (eg. identifier type 1015 must
      * not be used in any program type other than 1015).
      */
diff --git a/core/java/android/hardware/usb/UsbPort.java b/core/java/android/hardware/usb/UsbPort.java
index e0f9cad..cdd67b7 100644
--- a/core/java/android/hardware/usb/UsbPort.java
+++ b/core/java/android/hardware/usb/UsbPort.java
@@ -46,6 +46,8 @@
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
 import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
+import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+import static android.hardware.usb.UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_BC_1_2;
 import static android.hardware.usb.UsbPortStatus.COMPLIANCE_WARNING_MISSING_RP;
@@ -676,6 +678,15 @@
             statusString.append("disabled-debug, ");
         }
 
+        if ((usbDataStatus & DATA_STATUS_DISABLED_DOCK_HOST_MODE) ==
+            DATA_STATUS_DISABLED_DOCK_HOST_MODE) {
+            statusString.append("disabled-host-dock, ");
+        }
+
+        if ((usbDataStatus & DATA_STATUS_DISABLED_DOCK_DEVICE_MODE) ==
+            DATA_STATUS_DISABLED_DOCK_DEVICE_MODE) {
+            statusString.append("disabled-device-dock, ");
+        }
         return statusString.toString().replaceAll(", $", "");
     }
 
diff --git a/core/java/android/hardware/usb/UsbPortStatus.java b/core/java/android/hardware/usb/UsbPortStatus.java
index ed3e40d..e61703d 100644
--- a/core/java/android/hardware/usb/UsbPortStatus.java
+++ b/core/java/android/hardware/usb/UsbPortStatus.java
@@ -219,7 +219,11 @@
     public static final int DATA_STATUS_DISABLED_CONTAMINANT = 1 << 2;
 
     /**
-     * USB data is disabled due to docking event.
+     * This flag indicates that some or all data modes are disabled
+     * due to docking event, and the specific sub-statuses viz.,
+     * {@link #DATA_STATUS_DISABLED_DOCK_HOST_MODE},
+     * {@link #DATA_STATUS_DISABLED_DOCK_DEVICE_MODE}
+     * can be checked for individual modes.
      */
     public static final int DATA_STATUS_DISABLED_DOCK = 1 << 3;
 
@@ -235,6 +239,18 @@
     public static final int DATA_STATUS_DISABLED_DEBUG = 1 << 5;
 
     /**
+     * USB host mode is disabled due to docking event.
+     * {@link #DATA_STATUS_DISABLED_DOCK} will be set as well.
+     */
+    public static final int DATA_STATUS_DISABLED_DOCK_HOST_MODE = 1 << 6;
+
+    /**
+     * USB device mode is disabled due to docking event.
+     * {@link #DATA_STATUS_DISABLED_DOCK} will be set as well.
+     */
+    public static final int DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 1 << 7;
+
+    /**
      * Unknown whether a power brick is connected.
      */
     public static final int POWER_BRICK_STATUS_UNKNOWN = 0;
@@ -329,6 +345,8 @@
             DATA_STATUS_DISABLED_OVERHEAT,
             DATA_STATUS_DISABLED_CONTAMINANT,
             DATA_STATUS_DISABLED_DOCK,
+            DATA_STATUS_DISABLED_DOCK_HOST_MODE,
+            DATA_STATUS_DISABLED_DOCK_DEVICE_MODE,
             DATA_STATUS_DISABLED_FORCE,
             DATA_STATUS_DISABLED_DEBUG
     })
@@ -357,6 +375,20 @@
         mSupportedRoleCombinations = supportedRoleCombinations;
         mContaminantProtectionStatus = contaminantProtectionStatus;
         mContaminantDetectionStatus = contaminantDetectionStatus;
+
+        // Older implementations that only set the DISABLED_DOCK_MODE will have the other two
+        // set at the HAL interface level, so the "dock mode only" state shouldn't be visible here.
+        // But the semantics are ensured here.
+        int disabledDockModes = (usbDataStatus &
+            (DATA_STATUS_DISABLED_DOCK_HOST_MODE | DATA_STATUS_DISABLED_DOCK_DEVICE_MODE));
+        if (disabledDockModes != 0) {
+            // Set DATA_STATUS_DISABLED_DOCK when one of DATA_STATUS_DISABLED_DOCK_*_MODE is set
+            usbDataStatus |= DATA_STATUS_DISABLED_DOCK;
+        } else {
+            // Clear DATA_STATUS_DISABLED_DOCK when none of DATA_STATUS_DISABLED_DOCK_*_MODE is set
+            usbDataStatus &= ~DATA_STATUS_DISABLED_DOCK;
+        }
+
         mUsbDataStatus = usbDataStatus;
         mPowerTransferLimited = powerTransferLimited;
         mPowerBrickConnectionStatus = powerBrickConnectionStatus;
@@ -472,7 +504,8 @@
      *         {@link #DATA_STATUS_UNKNOWN}, {@link #DATA_STATUS_ENABLED},
      *         {@link #DATA_STATUS_DISABLED_OVERHEAT}, {@link #DATA_STATUS_DISABLED_CONTAMINANT},
      *         {@link #DATA_STATUS_DISABLED_DOCK}, {@link #DATA_STATUS_DISABLED_FORCE},
-     *         {@link #DATA_STATUS_DISABLED_DEBUG}
+     *         {@link #DATA_STATUS_DISABLED_DEBUG}, {@link #DATA_STATUS_DISABLED_DOCK_HOST_MODE},
+     *         {@link #DATA_STATUS_DISABLED_DOCK_DEVICE_MODE}
      */
     public @UsbDataStatus int getUsbDataStatus() {
         return mUsbDataStatus;
diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java
index de2dec7..0ba8d51 100644
--- a/core/java/android/os/BatteryConsumer.java
+++ b/core/java/android/os/BatteryConsumer.java
@@ -132,7 +132,7 @@
     @IntDef(prefix = {"POWER_MODEL_"}, value = {
             POWER_MODEL_UNDEFINED,
             POWER_MODEL_POWER_PROFILE,
-            POWER_MODEL_MEASURED_ENERGY,
+            POWER_MODEL_ENERGY_CONSUMPTION,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PowerModel {
@@ -150,9 +150,9 @@
     public static final int POWER_MODEL_POWER_PROFILE = 1;
 
     /**
-     * Power model that is based on energy consumption measured by on-device power monitors.
+     * Power model that is based on energy consumption stats provided by PowerStats HAL.
      */
-    public static final int POWER_MODEL_MEASURED_ENERGY = 2;
+    public static final int POWER_MODEL_ENERGY_CONSUMPTION = 2;
 
     /**
      * Identifiers of consumed power aggregations.
@@ -469,8 +469,8 @@
      */
     public static String powerModelToString(@BatteryConsumer.PowerModel int powerModel) {
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
-                return "measured energy";
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
+                return "energy consumption";
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
                 return "power profile";
             default:
@@ -484,7 +484,7 @@
      */
     public static int powerModelToProtoEnum(@BatteryConsumer.PowerModel int powerModel) {
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
                 return BatteryUsageStatsAtomsProto.PowerComponentModel.MEASURED_ENERGY;
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
                 return BatteryUsageStatsAtomsProto.PowerComponentModel.POWER_PROFILE;
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2a4c861..34aa7ef 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1043,12 +1043,13 @@
 
         /**
          * Returns the battery consumption (in microcoulombs) of bluetooth for this uid,
-         * derived from on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#BLUETOOTH} bucket
+         * provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getBluetoothMeasuredBatteryConsumptionUC();
+        public abstract long getBluetoothEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's bluetooth usage
@@ -1057,17 +1058,18 @@
          *
          * {@hide}
          */
-        public abstract long getBluetoothMeasuredBatteryConsumptionUC(
+        public abstract long getBluetoothEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState);
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's cpu usage, derived from
-         * on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#CPU} bucket
+         * provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getCpuMeasuredBatteryConsumptionUC();
+        public abstract long getCpuEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's cpu usage when in the
@@ -1076,26 +1078,28 @@
          *
          * {@hide}
          */
-        public abstract long getCpuMeasuredBatteryConsumptionUC(
+        public abstract long getCpuEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState);
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's GNSS usage, derived from
-         * on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#GNSS} bucket
+         * provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getGnssMeasuredBatteryConsumptionUC();
+        public abstract long getGnssEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's radio usage, derived from
-         * on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#MOBILE_RADIO}
+         * bucket provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
+        public abstract long getMobileRadioEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's radio usage when in the
@@ -1104,26 +1108,28 @@
          *
          * {@hide}
          */
-        public abstract long getMobileRadioMeasuredBatteryConsumptionUC(
+        public abstract long getMobileRadioEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState);
 
         /**
          * Returns the battery consumption (in microcoulombs) of the screen while on and uid active,
-         * derived from on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#DISPLAY} bucket
+         * provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getScreenOnMeasuredBatteryConsumptionUC();
+        public abstract long getScreenOnEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of wifi for this uid,
-         * derived from on device power measurement data.
+         * derived from {@link android.hardware.power.stats.EnergyConsumerType#WIFI} bucket
+         * provided by the PowerStats service.
          * Will return {@link #POWER_DATA_UNAVAILABLE} if data is unavailable.
          *
          * {@hide}
          */
-        public abstract long getWifiMeasuredBatteryConsumptionUC();
+        public abstract long getWifiEnergyConsumptionUC();
 
         /**
          * Returns the battery consumption (in microcoulombs) of the uid's wifi usage when in the
@@ -1132,7 +1138,7 @@
          *
          * {@hide}
          */
-        public abstract long getWifiMeasuredBatteryConsumptionUC(
+        public abstract long getWifiEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState);
 
 
@@ -1147,7 +1153,7 @@
          *
          * {@hide}
          */
-        public abstract @Nullable long[] getCustomConsumerMeasuredBatteryConsumptionUC();
+        public abstract @Nullable long[] getCustomEnergyConsumerBatteryConsumptionUC();
 
         public static abstract class Sensor {
 
@@ -1776,7 +1782,7 @@
     /**
      * Measured energy delta from the previous reading.
      */
-    public static final class MeasuredEnergyDetails {
+    public static final class EnergyConsumerDetails {
         /**
          * Description of the energy consumer, such as CPU, DISPLAY etc
          */
@@ -1986,8 +1992,8 @@
         // Non-null when there is more detailed information at this step.
         public HistoryStepDetails stepDetails;
 
-        // Non-null when there is measured energy information
-        public MeasuredEnergyDetails measuredEnergyDetails;
+        // Non-null when there is energy consumer information
+        public EnergyConsumerDetails energyConsumerDetails;
 
         // Non-null when there is CPU usage information
         public CpuUsageDetails cpuUsageDetails;
@@ -2200,7 +2206,7 @@
             eventCode = EVENT_NONE;
             eventTag = null;
             tagsFirstOccurrence = false;
-            measuredEnergyDetails = null;
+            energyConsumerDetails = null;
             cpuUsageDetails = null;
         }
 
@@ -2251,7 +2257,7 @@
             }
             tagsFirstOccurrence = o.tagsFirstOccurrence;
             currentTime = o.currentTime;
-            measuredEnergyDetails = o.measuredEnergyDetails;
+            energyConsumerDetails = o.energyConsumerDetails;
             cpuUsageDetails = o.cpuUsageDetails;
         }
 
@@ -2858,7 +2864,7 @@
      *
      * {@hide}
      */
-    public abstract long getBluetoothMeasuredBatteryConsumptionUC();
+    public abstract long getBluetoothEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of the cpu, derived from on device power
@@ -2867,7 +2873,7 @@
      *
      * {@hide}
      */
-    public abstract long getCpuMeasuredBatteryConsumptionUC();
+    public abstract long getCpuEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of the GNSS, derived from on device power
@@ -2876,7 +2882,7 @@
      *
      * {@hide}
      */
-    public abstract long getGnssMeasuredBatteryConsumptionUC();
+    public abstract long getGnssEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of the radio, derived from on device power
@@ -2885,7 +2891,7 @@
      *
      * {@hide}
      */
-    public abstract long getMobileRadioMeasuredBatteryConsumptionUC();
+    public abstract long getMobileRadioEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of the screen while on, derived from on
@@ -2894,7 +2900,7 @@
      *
      * {@hide}
      */
-    public abstract long getScreenOnMeasuredBatteryConsumptionUC();
+    public abstract long getScreenOnEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of the screen in doze, derived from on
@@ -2903,7 +2909,7 @@
      *
      * {@hide}
      */
-    public abstract long getScreenDozeMeasuredBatteryConsumptionUC();
+    public abstract long getScreenDozeEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) of wifi, derived from on
@@ -2912,7 +2918,7 @@
      *
      * {@hide}
      */
-    public abstract long getWifiMeasuredBatteryConsumptionUC();
+    public abstract long getWifiEnergyConsumptionUC();
 
     /**
      * Returns the battery consumption (in microcoulombs) that each
@@ -2924,7 +2930,7 @@
      *
      * {@hide}
      */
-    public abstract @Nullable long[] getCustomConsumerMeasuredBatteryConsumptionUC();
+    public abstract @Nullable long[] getCustomEnergyConsumerBatteryConsumptionUC();
 
     /**
      * Returns the names of all {@link android.hardware.power.stats.EnergyConsumer}'s
@@ -7088,19 +7094,19 @@
                     }
                 }
                 boolean firstExtension = true;
-                if (rec.measuredEnergyDetails != null) {
+                if (rec.energyConsumerDetails != null) {
                     firstExtension = false;
                     if (!checkin) {
                         item.append(" ext=energy:");
-                        item.append(rec.measuredEnergyDetails);
+                        item.append(rec.energyConsumerDetails);
                     } else {
                         item.append(",XE");
-                        for (int i = 0; i < rec.measuredEnergyDetails.consumers.length; i++) {
-                            if (rec.measuredEnergyDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
+                        for (int i = 0; i < rec.energyConsumerDetails.consumers.length; i++) {
+                            if (rec.energyConsumerDetails.chargeUC[i] != POWER_DATA_UNAVAILABLE) {
                                 item.append(',');
-                                item.append(rec.measuredEnergyDetails.consumers[i].name);
+                                item.append(rec.energyConsumerDetails.consumers[i].name);
                                 item.append('=');
-                                item.append(rec.measuredEnergyDetails.chargeUC[i]);
+                                item.append(rec.energyConsumerDetails.chargeUC[i]);
                             }
                         }
                     }
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index a31c2e6..ada5c55 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -982,6 +982,43 @@
 
 
     /**
+     * Wait until a debugger attaches. As soon as a debugger attaches,
+     * suspend all Java threads and send VM_START (a.k.a VM_INIT)
+     * packet.
+     *
+     * @hide
+     */
+    public static void suspendAllAndSendVmStart() {
+        if (!VMDebug.isDebuggingEnabled()) {
+            return;
+        }
+
+        // if DDMS is listening, inform them of our plight
+        System.out.println("Sending WAIT chunk");
+        byte[] data = new byte[] { 0 };     // 0 == "waiting for debugger"
+        Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1);
+        DdmServer.sendChunk(waitChunk);
+
+        // We must wait until a debugger is connected (debug socket is
+        // open and at least one non-DDM JDWP packedt has been received.
+        // This guarantees that oj-libjdwp has been attached and that
+        // ART's default implementation of suspendAllAndSendVmStart has
+        // been replaced with an implementation that will suspendAll and
+        // send VM_START.
+        System.out.println("Waiting for debugger first packet");
+        while (!isDebuggerConnected()) {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException ie) {
+            }
+        }
+
+        System.out.println("Debug.suspendAllAndSentVmStart");
+        VMDebug.suspendAllAndSendVmStart();
+        System.out.println("Debug.suspendAllAndSendVmStart, resumed");
+    }
+
+    /**
      * Wait until a debugger attaches.  As soon as the debugger attaches,
      * this returns, so you will need to place a breakpoint after the
      * waitForDebugger() call if you want to start tracing immediately.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 1490c6a..3b4e8cd 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -51,6 +51,7 @@
     String[] getPreInstallableSystemPackages(in String userType);
     void setUserEnabled(int userId);
     void setUserAdmin(int userId);
+    void revokeUserAdmin(int userId);
     void evictCredentialEncryptionKey(int userId);
     boolean removeUser(int userId);
     boolean removeUserEvenWhenDisallowed(int userId);
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 25a2852..90502af 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -591,7 +591,10 @@
             WAKE_REASON_DISPLAY_GROUP_TURNED_ON,
             WAKE_REASON_UNFOLD_DEVICE,
             WAKE_REASON_DREAM_FINISHED,
-            WAKE_REASON_TILT
+            WAKE_REASON_TILT,
+            WAKE_REASON_TAP,
+            WAKE_REASON_LIFT,
+            WAKE_REASON_BIOMETRIC,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WakeReason{}
@@ -643,8 +646,9 @@
     public static final int WAKE_REASON_PLUGGED_IN = 3;
 
     /**
-     * Wake up reason code: Waking up due to a user performed gesture (e.g. double tapping on the
-     * screen).
+     * Wake up reason code: Waking up due to a user performed gesture. This includes user
+     * interactions with UI on the screen such as the notification shade. This does not include
+     * {@link WAKE_REASON_TAP} or {@link WAKE_REASON_LIFT}.
      * @hide
      */
     public static final int WAKE_REASON_GESTURE = 4;
@@ -710,6 +714,25 @@
      * @hide
      */
     public static final int WAKE_REASON_TILT = 14;
+    /**
+     * Wake up reason code: Waking up due to the user single or double tapping on the screen. This
+     * wake reason is used when the user is not tapping on a specific UI element; rather, the device
+     * wakes up due to a generic tap on the screen.
+     * @hide
+     */
+    public static final int WAKE_REASON_TAP = 15;
+
+    /**
+     * Wake up reason code: Waking up due to a user performed lift gesture.
+     * @hide
+     */
+    public static final int WAKE_REASON_LIFT = 16;
+
+    /**
+     * Wake up reason code: Waking up due to a user interacting with a biometric.
+     * @hide
+     */
+    public static final int WAKE_REASON_BIOMETRIC = 17;
 
     /**
      * Convert the wake reason to a string for debugging purposes.
@@ -732,6 +755,9 @@
             case WAKE_REASON_UNFOLD_DEVICE: return "WAKE_REASON_UNFOLD_DEVICE";
             case WAKE_REASON_DREAM_FINISHED: return "WAKE_REASON_DREAM_FINISHED";
             case WAKE_REASON_TILT: return "WAKE_REASON_TILT";
+            case WAKE_REASON_TAP: return "WAKE_REASON_TAP";
+            case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT";
+            case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC";
             default: return Integer.toString(wakeReason);
         }
     }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index a6b62b0..180735b 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityThread;
 import android.app.IActivityManager;
+import android.app.IUnsafeIntentStrictModeCallback;
 import android.app.compat.CompatChanges;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
@@ -1079,8 +1080,7 @@
             }
 
             /**
-             * Detect when your app launches an {@link Intent} which originated
-             * from outside your app.
+             * Detect when your app sends an unsafe {@link Intent}.
              * <p>
              * Violations may indicate security vulnerabilities in the design of
              * your app, where a malicious app could trick you into granting
@@ -1088,10 +1088,14 @@
              * are some typical design patterns that can be used to safely
              * resolve these violations:
              * <ul>
-             * <li>The ideal approach is to migrate to using a
-             * {@link android.app.PendingIntent}, which ensures that your launch is
-             * performed using the identity of the original creator, completely
-             * avoiding the security issues described above.
+             * <li> If you are sending an implicit intent to an unexported component, you should
+             * make it an explicit intent by using {@link Intent#setPackage},
+             * {@link Intent#setClassName} or {@link Intent#setComponent}.
+             * </li>
+             * <li> If you are unparceling and sending an intent from the intent delivered, The
+             * ideal approach is to migrate to using a {@link android.app.PendingIntent}, which
+             * ensures that your launch is performed using the identity of the original creator,
+             * completely avoiding the security issues described above.
              * <li>If using a {@link android.app.PendingIntent} isn't feasible, an
              * alternative approach is to create a brand new {@link Intent} and
              * carefully copy only specific values from the original
@@ -2106,10 +2110,39 @@
                 VMRuntime.setDedupeHiddenApiWarnings(true);
             }
 
+            if ((sVmPolicy.mask & DETECT_VM_UNSAFE_INTENT_LAUNCH) != 0) {
+                registerIntentMatchingRestrictionCallback();
+            }
+
             setBlockGuardVmPolicy(sVmPolicy.mask);
         }
     }
 
+    private static void registerIntentMatchingRestrictionCallback() {
+        try {
+            ActivityManager.getService().registerStrictModeCallback(
+                    new UnsafeIntentStrictModeCallback());
+        } catch (RemoteException e) {
+            /*
+            If exception is DeadObjectException it means system process is dead, so we can ignore
+             */
+            if (!(e instanceof DeadObjectException)) {
+                Log.e(TAG, "RemoteException handling StrictMode violation", e);
+            }
+        }
+    }
+
+    private static final class UnsafeIntentStrictModeCallback
+            extends IUnsafeIntentStrictModeCallback.Stub {
+        @Override
+        public void onImplicitIntentMatchedInternalComponent(Intent intent) {
+            if (StrictMode.vmUnsafeIntentLaunchEnabled()) {
+                StrictMode.onUnsafeIntentLaunch(intent,
+                        "Launch of unsafe implicit intent: " + intent);
+            }
+        }
+    }
+
     /** Gets the current VM policy. */
     public static VmPolicy getVmPolicy() {
         synchronized (StrictMode.class) {
@@ -2333,6 +2366,11 @@
         onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent));
     }
 
+    /** @hide */
+    public static void onUnsafeIntentLaunch(Intent intent, String message) {
+        onVmPolicyViolation(new UnsafeIntentLaunchViolation(intent, message));
+    }
+
     /** Assume locked until we hear otherwise */
     private static volatile boolean sUserKeyUnlocked = false;
 
diff --git a/core/java/android/os/ThreadLocalWorkSource.java b/core/java/android/os/ThreadLocalWorkSource.java
index 894b1cc4..e9adb20 100644
--- a/core/java/android/os/ThreadLocalWorkSource.java
+++ b/core/java/android/os/ThreadLocalWorkSource.java
@@ -39,8 +39,8 @@
  */
 public final class ThreadLocalWorkSource {
     public static final int UID_NONE = Message.UID_NONE;
-    private static final ThreadLocal<Integer> sWorkSourceUid =
-            ThreadLocal.withInitial(() -> UID_NONE);
+    private static final ThreadLocal<int []> sWorkSourceUid =
+            ThreadLocal.withInitial(() -> new int[] {UID_NONE});
 
     /**
      * Returns the UID to blame for the code currently executed on this thread.
@@ -50,7 +50,7 @@
      * <p>It can also be set manually using {@link #setUid(int)}.
      */
     public static int getUid() {
-        return sWorkSourceUid.get();
+        return sWorkSourceUid.get()[0];
     }
 
     /**
@@ -65,7 +65,7 @@
      */
     public static long setUid(int uid) {
         final long token = getToken();
-        sWorkSourceUid.set(uid);
+        sWorkSourceUid.get()[0] = uid;
         return token;
     }
 
@@ -73,7 +73,7 @@
      * Restores the state using the provided token.
      */
     public static void restore(long token) {
-        sWorkSourceUid.set(parseUidFromToken(token));
+        sWorkSourceUid.get()[0] = parseUidFromToken(token);
     }
 
     /**
@@ -88,7 +88,7 @@
      * </pre>
      *
      * @return a token that can be used to restore the state.
-     **/
+     */
     public static long clear() {
         return setUid(UID_NONE);
     }
@@ -98,7 +98,7 @@
     }
 
     private static long getToken() {
-        return sWorkSourceUid.get();
+        return sWorkSourceUid.get()[0];
     }
 
     private ThreadLocalWorkSource() {
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 08d15c7..9915234 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2912,14 +2912,15 @@
     /**
      * @hide
      */
-    public static boolean isUsersOnSecondaryDisplaysEnabled() {
-        return SystemProperties.getBoolean("fw.users_on_secondary_displays",
+    public static boolean isVisibleBackgroundUsersEnabled() {
+        return SystemProperties.getBoolean("fw.visible_bg_users",
                 Resources.getSystem()
-                        .getBoolean(R.bool.config_multiuserUsersOnSecondaryDisplays));
+                        .getBoolean(R.bool.config_multiuserVisibleBackgroundUsers));
     }
 
     /**
-     * Returns whether the device allows users to run (and launch activities) on secondary displays.
+     * Returns whether the device allows (full) users to be started in background visible in a given
+     * display (which would allow them to launch activities in that display).
      *
      * @return {@code false} for most devices, except on automotive builds for vehiches with
      * passenger displays.
@@ -2927,8 +2928,8 @@
      * @hide
      */
     @TestApi
-    public boolean isUsersOnSecondaryDisplaysSupported() {
-        return isUsersOnSecondaryDisplaysEnabled();
+    public boolean isVisibleBackgroundUsersSupported() {
+        return isVisibleBackgroundUsersEnabled();
     }
 
     /**
@@ -4102,6 +4103,26 @@
     }
 
     /**
+     * Revokes admin privileges from the user, if such a user exists.
+     *
+     * <p>Note that this does not alter the user's pre-existing user restrictions.
+     *
+     * @param userId the id of the user to revoke admin rights from
+     * @hide
+     */
+    @RequiresPermission(allOf = {
+            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+            Manifest.permission.MANAGE_USERS
+    })
+    public void revokeUserAdmin(@UserIdInt int userId) {
+        try {
+            mService.revokeUserAdmin(userId);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Evicts the user's credential encryption key from memory by stopping and restarting the user.
      *
      * @hide
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 0b6a99b..3cb5c60 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -73,8 +73,6 @@
 
     private static final int ZYGOTE_CONNECT_TIMEOUT_MS = 20000;
 
-    private static final int APPLICATION_ZYGOTE_READ_TIMEOUT_MS = 5000;
-
     /**
      * Use a relatively short delay, because for app zygote, this is in the critical path of
      * service launch.
@@ -1111,9 +1109,6 @@
 
             state.mZygoteOutputWriter.flush();
 
-            // The system_server should not be blocked by a defective or bad application zygote.
-            state.mZygoteSessionSocket.setSoTimeout(APPLICATION_ZYGOTE_READ_TIMEOUT_MS);
-
             return (state.mZygoteInputStream.readInt() == 0);
         }
     }
diff --git a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
index f0f3cef..4abdc1b 100644
--- a/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
+++ b/core/java/android/os/strictmode/UnsafeIntentLaunchViolation.java
@@ -56,6 +56,12 @@
         mIntent = Objects.requireNonNull(intent);
     }
 
+    /** @hide */
+    public UnsafeIntentLaunchViolation(@NonNull Intent intent, @NonNull String message) {
+        super(message);
+        mIntent = Objects.requireNonNull(intent);
+    }
+
     /**
      * Return the {@link Intent} which caused this violation to be raised. Note
      * that this value is not available if this violation has been serialized
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d01c33d..b236d66 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -17,6 +17,7 @@
 package android.provider;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -87,6 +88,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.MemoryIntArray;
+import android.util.Slog;
 import android.view.Display;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
@@ -112,6 +114,8 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
+
 /**
  * The Settings provider contains global system-level device preferences.
  */
@@ -220,6 +224,7 @@
      * @hide
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    @SystemApi
     public static final String ACTION_USER_SETTINGS =
             "android.settings.USER_SETTINGS";
 
@@ -2719,6 +2724,10 @@
     public static final String CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG =
             "REGISTER_MONITOR_CALLBACK_config";
 
+    /** @hide - Private call() method to unregister monitor callback for 'configuration' table */
+    public static final String CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG =
+            "UNREGISTER_MONITOR_CALLBACK_config";
+
     /** @hide - String argument extra to the config monitor callback */
     public static final String EXTRA_MONITOR_CALLBACK_TYPE = "monitor_callback_type";
 
@@ -18370,16 +18379,41 @@
         }
 
         /**
-         * Register callback for monitoring Config table.
+         * Setter callback for monitoring Config table.
          *
-         * @param callback callback to register
+         * @param executor the {@link Executor} on which to invoke the callback
+         * @param callback callback to set
          *
          * @hide
          */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
         @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS)
-        public static void registerMonitorCallback(@NonNull ContentResolver resolver,
-                @NonNull RemoteCallback callback) {
-            registerMonitorCallbackAsUser(resolver, resolver.getUserId(), callback);
+        public static void setMonitorCallback(
+                @NonNull ContentResolver resolver,
+                @NonNull @CallbackExecutor Executor executor,
+                @NonNull DeviceConfig.MonitorCallback callback) {
+            setMonitorCallbackAsUser(executor, resolver, resolver.getUserId(), callback);
+        }
+
+        /**
+         * Clear callback for monitoring Config table.
+         * this may only be used to clear callback function registered by
+         * {@link Config#setMonitorCallback}
+         * @hide
+         */
+        @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+        @RequiresPermission(Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS)
+        public static void clearMonitorCallback(@NonNull ContentResolver resolver) {
+            try {
+                Bundle arg = new Bundle();
+                arg.putInt(CALL_METHOD_USER_KEY, resolver.getUserId());
+                IContentProvider cp = sProviderHolder.getProvider(resolver);
+                cp.call(resolver.getAttributionSource(),
+                        sProviderHolder.mUri.getAuthority(),
+                        CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG, null, arg);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Can't clear config monitor callback", e);
+            }
         }
 
 
@@ -18446,19 +18480,23 @@
             }
         }
 
-        private static void registerMonitorCallbackAsUser(
+        private static void setMonitorCallbackAsUser(
+                @NonNull @CallbackExecutor Executor executor,
                 @NonNull ContentResolver resolver, @UserIdInt int userHandle,
-                @NonNull RemoteCallback callback) {
+                @NonNull DeviceConfig.MonitorCallback callback) {
             try {
                 Bundle arg = new Bundle();
                 arg.putInt(CALL_METHOD_USER_KEY, userHandle);
-                arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY, callback);
+                arg.putParcelable(CALL_METHOD_MONITOR_CALLBACK_KEY,
+                        new RemoteCallback(result -> {
+                            handleMonitorCallback(result, executor, callback);
+                        }));
                 IContentProvider cp = sProviderHolder.getProvider(resolver);
                 cp.call(resolver.getAttributionSource(),
                         sProviderHolder.mUri.getAuthority(),
                         CALL_METHOD_REGISTER_MONITOR_CALLBACK_CONFIG, null, arg);
             } catch (RemoteException e) {
-                Log.w(TAG, "Can't register config monitor callback", e);
+                Log.w(TAG, "Can't set config monitor callback", e);
             }
         }
 
@@ -18468,6 +18506,32 @@
             sNameValueCache.clearGenerationTrackerForTest();
         }
 
+        private static void handleMonitorCallback(
+                Bundle result,
+                @NonNull @CallbackExecutor Executor executor,
+                DeviceConfig.MonitorCallback monitorCallback) {
+            String callbackType = result.getString(EXTRA_MONITOR_CALLBACK_TYPE, "");
+            switch (callbackType) {
+                case EXTRA_NAMESPACE_UPDATED_CALLBACK:
+                    String updatedNamespace = result.getString(EXTRA_NAMESPACE);
+                    if (updatedNamespace != null) {
+                        executor.execute(() -> monitorCallback.onNamespaceUpdate(updatedNamespace));
+                    }
+                    break;
+                case EXTRA_ACCESS_CALLBACK:
+                    String callingPackage = result.getString(EXTRA_CALLING_PACKAGE, null);
+                    String namespace = result.getString(EXTRA_NAMESPACE, null);
+                    if (namespace != null && callingPackage != null) {
+                        executor.execute(() ->
+                                monitorCallback.onDeviceConfigAccess(callingPackage, namespace));
+                    }
+                    break;
+                default:
+                    Slog.w(TAG, "Unrecognized DeviceConfig callback");
+                    break;
+            }
+        }
+
         private static String createCompositeName(@NonNull String namespace, @NonNull String name) {
             Preconditions.checkNotNull(namespace);
             Preconditions.checkNotNull(name);
diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java
index cfb195b..4892312 100644
--- a/core/java/android/security/net/config/SystemCertificateSource.java
+++ b/core/java/android/security/net/config/SystemCertificateSource.java
@@ -18,6 +18,7 @@
 
 import android.os.Environment;
 import android.os.UserHandle;
+
 import java.io.File;
 
 /**
@@ -32,11 +33,20 @@
     private final File mUserRemovedCaDir;
 
     private SystemCertificateSource() {
-        super(new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"));
+        super(getDirectory());
         File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
         mUserRemovedCaDir = new File(configDir, "cacerts-removed");
     }
 
+    private static File getDirectory() {
+        // TODO(miguelaranda): figure out correct code path.
+        File updatable_dir = new File("/apex/com.android.conscrypt/cacerts");
+        if (updatable_dir.exists()) {
+            return updatable_dir;
+        }
+        return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts");
+    }
+
     public static SystemCertificateSource getInstance() {
         return NoPreloadHolder.INSTANCE;
     }
diff --git a/core/java/android/service/controls/ControlsProviderService.java b/core/java/android/service/controls/ControlsProviderService.java
index 950c8ac..b85cf6d 100644
--- a/core/java/android/service/controls/ControlsProviderService.java
+++ b/core/java/android/service/controls/ControlsProviderService.java
@@ -58,27 +58,28 @@
      * Manifest metadata to show a custom embedded activity as part of device controls.
      *
      * The value of this metadata must be the {@link ComponentName} as a string of an activity in
-     * the same package that will be launched as part of a TaskView.
+     * the same package that will be launched embedded in the device controls space.
      *
      * The activity must be exported, enabled and protected by
-     * {@link Manifest.permission.BIND_CONTROLS}.
+     * {@link Manifest.permission#BIND_CONTROLS}.
      *
-     * @hide
+     * It is recommended that the activity is declared {@code android:resizeableActivity="true"}.
      */
     public static final String META_DATA_PANEL_ACTIVITY =
             "android.service.controls.META_DATA_PANEL_ACTIVITY";
 
     /**
-     * Boolean extra containing the value of
-     * {@link android.provider.Settings.Secure#LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS}.
+     * Boolean extra containing the value of the setting allowing actions on a locked device.
+     *
+     * This corresponds to the setting that indicates whether the user has
+     * consented to allow actions on devices that declare {@link Control#isAuthRequired()} as
+     * {@code false} when the device is locked.
      *
      * 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.EXTRA_LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
+            "android.service.controls.extra.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS";
 
     /**
      * @hide
diff --git a/core/java/android/service/controls/OWNERS b/core/java/android/service/controls/OWNERS
new file mode 100644
index 0000000..4bb78c7
--- /dev/null
+++ b/core/java/android/service/controls/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 802726
+asc@google.com
+kozynski@google.com
+juliacr@google.com
\ No newline at end of file
diff --git a/core/java/android/service/notification/ZenPolicy.java b/core/java/android/service/notification/ZenPolicy.java
index a892570..bffa660 100644
--- a/core/java/android/service/notification/ZenPolicy.java
+++ b/core/java/android/service/notification/ZenPolicy.java
@@ -79,6 +79,12 @@
     /** @hide */
     public static final int PRIORITY_CATEGORY_CONVERSATIONS = 8;
 
+    /**
+     * Total number of priority categories. Keep updated with any updates to PriorityCategory enum.
+     * @hide
+     */
+    public static final int NUM_PRIORITY_CATEGORIES = 9;
+
     /** @hide */
     @IntDef(prefix = { "VISUAL_EFFECT_" }, value = {
             VISUAL_EFFECT_FULL_SCREEN_INTENT,
@@ -107,6 +113,12 @@
     /** @hide */
     public static final int VISUAL_EFFECT_NOTIFICATION_LIST = 6;
 
+    /**
+     * Total number of visual effects. Keep updated with any updates to VisualEffect enum.
+     * @hide
+     */
+    public static final int NUM_VISUAL_EFFECTS = 7;
+
     /** @hide */
     @IntDef(prefix = { "PEOPLE_TYPE_" }, value = {
             PEOPLE_TYPE_UNSET,
@@ -202,8 +214,8 @@
 
     /** @hide */
     public ZenPolicy() {
-        mPriorityCategories = new ArrayList<>(Collections.nCopies(9, 0));
-        mVisualEffects = new ArrayList<>(Collections.nCopies(7, 0));
+        mPriorityCategories = new ArrayList<>(Collections.nCopies(NUM_PRIORITY_CATEGORIES, 0));
+        mVisualEffects = new ArrayList<>(Collections.nCopies(NUM_VISUAL_EFFECTS, 0));
     }
 
     /**
@@ -804,8 +816,12 @@
         @Override
         public ZenPolicy createFromParcel(Parcel source) {
             ZenPolicy policy = new ZenPolicy();
-            policy.mPriorityCategories = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
-            policy.mVisualEffects = source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class);
+            policy.mPriorityCategories = trimList(
+                    source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+                    NUM_PRIORITY_CATEGORIES);
+            policy.mVisualEffects = trimList(
+                    source.readArrayList(Integer.class.getClassLoader(), java.lang.Integer.class),
+                    NUM_VISUAL_EFFECTS);
             policy.mPriorityCalls = source.readInt();
             policy.mPriorityMessages = source.readInt();
             policy.mConversationSenders = source.readInt();
@@ -832,6 +848,15 @@
                 .toString();
     }
 
+    // Returns a list containing the first maxLength elements of the input list if the list is
+    // longer than that size. For the lists in ZenPolicy, this should not happen unless the input
+    // is corrupt.
+    private static ArrayList<Integer> trimList(ArrayList<Integer> list, int maxLength) {
+        if (list == null || list.size() <= maxLength) {
+            return list;
+        }
+        return new ArrayList<>(list.subList(0, maxLength));
+    }
 
     private String priorityCategoriesToString() {
         StringBuilder builder = new StringBuilder();
diff --git a/core/java/android/service/wallpaper/OWNERS b/core/java/android/service/wallpaper/OWNERS
index 756eef8..71bd190 100644
--- a/core/java/android/service/wallpaper/OWNERS
+++ b/core/java/android/service/wallpaper/OWNERS
@@ -3,3 +3,6 @@
 dupin@google.com
 dsandler@android.com
 dsandler@google.com
+pomini@google.com
+poultney@google.com
+santie@google.com
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index de5d395..0eab81c 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -394,7 +394,7 @@
             public void resized(ClientWindowFrames frames, boolean reportDraw,
                     MergedConfiguration mergedConfiguration, InsetsState insetsState,
                     boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
-                    int syncSeqId, int resizeMode) {
+                    int syncSeqId, boolean dragResizing) {
                 Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED,
                         reportDraw ? 1 : 0,
                         mergedConfiguration);
diff --git a/core/java/android/service/wearable/IWearableSensingService.aidl b/core/java/android/service/wearable/IWearableSensingService.aidl
index ba71174..44a13c4 100644
--- a/core/java/android/service/wearable/IWearableSensingService.aidl
+++ b/core/java/android/service/wearable/IWearableSensingService.aidl
@@ -16,6 +16,7 @@
 
 package android.service.wearable;
 
+import android.app.ambientcontext.AmbientContextEventRequest;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
@@ -29,4 +30,8 @@
 oneway interface IWearableSensingService {
     void provideDataStream(in ParcelFileDescriptor parcelFileDescriptor, in RemoteCallback callback);
     void provideData(in PersistableBundle data, in SharedMemory sharedMemory, in RemoteCallback callback);
+    void startDetection(in AmbientContextEventRequest request, in String packageName,
+            in RemoteCallback detectionResultCallback, in RemoteCallback statusCallback);
+    void stopDetection(in String packageName);
+    void queryServiceStatus(in int[] eventTypes, in String packageName, in RemoteCallback callback);
 }
\ No newline at end of file
diff --git a/core/java/android/service/wearable/WearableSensingService.java b/core/java/android/service/wearable/WearableSensingService.java
index a1c7658..8f49bcb 100644
--- a/core/java/android/service/wearable/WearableSensingService.java
+++ b/core/java/android/service/wearable/WearableSensingService.java
@@ -22,6 +22,7 @@
 import android.annotation.SystemApi;
 import android.app.Service;
 import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
 import android.app.wearable.WearableSensingManager;
 import android.content.Intent;
 import android.os.Bundle;
@@ -30,9 +31,14 @@
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.SharedMemory;
+import android.service.ambientcontext.AmbientContextDetectionResult;
+import android.service.ambientcontext.AmbientContextDetectionServiceStatus;
 import android.util.Slog;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Objects;
+import java.util.Set;
 import java.util.function.Consumer;
 
 /**
@@ -116,6 +122,60 @@
                     };
                     WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
                 }
+
+                /** {@inheritDoc} */
+                @Override
+                public void startDetection(@NonNull AmbientContextEventRequest request,
+                        String packageName, RemoteCallback detectionResultCallback,
+                        RemoteCallback statusCallback) {
+                    Objects.requireNonNull(request);
+                    Objects.requireNonNull(packageName);
+                    Objects.requireNonNull(detectionResultCallback);
+                    Objects.requireNonNull(statusCallback);
+                    Consumer<AmbientContextDetectionResult> detectionResultConsumer = result -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(
+                                AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY, result);
+                        detectionResultCallback.sendResult(bundle);
+                    };
+                    Consumer<AmbientContextDetectionServiceStatus> statusConsumer = status -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(
+                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
+                                status);
+                        statusCallback.sendResult(bundle);
+                    };
+                    WearableSensingService.this.onStartDetection(
+                            request, packageName, statusConsumer, detectionResultConsumer);
+                    Slog.d(TAG, "startDetection " + request);
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public void stopDetection(String packageName) {
+                    Objects.requireNonNull(packageName);
+                    WearableSensingService.this.onStopDetection(packageName);
+                }
+
+                /** {@inheritDoc} */
+                @Override
+                public void queryServiceStatus(@AmbientContextEvent.EventCode int[] eventTypes,
+                        String packageName, RemoteCallback callback) {
+                    Objects.requireNonNull(eventTypes);
+                    Objects.requireNonNull(packageName);
+                    Objects.requireNonNull(callback);
+                    Consumer<AmbientContextDetectionServiceStatus> consumer = response -> {
+                        Bundle bundle = new Bundle();
+                        bundle.putParcelable(
+                                AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY,
+                                response);
+                        callback.sendResult(bundle);
+                    };
+                    Integer[] events = intArrayToIntegerArray(eventTypes);
+                    WearableSensingService.this.onQueryServiceStatus(
+                            new HashSet<>(Arrays.asList(events)), packageName, consumer);
+                }
+
             };
         }
         Slog.w(TAG, "Incorrect service interface, returning null.");
@@ -155,4 +215,61 @@
             @NonNull PersistableBundle data,
             @Nullable SharedMemory sharedMemory,
             @NonNull Consumer<Integer> statusConsumer);
+
+    /**
+     * Called when a client app requests starting detection of the events in the request. The
+     * implementation should keep track of whether the user has explicitly consented to detecting
+     * the events using on-going ambient sensor (e.g. microphone), and agreed to share the
+     * detection results with this client app. If the user has not consented, the detection
+     * should not start, and the statusConsumer should get a response with STATUS_ACCESS_DENIED.
+     * If the user has made the consent and the underlying services are available, the
+     * implementation should start detection and provide detected events to the
+     * detectionResultConsumer. If the type of event needs immediate attention, the implementation
+     * should send result as soon as detected. Otherwise, the implementation can batch response.
+     * The ongoing detection will keep running, until onStopDetection is called. If there were
+     * previously requested detections from the same package, regardless of the type of events in
+     * the request, the previous request will be replaced with the new request and pending events
+     * are discarded.
+     *
+     * @param request The request with events to detect.
+     * @param packageName the requesting app's package name
+     * @param statusConsumer the consumer for the service status.
+     * @param detectionResultConsumer the consumer for the detected event
+     */
+    @BinderThread
+    public abstract void onStartDetection(@NonNull AmbientContextEventRequest request,
+            @NonNull String packageName,
+            @NonNull Consumer<AmbientContextDetectionServiceStatus> statusConsumer,
+            @NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer);
+
+    /**
+     * Stops detection of the events. Events that are not being detected will be ignored.
+     *
+     * @param packageName stops detection for the given package.
+     */
+    public abstract void onStopDetection(@NonNull String packageName);
+
+    /**
+     * Called when a query for the detection status occurs. The implementation should check
+     * the detection status of the requested events for the package, and provide results in a
+     * {@link AmbientContextDetectionServiceStatus} for the consumer.
+     *
+     * @param eventTypes The events to check for status.
+     * @param packageName the requesting app's package name
+     * @param consumer the consumer for the query results
+     */
+    @BinderThread
+    public abstract void onQueryServiceStatus(@NonNull Set<Integer> eventTypes,
+            @NonNull String packageName,
+            @NonNull Consumer<AmbientContextDetectionServiceStatus> consumer);
+
+    @NonNull
+    private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) {
+        Integer[] intArray = new Integer[integerSet.length];
+        int i = 0;
+        for (Integer type : integerSet) {
+            intArray[i++] = type;
+        }
+        return intArray;
+    }
 }
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index a50e6db..f8df668 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -106,6 +106,10 @@
     /**
      * Event for changes to the network service state (cellular).
      *
+     * <p>Requires {@link Manifest.permission#ACCESS_FINE_LOCATION} or {@link
+     * Manifest.permission#ACCESS_COARSE_LOCATION} depending on the accuracy of the location info
+     * listeners want to get.
+     *
      * @hide
      * @see ServiceStateListener#onServiceStateChanged
      * @see ServiceState
@@ -485,8 +489,9 @@
      * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
      * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
+     * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+     * listener want to get location info in {@link CellIdentity} regardless of whether the calling
+     * app has carrier privileges.
      *
      * @hide
      * @see RegistrationFailedListener#onRegistrationFailed
@@ -504,8 +509,9 @@
      * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} or
      * the calling app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
      *
-     * <p>Also requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission, regardless
-     * of whether the calling app has carrier privileges.
+     * <p>Requires the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission in case that
+     * listener want to get {@link BarringInfo} which includes location info in {@link CellIdentity}
+     * regardless of whether the calling app has carrier privileges.
      *
      * @hide
      * @see BarringInfoListener#onBarringInfoChanged
@@ -691,10 +697,8 @@
          * Only apps holding the {@link Manifest.permission#ACCESS_FINE_LOCATION} permission will
          * receive all the information in {@link ServiceState}, otherwise the cellIdentity
          * will be null if apps only holding the {@link Manifest.permission#ACCESS_COARSE_LOCATION}
-         * permission.
-         * Network operator name in long/short alphanumeric format and numeric id will be null if
-         * apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION} nor
-         * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
+         * permission. Network operator name in long/short alphanumeric format and numeric id will
+         * be null if apps holding neither {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
          *
          * @see ServiceState#STATE_EMERGENCY_ONLY
          * @see ServiceState#STATE_IN_SERVICE
@@ -1284,6 +1288,9 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
          * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
          *
+         * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+         * it will receive {@link CellIdentity} without location-sensitive information included.
+         *
          * @param cellIdentity        the CellIdentity, which must include the globally unique
          *                            identifier
          *                            for the cell (for example, all components of the CGI or ECGI).
@@ -1462,6 +1469,10 @@
          * {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE} and
          * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
          *
+         * If the calling app doesn't have {@link android.Manifest.permission#ACCESS_FINE_LOCATION},
+         * it will receive {@link BarringInfo} including {@link CellIdentity} without
+         * location-sensitive information included.
+         *
          * @param barringInfo for all services on the current cell.
          * @see android.telephony.BarringInfo
          */
diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java
index 3b082bc..787ffb7 100644
--- a/core/java/android/view/AttachedSurfaceControl.java
+++ b/core/java/android/view/AttachedSurfaceControl.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
+import android.graphics.Rect;
 import android.graphics.Region;
 import android.hardware.HardwareBuffer;
 import android.window.SurfaceSyncGroup;
@@ -149,4 +150,21 @@
     default SurfaceSyncGroup getOrCreateSurfaceSyncGroup() {
         return null;
     }
+
+    /**
+     * Set a crop region on all children parented to the layer represented by this
+     * AttachedSurfaceControl. This includes SurfaceView, and an example usage may
+     * be to ensure that SurfaceView with {@link android.view.SurfaceView#setZOrderOnTop}
+     * are cropped to a region not including the app bar.
+     *
+     * This cropped is expressed in terms of insets in window-space. Negative insets
+     * are considered invalid and will produce an exception. Insets of zero will produce
+     * the same result as if this function had never been called.
+     *
+     * @param insets The insets in each direction by which to bound the children
+     *               expressed in window-space.
+     * @hide
+     */
+    default void setChildBoundingInsets(@NonNull Rect insets) {
+    }
 }
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 71030bcc..689dce8 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -332,6 +332,34 @@
     public static final int FLAG_OWN_FOCUS = 1 << 11;
 
     /**
+     * Flag: Indicates that the display should not become the top focused display by stealing the
+     * top focus from another display.
+     *
+     * <p>The result is that only targeted input events (displayId of input event matches the
+     * displayId of the display) can reach this display. A display with this flag set can still
+     * become the top focused display, if the system consists of only one display or if all
+     * displays have this flag set. In both cases the default display becomes the top focused
+     * display.
+     *
+     * <p>Note:  A display only has a focused window if either
+     * - the display is the top focused display or
+     * - the display manages its own focus (via {@link #FLAG_OWN_FOCUS})
+     * - or all the displays manage their own focus (via {@code config_perDisplayFocusEnabled} flag)
+     * If a display has no focused window, no input event is dispatched to it. Therefore this
+     * flag is only useful together with {@link #FLAG_OWN_FOCUS} and will be
+     * ignored if it is not set.
+     *
+     * <p>Note: The framework only supports IME on the top focused display (b/262520411). Therefore,
+     * Enabling this flag on a display implicitly disables showing any IME. This is not intended
+     * behavior but cannot be fixed until b/262520411 is implemented. If you need IME on display do
+     * not set this flag.
+     *
+     * @hide
+     * @see #getFlags()
+     */
+    public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 12;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
@@ -1705,6 +1733,16 @@
         return (mFlags & FLAG_TRUSTED) == FLAG_TRUSTED;
     }
 
+    /**
+     * @return {@code true} if the display can steal the top focus from another display.
+     *
+     * @see #FLAG_STEAL_TOP_FOCUS_DISABLED
+     * @hide
+     */
+    public boolean canStealTopFocus() {
+        return (mFlags & FLAG_STEAL_TOP_FOCUS_DISABLED) == 0;
+    }
+
     private void updateDisplayInfoLocked() {
         // Note: The display manager caches display info objects on our behalf.
         DisplayInfo newInfo = mGlobal.getDisplayInfo(mDisplayId);
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index 8e16f24..d554514 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -57,7 +57,7 @@
     void resized(in ClientWindowFrames frames, boolean reportDraw,
             in MergedConfiguration newMergedConfiguration, in InsetsState insetsState,
             boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId,
-            int syncSeqId, int resizeMode);
+            int syncSeqId, boolean dragResizing);
 
     /**
      * Called when this window retrieved control over a specified set of insets sources.
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 6d9f99f..0aba80d 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -457,12 +457,6 @@
     int getDockedStackSide();
 
     /**
-     * Sets the region the user can touch the divider. This region will be excluded from the region
-     * which is used to cause a focus switch when dispatching touch.
-     */
-    void setDockedTaskDividerTouchRegion(in Rect touchableRegion);
-
-    /**
      * Registers a listener that will be called when the pinned task state changes.
      */
     void registerPinnedTaskListener(int displayId, IPinnedTaskListener listener);
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index b8cd7b9..f87b746 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import static android.os.Trace.TRACE_TAG_VIEW;
 import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
 import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
 import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
@@ -23,11 +24,15 @@
 
 import android.annotation.Nullable;
 import android.os.IBinder;
+import android.os.Process;
+import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
+import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 
 import java.util.function.Supplier;
 
@@ -48,8 +53,8 @@
     /**
      * Tracks whether {@link WindowInsetsController#show(int)} or
      * {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
-     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
-     * because the IME is being shown.
+     * If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder,
+     * ImeTracker.Token)}, because the IME is being shown.
      */
     private boolean mIsShowRequestedDuringHideAnimation;
 
@@ -76,7 +81,7 @@
                 // Remove IME surface as IME has finished hide animation, if there is no pending
                 // show request.
                 if (!mIsShowRequestedDuringHideAnimation) {
-                    notifyHidden();
+                    notifyHidden(null /* statsToken */);
                     removeSurface();
                 }
             }
@@ -120,7 +125,8 @@
      * @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
      */
     @Override
-    public @ShowResult int requestShow(boolean fromIme) {
+    @ShowResult
+    public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
         if (fromIme) {
             ImeTracing.getInstance().triggerClientDump(
                     "ImeInsetsSourceConsumer#requestShow",
@@ -129,6 +135,9 @@
 
         // TODO: ResultReceiver for IME.
         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
+        ImeTracker.get().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
+
         if (getControl() == null) {
             // If control is null, schedule to show IME when control is available.
             mIsRequestedVisibleAwaitingControl = true;
@@ -140,16 +149,32 @@
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
-        return getImm().requestImeShow(mController.getHost().getWindowToken())
+        return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
                 ? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
     }
 
     /**
      * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
      * IME insets are hidden.
+     *
+     * @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
      */
-    private void notifyHidden() {
-        getImm().notifyImeHidden(mController.getHost().getWindowToken());
+    private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
+        // Create a new stats token to track the hide request when:
+        //  - we do not already have one, or
+        //  - we do already have one, but we have control and use the passed in token
+        //      for the insets animation already.
+        if (statsToken == null || getControl() != null) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
+                    ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        ImeTracker.get().onProgress(statsToken,
+                ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
+
+        getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
+        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
     }
 
     @Override
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index c074e84..8abe66a 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -41,13 +41,13 @@
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.Trace;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.InsetsSourceConsumer.ShowResult;
 import android.view.InsetsState.InternalInsetsType;
@@ -65,6 +65,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
 
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
@@ -978,7 +979,14 @@
 
     @Override
     public void show(@InsetsType int types) {
-        show(types, false /* fromIme */, null /* statsToken */);
+        ImeTracker.Token statsToken = null;
+        if ((types & ime()) != 0) {
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        show(types, false /* fromIme */, statsToken);
     }
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1055,7 +1063,14 @@
 
     @Override
     public void hide(@InsetsType int types) {
-        hide(types, false /* fromIme */, null /* statsToken */);
+        ImeTracker.Token statsToken = null;
+        if ((types & ime()) != 0) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
+
+        hide(types, false /* fromIme */, statsToken);
     }
 
     @VisibleForTesting
@@ -1165,13 +1180,17 @@
             if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
             types &= ~mDisabledUserAnimationInsetsTypes;
 
-            if (fromIme && (disabledTypes & ime()) != 0
-                    && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
-                // We've requested IMM to show IME, but the IME is not controllable. We need to
-                // cancel the request.
-                setRequestedVisibleTypes(0 /* visibleTypes */, ime());
-                if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
-                    notifyVisibilityChanged();
+            if ((disabledTypes & ime()) != 0) {
+                ImeTracker.get().onFailed(statsToken,
+                        ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
+                if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
+                    // We've requested IMM to show IME, but the IME is not controllable. We need to
+                    // cancel the request.
+                    setRequestedVisibleTypes(0 /* visibleTypes */, ime());
+                    if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
+                        notifyVisibilityChanged();
+                    }
                 }
             }
         }
@@ -1179,8 +1198,12 @@
             // nothing to animate.
             listener.onCancelled(null);
             if (DEBUG) Log.d(TAG, "no types to animate in controlAnimationUnchecked");
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
             return;
         }
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
+
         cancelExistingControllers(types);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
@@ -1188,7 +1211,7 @@
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
 
         Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
-                fromIme, types, controls, animationType);
+                fromIme, types, controls, animationType, statsToken);
         int typesReady = typesReadyPair.first;
         boolean imeReady = typesReadyPair.second;
         if (DEBUG) Log.d(TAG, String.format(
@@ -1218,12 +1241,20 @@
             setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);
 
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
         if (typesReady == 0) {
             if (DEBUG) Log.d(TAG, "No types ready. onCancelled()");
             listener.onCancelled(null);
+            reportRequestedVisibleTypes();
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
@@ -1279,7 +1310,10 @@
      * @return Pair of (types ready to animate, IME ready to animate).
      */
     private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
-            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
+            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
+            @Nullable ImeTracker.Token statsToken) {
+        ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
+
         int typesReady = 0;
         boolean imeReady = true;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1292,7 +1326,7 @@
             boolean canRun = true;
             if (show) {
                 // Show request
-                switch(consumer.requestShow(fromIme)) {
+                switch(consumer.requestShow(fromIme, statsToken)) {
                     case ShowResult.SHOW_IMMEDIATELY:
                         break;
                     case ShowResult.IME_SHOW_DELAYED:
@@ -1571,6 +1605,10 @@
         if (types == 0) {
             // nothing to animate.
             if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate");
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index f46eb34..47672a3 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -35,6 +35,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.inputmethod.ImeTracker;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -50,10 +51,14 @@
 public class InsetsSourceConsumer {
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
+    @IntDef(value = {
+            ShowResult.SHOW_IMMEDIATELY,
+            ShowResult.IME_SHOW_DELAYED,
+            ShowResult.IME_SHOW_FAILED
+    })
     @interface ShowResult {
         /**
-         * Window type is ready to be shown, will be shown immidiately.
+         * Window type is ready to be shown, will be shown immediately.
          */
         int SHOW_IMMEDIATELY = 0;
         /**
@@ -71,11 +76,13 @@
     protected final InsetsController mController;
     protected final InsetsState mState;
     private int mId;
-    private final @InsetsType int mType;
+    @InsetsType
+    private final int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
     private final Supplier<Transaction> mTransactionSupplier;
-    private @Nullable InsetsSourceControl mSourceControl;
+    @Nullable
+    private InsetsSourceControl mSourceControl;
     private boolean mHasWindowFocus;
 
     /**
@@ -180,7 +187,7 @@
         return true;
     }
 
-    @VisibleForTesting
+    @VisibleForTesting(visibility = PACKAGE)
     public InsetsSourceControl getControl() {
         return mSourceControl;
     }
@@ -280,10 +287,16 @@
      * @param fromController {@code true} if request is coming from controller.
      *                       (e.g. in IME case, controller is
      *                       {@link android.inputmethodservice.InputMethodService}).
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
+     *
+     * @implNote The {@code statsToken} is ignored here, and only handled in
+     * {@link ImeInsetsSourceConsumer} for IME animations only.
+     *
      * @return @see {@link ShowResult}.
      */
-    @VisibleForTesting
-    public @ShowResult int requestShow(boolean fromController) {
+    @VisibleForTesting(visibility = PACKAGE)
+    @ShowResult
+    public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
         return ShowResult.SHOW_IMMEDIATELY;
     }
 
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 5e8e191..5c23c31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -27,12 +27,14 @@
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.Size;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -3166,6 +3168,7 @@
           *
           * @hide
           */
+        @RequiresPermission(Manifest.permission.WAKEUP_SURFACE_FLINGER)
         public Transaction setEarlyWakeupStart() {
             nativeSetEarlyWakeupStart(mNativeObject);
             return this;
@@ -3176,6 +3179,7 @@
          *
          * @hide
          */
+        @RequiresPermission(Manifest.permission.WAKEUP_SURFACE_FLINGER)
         public Transaction setEarlyWakeupEnd() {
             nativeSetEarlyWakeupEnd(mNativeObject);
             return this;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ff4ea81..52232f8 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -51,8 +51,6 @@
 import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES;
 import static android.view.ViewRootImplProto.WIN_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
-import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
-import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -515,7 +513,6 @@
     private boolean mPendingDragResizing;
     private boolean mDragResizing;
     private boolean mInvalidateRootRequested;
-    private int mResizeMode = RESIZE_MODE_INVALID;
     private int mCanvasOffsetX;
     private int mCanvasOffsetY;
     private boolean mActivityRelaunched;
@@ -922,6 +919,8 @@
                 }
             }
         };
+    private final Rect mChildBoundingInsets = new Rect();
+    private boolean mChildBoundingInsetsChanged = false;
 
     private String mTag = TAG;
 
@@ -1806,7 +1805,7 @@
         CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
         final boolean forceNextWindowRelayout = args.argi1 != 0;
         final int displayId = args.argi3;
-        final int resizeMode = args.argi5;
+        final boolean dragResizing = args.argi5 != 0;
 
         final Rect frame = frames.frame;
         final Rect displayFrame = frames.displayFrame;
@@ -1822,16 +1821,14 @@
         final boolean attachedFrameChanged = LOCAL_LAYOUT
                 && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
         final boolean displayChanged = mDisplay.getDisplayId() != displayId;
-        final boolean resizeModeChanged = mResizeMode != resizeMode;
         final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
         if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
-                && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout
+                && !displayChanged && !forceNextWindowRelayout
                 && !compatScaleChanged) {
             return;
         }
 
-        mPendingDragResizing = resizeMode != RESIZE_MODE_INVALID;
-        mResizeMode = resizeMode;
+        mPendingDragResizing = dragResizing;
         mTmpFrames.compatScale = compatScale;
         mInvCompatScale = 1f / compatScale;
 
@@ -2227,6 +2224,8 @@
         mTempRect.inset(mWindowAttributes.surfaceInsets.left,
                 mWindowAttributes.surfaceInsets.top,
                 mWindowAttributes.surfaceInsets.right, mWindowAttributes.surfaceInsets.bottom);
+        mTempRect.inset(mChildBoundingInsets.left, mChildBoundingInsets.top,
+            mChildBoundingInsets.right, mChildBoundingInsets.bottom);
         t.setWindowCrop(mBoundsLayer, mTempRect);
     }
 
@@ -2248,7 +2247,7 @@
         if (!sc.isValid()) return;
 
         if (updateBoundsLayer(t)) {
-              mergeWithNextTransaction(t, mSurface.getNextFrameNumber());
+            applyTransactionOnDraw(t);
         }
     }
 
@@ -2334,7 +2333,6 @@
      */
     void notifyRendererOfFramePending() {
         if (mAttachInfo.mThreadedRenderer != null) {
-            mAttachInfo.mThreadedRenderer.notifyCallbackPending();
             mAttachInfo.mThreadedRenderer.notifyFramePending();
         }
     }
@@ -3028,7 +3026,7 @@
                         frame.width() < desiredWindowWidth && frame.width() != mWidth)
                 || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                         frame.height() < desiredWindowHeight && frame.height() != mHeight));
-        windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
+        windowShouldResize |= mDragResizing && mPendingDragResizing;
 
         // If the activity was just relaunched, it might have unfrozen the task bounds (while
         // relaunching), so we need to force a call into window manager to pick up the latest
@@ -3275,7 +3273,7 @@
                                         && mWinFrame.height() == mPendingBackDropFrame.height();
                         // TODO: Need cutout?
                         startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
-                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mResizeMode);
+                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets);
                     } else {
                         // We shouldn't come here, but if we come we should end the resize.
                         endDragResizing();
@@ -3452,7 +3450,8 @@
             }
         }
 
-        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated || windowAttributesChanged) {
+        if (surfaceSizeChanged || surfaceReplaced || surfaceCreated ||
+            windowAttributesChanged || mChildBoundingInsetsChanged) {
             // If the surface has been replaced, there's a chance the bounds layer is not parented
             // to the new layer. When updating bounds layer, also reparent to the main VRI
             // SurfaceControl to ensure it's correctly placed in the hierarchy.
@@ -3463,6 +3462,7 @@
             // enough. WMS doesn't want to keep around old children since they will leak when the
             // client creates new children.
             prepareSurfaces();
+            mChildBoundingInsetsChanged = false;
         }
 
         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
@@ -8841,7 +8841,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private void dispatchResized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, int resizeMode) {
+            boolean alwaysConsumeSystemBars, int displayId, int syncSeqId, boolean dragResizing) {
         Message msg = mHandler.obtainMessage(reportDraw ? MSG_RESIZED_REPORT : MSG_RESIZED);
         SomeArgs args = SomeArgs.obtain();
         final boolean sameProcessCall = (Binder.getCallingPid() == android.os.Process.myPid());
@@ -8863,7 +8863,7 @@
         args.argi2 = alwaysConsumeSystemBars ? 1 : 0;
         args.argi3 = displayId;
         args.argi4 = syncSeqId;
-        args.argi5 = resizeMode;
+        args.argi5 = dragResizing ? 1 : 0;
 
         msg.obj = args;
         mHandler.sendMessage(msg);
@@ -10255,11 +10255,11 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
-                int resizeMode) {
+                boolean dragResizing) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
                 viewAncestor.dispatchResized(frames, reportDraw, mergedConfiguration, insetsState,
-                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, resizeMode);
+                        forceLayout, alwaysConsumeSystemBars, displayId, syncSeqId, dragResizing);
             }
         }
 
@@ -10464,13 +10464,13 @@
      * Start a drag resizing which will inform all listeners that a window resize is taking place.
      */
     private void startDragResizing(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets, int resizeMode) {
+            Rect stableInsets) {
         if (!mDragResizing) {
             mDragResizing = true;
             if (mUseMTRenderer) {
                 for (int i = mWindowCallbacks.size() - 1; i >= 0; i--) {
                     mWindowCallbacks.get(i).onWindowDragResizeStart(
-                            initialBounds, fullscreen, systemInsets, stableInsets, resizeMode);
+                            initialBounds, fullscreen, systemInsets, stableInsets);
                 }
             }
             mFullRedrawNeeded = true;
@@ -11083,7 +11083,7 @@
     @Nullable public SurfaceControl.Transaction buildReparentTransaction(
         @NonNull SurfaceControl child) {
         if (mSurfaceControl.isValid()) {
-            return new SurfaceControl.Transaction().reparent(child, mSurfaceControl);
+          return new SurfaceControl.Transaction().reparent(child, getBoundsLayer());
         }
         return null;
     }
@@ -11339,4 +11339,14 @@
         }
         mActiveSurfaceSyncGroup.addToSync(syncable, false /* parentSyncGroupMerge */);
     }
+
+    @Override
+    public void setChildBoundingInsets(@NonNull Rect insets) {
+        if (insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0) {
+            throw new IllegalArgumentException("Negative insets passed to setChildBoundingInsets.");
+        }
+        mChildBoundingInsets.set(insets);
+        mChildBoundingInsetsChanged = true;
+        scheduleTraversals();
+    }
 }
diff --git a/core/java/android/view/WindowCallbacks.java b/core/java/android/view/WindowCallbacks.java
index a7f0ef0..94b2d93 100644
--- a/core/java/android/view/WindowCallbacks.java
+++ b/core/java/android/view/WindowCallbacks.java
@@ -28,22 +28,6 @@
  */
 public interface WindowCallbacks {
 
-    int RESIZE_MODE_INVALID = -1;
-
-    /**
-     * The window is being resized by dragging one of the window corners,
-     * in this case the surface would be fullscreen-sized. The client should
-     * render to the actual frame location (instead of (0,curScrollY)).
-     */
-    int RESIZE_MODE_FREEFORM = 0;
-
-    /**
-     * The window is being resized by dragging on the docked divider. The client should render
-     * at (0, 0) and extend its background to the background frame passed into
-     * {@link IWindow#resized}.
-     */
-    int RESIZE_MODE_DOCKED_DIVIDER = 1;
-
     /**
      * Called by the system when the window got changed by the user, before the layouter got called.
      * It also gets called when the insets changed, or when the window switched between a fullscreen
@@ -69,7 +53,7 @@
      * @param stableInsets The stable insets for the window.
      */
     void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets, int resizeMode);
+            Rect stableInsets);
 
     /**
      * Called when a drag resize ends.
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 43d427d..9472d86 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -55,13 +55,6 @@
     int PRESENCE_INTERNAL = 1 << 0;
     int PRESENCE_EXTERNAL = 1 << 1;
 
-    // Alternate bars position values
-    int ALT_BAR_UNKNOWN = -1;
-    int ALT_BAR_LEFT = 1 << 0;
-    int ALT_BAR_RIGHT = 1 << 1;
-    int ALT_BAR_BOTTOM = 1 << 2;
-    int ALT_BAR_TOP = 1 << 3;
-
     // Navigation bar position values
     int NAV_BAR_INVALID = -1;
     int NAV_BAR_LEFT = 1 << 0;
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index a9fea017..4c95728 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,8 +16,6 @@
 
 package android.view;
 
-import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
-
 import android.annotation.Nullable;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
@@ -552,7 +550,7 @@
                 mTmpConfig.setConfiguration(mConfiguration, mConfiguration);
                 s.mClient.resized(mTmpFrames, false /* reportDraw */, mTmpConfig, state,
                         false /* forceLayout */, false /* alwaysConsumeSystemBars */, s.mDisplayId,
-                        Integer.MAX_VALUE, RESIZE_MODE_INVALID);
+                        Integer.MAX_VALUE, false /* dragResizing */);
             } catch (RemoteException e) {
                 // Too bad
             }
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index 313dc43..15ba1a1 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -22,6 +22,8 @@
 import android.os.Parcelable;
 import android.view.View;
 
+import java.util.Objects;
+
 /**
  * A unique identifier for an autofill node inside an {@link android.app.Activity}.
  */
@@ -76,6 +78,23 @@
     @NonNull
     public static final AutofillId NO_AUTOFILL_ID = new AutofillId(0);
 
+    /**
+     * Creates an {@link AutofillId} with the virtual id.
+     *
+     * This method is used by a {@link View} that contains the virtual view hierarchy. Use this
+     * method to create the {@link AutofillId} for each virtual view.
+     *
+     * @param host the view hosting the virtual view hierarchy which is used to show autofill
+     *            suggestions.
+     * @param virtualId id identifying the virtual view inside the host view.
+     * @return an {@link AutofillId} for the virtual view
+     */
+    @NonNull
+    public static AutofillId create(@NonNull View host, int virtualId) {
+        Objects.requireNonNull(host);
+        return new AutofillId(host.getAutofillId(), virtualId);
+    }
+
     /** @hide */
     @NonNull
     @TestApi
diff --git a/core/java/android/view/inputmethod/HandwritingGesture.java b/core/java/android/view/inputmethod/HandwritingGesture.java
index 07b1e1f..2516269 100644
--- a/core/java/android/view/inputmethod/HandwritingGesture.java
+++ b/core/java/android/view/inputmethod/HandwritingGesture.java
@@ -159,7 +159,7 @@
      * @hide
      */
     @TestApi
-    public @GestureType int getGestureType() {
+    public final @GestureType int getGestureType() {
         return mType;
     }
 
@@ -173,7 +173,7 @@
      * example 2: join can fail if the gesture is drawn over text but there is no whitespace.
      */
     @Nullable
-    public String getFallbackText() {
+    public final String getFallbackText() {
         return mFallbackText;
     }
 }
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
index 6eae63a..f08f61f 100644
--- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -40,6 +40,7 @@
 import com.android.internal.inputmethod.SoftInputShowHideReason;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 
 import java.util.ArrayList;
@@ -61,6 +62,9 @@
     @Nullable
     private static volatile IInputMethodManager sServiceCache = null;
 
+    @Nullable
+    private static volatile IImeTracker sTrackerServiceCache = null;
+
     /**
      * @return {@code true} if {@link IInputMethodManager} is available.
      */
@@ -527,4 +531,137 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    @AnyThread
+    @Nullable
+    static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.onRequestShow(uid, origin, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.onRequestHide(uid, origin, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onProgress(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onFailed(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onCancelled(statsToken, phase);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onShown(@NonNull IBinder statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onShown(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void onHidden(@NonNull IBinder statsToken) {
+        final IImeTracker service = getImeTrackerService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.onHidden(statsToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static boolean hasPendingImeVisibilityRequests() {
+        final var service = getImeTrackerService();
+        if (service == null) {
+            return true;
+        }
+        try {
+            return service.hasPendingImeVisibilityRequests();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    private static IImeTracker getImeTrackerService() {
+        var trackerService = sTrackerServiceCache;
+        if (trackerService == null) {
+            final var service = getService();
+            if (service == null) {
+                return null;
+            }
+
+            try {
+                trackerService = service.getImeTrackerService();
+                if (trackerService == null) {
+                    return null;
+                }
+
+                sTrackerServiceCache = trackerService;
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        return trackerService;
+    }
 }
diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java
index 927d769..1bcb040 100644
--- a/core/java/android/view/inputmethod/ImeTracker.java
+++ b/core/java/android/view/inputmethod/ImeTracker.java
@@ -16,9 +16,6 @@
 
 package android.view.inputmethod;
 
-import static android.view.inputmethod.ImeTracker.Debug.originToString;
-import static android.view.inputmethod.ImeTracker.Debug.phaseToString;
-
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,6 +43,46 @@
 
     String TAG = "ImeTracker";
 
+    /** The type of the IME request. */
+    @IntDef(prefix = { "TYPE_" }, value = {
+            TYPE_SHOW,
+            TYPE_HIDE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Type {}
+
+    /** IME show request type. */
+    int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
+
+    /** IME hide request type. */
+    int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
+
+    /** The status of the IME request. */
+    @IntDef(prefix = { "STATUS_" }, value = {
+            STATUS_RUN,
+            STATUS_CANCEL,
+            STATUS_FAIL,
+            STATUS_SUCCESS,
+            STATUS_TIMEOUT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Status {}
+
+    /** IME request running. */
+    int STATUS_RUN = ImeProtoEnums.STATUS_RUN;
+
+    /** IME request cancelled. */
+    int STATUS_CANCEL = ImeProtoEnums.STATUS_CANCEL;
+
+    /** IME request failed. */
+    int STATUS_FAIL = ImeProtoEnums.STATUS_FAIL;
+
+    /** IME request succeeded. */
+    int STATUS_SUCCESS = ImeProtoEnums.STATUS_SUCCESS;
+
+    /** IME request timed out. */
+    int STATUS_TIMEOUT = ImeProtoEnums.STATUS_TIMEOUT;
+
     /**
      * The origin of the IME request
      *
@@ -61,25 +98,17 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface Origin {}
 
-    /**
-     * The IME show request originated in the client.
-     */
-    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
+    /** The IME show request originated in the client. */
+    int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT;
 
-    /**
-     * The IME hide request originated in the client.
-     */
-    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
+    /** The IME hide request originated in the client. */
+    int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT;
 
-    /**
-     * The IME show request originated in the server.
-     */
-    int ORIGIN_SERVER_START_INPUT = 2;
+    /** The IME show request originated in the server. */
+    int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT;
 
-    /**
-     * The IME hide request originated in the server.
-     */
-    int ORIGIN_SERVER_HIDE_INPUT = 3;
+    /** The IME hide request originated in the server. */
+    int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT;
 
     /**
      * The current phase of the IME request.
@@ -88,6 +117,7 @@
      * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server).
      */
     @IntDef(prefix = { "PHASE_" }, value = {
+            PHASE_NOT_SET,
             PHASE_CLIENT_VIEW_SERVED,
             PHASE_SERVER_CLIENT_KNOWN,
             PHASE_SERVER_CLIENT_FOCUSED,
@@ -121,6 +151,11 @@
             PHASE_CLIENT_HANDLE_HIDE_INSETS,
             PHASE_CLIENT_APPLY_ANIMATION,
             PHASE_CLIENT_CONTROL_ANIMATION,
+            PHASE_CLIENT_DISABLED_USER_ANIMATION,
+            PHASE_CLIENT_COLLECT_SOURCE_CONTROLS,
+            PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
+            PHASE_CLIENT_REQUEST_IME_SHOW,
+            PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN,
             PHASE_CLIENT_ANIMATION_RUNNING,
             PHASE_CLIENT_ANIMATION_CANCEL,
             PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
@@ -129,135 +164,172 @@
     @Retention(RetentionPolicy.SOURCE)
     @interface Phase {}
 
+    int PHASE_NOT_SET = ImeProtoEnums.PHASE_NOT_SET;
+
     /** The view that requested the IME has been served by the IMM. */
-    int PHASE_CLIENT_VIEW_SERVED = 0;
+    int PHASE_CLIENT_VIEW_SERVED = ImeProtoEnums.PHASE_CLIENT_VIEW_SERVED;
 
     /** The IME client that requested the IME has window manager focus. */
-    int PHASE_SERVER_CLIENT_KNOWN = 1;
+    int PHASE_SERVER_CLIENT_KNOWN = ImeProtoEnums.PHASE_SERVER_CLIENT_KNOWN;
 
     /** The IME client that requested the IME has IME focus. */
-    int PHASE_SERVER_CLIENT_FOCUSED = 2;
+    int PHASE_SERVER_CLIENT_FOCUSED = ImeProtoEnums.PHASE_SERVER_CLIENT_FOCUSED;
 
     /** The IME request complies with the current accessibility settings. */
-    int PHASE_SERVER_ACCESSIBILITY = 3;
+    int PHASE_SERVER_ACCESSIBILITY = ImeProtoEnums.PHASE_SERVER_ACCESSIBILITY;
 
     /** The server is ready to run third party code. */
-    int PHASE_SERVER_SYSTEM_READY = 4;
+    int PHASE_SERVER_SYSTEM_READY = ImeProtoEnums.PHASE_SERVER_SYSTEM_READY;
 
     /** Checked the implicit hide request against any explicit show requests. */
-    int PHASE_SERVER_HIDE_IMPLICIT = 5;
+    int PHASE_SERVER_HIDE_IMPLICIT = ImeProtoEnums.PHASE_SERVER_HIDE_IMPLICIT;
 
     /** Checked the not-always hide request against any forced show requests. */
-    int PHASE_SERVER_HIDE_NOT_ALWAYS = 6;
+    int PHASE_SERVER_HIDE_NOT_ALWAYS = ImeProtoEnums.PHASE_SERVER_HIDE_NOT_ALWAYS;
 
     /** The server is waiting for a connection to the IME. */
-    int PHASE_SERVER_WAIT_IME = 7;
+    int PHASE_SERVER_WAIT_IME = ImeProtoEnums.PHASE_SERVER_WAIT_IME;
 
     /** The server has a connection to the IME. */
-    int PHASE_SERVER_HAS_IME = 8;
+    int PHASE_SERVER_HAS_IME = ImeProtoEnums.PHASE_SERVER_HAS_IME;
 
     /** The server decided the IME should be hidden. */
-    int PHASE_SERVER_SHOULD_HIDE = 9;
+    int PHASE_SERVER_SHOULD_HIDE = ImeProtoEnums.PHASE_SERVER_SHOULD_HIDE;
 
     /** Reached the IME wrapper. */
-    int PHASE_IME_WRAPPER = 10;
+    int PHASE_IME_WRAPPER = ImeProtoEnums.PHASE_IME_WRAPPER;
 
     /** Dispatched from the IME wrapper to the IME. */
-    int PHASE_IME_WRAPPER_DISPATCH = 11;
+    int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
 
     /** Reached the IME' showSoftInput method. */
-    int PHASE_IME_SHOW_SOFT_INPUT = 12;
+    int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
 
     /** Reached the IME' hideSoftInput method. */
-    int PHASE_IME_HIDE_SOFT_INPUT = 13;
+    int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT;
 
     /** The server decided the IME should be shown. */
-    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14;
+    int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
 
     /** Requested applying the IME visibility in the insets source consumer. */
-    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
+    int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER =
+            ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER;
 
     /** Applied the IME visibility. */
-    int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
+    int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
 
     /** Created the show IME runner. */
-    int PHASE_WM_SHOW_IME_RUNNER = 17;
+    int PHASE_WM_SHOW_IME_RUNNER = ImeProtoEnums.PHASE_WM_SHOW_IME_RUNNER;
 
     /** Ready to show IME. */
-    int PHASE_WM_SHOW_IME_READY = 18;
+    int PHASE_WM_SHOW_IME_READY = ImeProtoEnums.PHASE_WM_SHOW_IME_READY;
 
     /** The Window Manager has a connection to the IME insets control target. */
-    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19;
+    int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET =
+            ImeProtoEnums.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET;
 
     /** Reached the window insets control target's show insets method. */
-    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20;
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS =
+            ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS;
 
     /** Reached the window insets control target's hide insets method. */
-    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21;
+    int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS =
+            ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS;
 
     /** Reached the remote insets control target's show insets method. */
-    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22;
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS =
+            ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS;
 
     /** Reached the remote insets control target's hide insets method. */
-    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23;
+    int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS =
+            ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS;
 
     /** Reached the remote insets controller. */
-    int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24;
+    int PHASE_WM_REMOTE_INSETS_CONTROLLER = ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROLLER;
 
     /** Created the IME window insets show animation. */
-    int PHASE_WM_ANIMATION_CREATE = 25;
+    int PHASE_WM_ANIMATION_CREATE = ImeProtoEnums.PHASE_WM_ANIMATION_CREATE;
 
     /** Started the IME window insets show animation. */
-    int PHASE_WM_ANIMATION_RUNNING = 26;
+    int PHASE_WM_ANIMATION_RUNNING = ImeProtoEnums.PHASE_WM_ANIMATION_RUNNING;
 
     /** Reached the client's show insets method. */
-    int PHASE_CLIENT_SHOW_INSETS = 27;
+    int PHASE_CLIENT_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_SHOW_INSETS;
 
     /** Reached the client's hide insets method. */
-    int PHASE_CLIENT_HIDE_INSETS = 28;
+    int PHASE_CLIENT_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HIDE_INSETS;
 
     /** Handling the IME window insets show request. */
-    int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
+    int PHASE_CLIENT_HANDLE_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_SHOW_INSETS;
 
     /** Handling the IME window insets hide request. */
-    int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30;
+    int PHASE_CLIENT_HANDLE_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_HIDE_INSETS;
 
     /** Applied the IME window insets show animation. */
-    int PHASE_CLIENT_APPLY_ANIMATION = 31;
+    int PHASE_CLIENT_APPLY_ANIMATION = ImeProtoEnums.PHASE_CLIENT_APPLY_ANIMATION;
 
     /** Started the IME window insets show animation. */
-    int PHASE_CLIENT_CONTROL_ANIMATION = 32;
+    int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
+
+    /** Checked that the IME is controllable. */
+    int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_ANIMATION;
+
+    /** Collecting insets source controls. */
+    int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
+
+    /** Reached the insets source consumer's show request method. */
+    int PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW =
+            ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW;
+
+    /** Reached input method manager's request IME show method. */
+    int PHASE_CLIENT_REQUEST_IME_SHOW = ImeProtoEnums.PHASE_CLIENT_REQUEST_IME_SHOW;
+
+    /** Reached the insets source consumer's notify hidden method. */
+    int PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN =
+            ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN;
 
     /** Queued the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_RUNNING = 33;
+    int PHASE_CLIENT_ANIMATION_RUNNING = ImeProtoEnums.PHASE_CLIENT_ANIMATION_RUNNING;
 
     /** Cancelled the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_CANCEL = 34;
+    int PHASE_CLIENT_ANIMATION_CANCEL = ImeProtoEnums.PHASE_CLIENT_ANIMATION_CANCEL;
 
     /** Finished the IME window insets show animation. */
-    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
+    int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_SHOW;
 
     /** Finished the IME window insets hide animation. */
-    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36;
+    int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_HIDE;
 
     /**
-     * Called when an IME show request is created.
+     * Creates an IME show request tracking token.
      *
-     * @param token the token tracking the current IME show request or {@code null} otherwise.
+     * @param component the component name where the IME show request was created,
+     *                  or {@code null} otherwise
+     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME show request.
      * @param reason the reason why the IME show request was created.
+     *
+     * @return An IME tracking token.
      */
-    void onRequestShow(@Nullable Token token, @Origin int origin,
+    @NonNull
+    Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
             @SoftInputShowHideReason int reason);
 
     /**
-     * Called when an IME hide request is created.
+     * Creates an IME hide request tracking token.
      *
-     * @param token the token tracking the current IME hide request or {@code null} otherwise.
+     * @param component the component name where the IME hide request was created,
+     *                  or {@code null} otherwise
+     *                  (defaulting to {@link ActivityThread#currentProcessName()}).
+     * @param uid the uid of the client that requested the IME.
      * @param origin the origin of the IME hide request.
      * @param reason the reason why the IME hide request was created.
+     *
+     * @return An IME tracking token.
      */
-    void onRequestHide(@Nullable Token token, @Origin int origin,
+    @NonNull
+    Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
             @SoftInputShowHideReason int reason);
 
     /**
@@ -313,112 +385,122 @@
      */
     @NonNull
     static ImeTracker get() {
-        return SystemProperties.getBoolean("persist.debug.imetracker", false)
-                ? LOGGER
-                : NOOP_LOGGER;
+        return LOGGER;
     }
 
     /** The singleton IME tracker instance. */
+    @NonNull
     ImeTracker LOGGER = new ImeTracker() {
 
+        {
+            // Set logging flag initial value.
+            mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
+            // Update logging flag dynamically.
+            SystemProperties.addChangeCallback(() ->
+                    mLogProgress =
+                            SystemProperties.getBoolean("persist.debug.imetracker", false));
+        }
+
+        /** Whether progress should be logged. */
+        private boolean mLogProgress;
+
+        @NonNull
         @Override
-        public void onRequestShow(@Nullable Token token, int origin,
+        public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            if (token == null) return;
-            Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
+            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
+            if (binder == null) binder = new Binder();
+
+            final Token token = Token.build(binder, component);
+
+            Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+            return token;
         }
 
+        @NonNull
         @Override
-        public void onRequestHide(@Nullable Token token, int origin,
+        public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
                 @SoftInputShowHideReason int reason) {
-            if (token == null) return;
-            Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
+            IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
+            if (binder == null) binder = new Binder();
+
+            final Token token = Token.build(binder, component);
+
+            Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
                     + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
+
+            return token;
         }
 
         @Override
-        public void onProgress(@Nullable Token token, int phase) {
+        public void onProgress(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onProgress(token.mBinder, phase);
+
+            if (mLogProgress) {
+                Log.i(TAG, token.mTag + ": onProgress at " + Debug.phaseToString(phase));
+            }
         }
 
         @Override
-        public void onFailed(@Nullable Token token, int phase) {
+        public void onFailed(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
+
+            Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
         }
 
         @Override
-        public void onTodo(@Nullable Token token, int phase) {
+        public void onTodo(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
+            Log.i(TAG, token.mTag + ": onTodo at " + Debug.phaseToString(phase));
         }
 
         @Override
-        public void onCancelled(@Nullable Token token, int phase) {
+        public void onCancelled(@Nullable Token token, @Phase int phase) {
             if (token == null) return;
-            Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
+            IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase);
+
+            Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
         }
 
         @Override
         public void onShown(@Nullable Token token) {
             if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onShown(token.mBinder);
+
             Log.i(TAG, token.mTag + ": onShown");
         }
 
         @Override
         public void onHidden(@Nullable Token token) {
             if (token == null) return;
+            IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
+
             Log.i(TAG, token.mTag + ": onHidden");
         }
     };
 
-    /** The singleton no-op IME tracker instance. */
-    ImeTracker NOOP_LOGGER = new ImeTracker() {
-
-        @Override
-        public void onRequestShow(@Nullable Token token, int origin,
-                @SoftInputShowHideReason int reason) {}
-
-        @Override
-        public void onRequestHide(@Nullable Token token, int origin,
-                @SoftInputShowHideReason int reason) {}
-
-        @Override
-        public void onProgress(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onFailed(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onTodo(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onCancelled(@Nullable Token token, int phase) {}
-
-        @Override
-        public void onShown(@Nullable Token token) {}
-
-        @Override
-        public void onHidden(@Nullable Token token) {}
-    };
-
     /** A token that tracks the progress of an IME request. */
     class Token implements Parcelable {
 
-        private final IBinder mBinder;
+        @NonNull
+        public final IBinder mBinder;
+
+        @NonNull
         private final String mTag;
 
-        public Token() {
-            this(ActivityThread.currentProcessName());
+        @NonNull
+        private static Token build(@NonNull IBinder binder, @Nullable String component) {
+            if (component == null) component = ActivityThread.currentProcessName();
+            final String tag = component + ":" + Integer.toHexString((new Random().nextInt()));
+
+            return new Token(binder, tag);
         }
 
-        public Token(String component) {
-            this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
-        }
-
-        private Token(IBinder binder, String tag) {
+        private Token(@NonNull IBinder binder, @NonNull String tag) {
             mBinder = binder;
             mTag = tag;
         }
@@ -443,10 +525,11 @@
 
         @NonNull
         public static final Creator<Token> CREATOR = new Creator<>() {
+            @NonNull
             @Override
             public Token createFromParcel(Parcel source) {
-                IBinder binder = source.readStrongBinder();
-                String tag = source.readString8();
+                final IBinder binder = source.readStrongBinder();
+                final String tag = source.readString8();
                 return new Token(binder, tag);
             }
 
@@ -458,22 +541,34 @@
     }
 
     /**
-     * Utilities for mapping phases and origins IntDef values to their names.
+     * Utilities for mapping IntDef values to their names.
      *
      * Note: This is held in a separate class so that it only gets initialized when actually needed.
      */
     class Debug {
 
+        private static final Map<Integer, String> sTypes =
+                getFieldMapping(ImeTracker.class, "TYPE_");
+        private static final Map<Integer, String> sStatus =
+                getFieldMapping(ImeTracker.class, "STATUS_");
         private static final Map<Integer, String> sOrigins =
                 getFieldMapping(ImeTracker.class, "ORIGIN_");
         private static final Map<Integer, String> sPhases =
                 getFieldMapping(ImeTracker.class, "PHASE_");
 
-        public static String originToString(int origin) {
+        public static String typeToString(@Type int type) {
+            return sTypes.getOrDefault(type, "TYPE_" + type);
+        }
+
+        public static String statusToString(@Status int status) {
+            return sStatus.getOrDefault(status, "STATUS_" + status);
+        }
+
+        public static String originToString(@Origin int origin) {
             return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
         }
 
-        public static String phaseToString(int phase) {
+        public static String phaseToString(@Phase int phase) {
             return sPhases.getOrDefault(phase, "PHASE_" + phase);
         }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ee31fd5..e9f0d29 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2002,14 +2002,16 @@
      * {@link #RESULT_HIDDEN}.
      */
     public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
-        return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
+        return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
+                SoftInputShowHideReason.SHOW_SOFT_INPUT);
     }
 
-    private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
-                reason);
+    private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
+            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
+        if (statsToken == null) {
+            statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
+        }
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                 null /* icProto */);
@@ -2057,8 +2059,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
     public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) {
         synchronized (mH) {
-            final ImeTracker.Token statsToken = new ImeTracker.Token();
-            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
+            final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT);
 
             Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
@@ -2148,9 +2150,8 @@
 
     private boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                reason);
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                 this, null /* icProto */);
@@ -2283,7 +2284,7 @@
                     hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
                             SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
                 } else {
-                    showSoftInput(view, showFlags, null,
+                    showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */,
                             SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
                 }
             }
@@ -2460,6 +2461,7 @@
         final EditorInfo editorInfo = connectionPair.second;
         final Handler icHandler;
         InputBindResult res = null;
+        final boolean hasServedView;
         synchronized (mH) {
             // Now that we are locked again, validate that our state hasn't
             // changed.
@@ -2591,6 +2593,7 @@
             switch (res.result) {
                 case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                     mRestartOnNextWindowFocus = true;
+                    mServedView = null;
                     break;
             }
             if (mCompletions != null) {
@@ -2598,10 +2601,11 @@
                     mCurBindState.mImeSession.displayCompletions(mCompletions);
                 }
             }
+            hasServedView = mServedView != null;
         }
 
         // Notify the app that the InputConnection is initialized and ready for use.
-        if (ic != null && res != null && res.method != null) {
+        if (ic != null && res != null && res.method != null && hasServedView) {
             if (DEBUG) {
                 Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
                         + ", ic=" + ic + ", editorInfo=" + editorInfo + ", handler=" + icHandler);
@@ -2793,8 +2797,8 @@
 
     @UnsupportedAppUsage
     void closeCurrentInput() {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+        final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
                 SoftInputShowHideReason.HIDE_SOFT_INPUT);
 
         synchronized (mH) {
@@ -2853,18 +2857,23 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored and returns {@code false}.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      *
      * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
      * @hide
      */
-    public boolean requestImeShow(IBinder windowToken) {
+    public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
         checkFocus();
         synchronized (mH) {
             final View servedView = getServedViewLocked();
             if (servedView == null || servedView.getWindowToken() != windowToken) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
                 return false;
             }
-            showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */,
+
+            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
+
+            showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
                     SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
             return true;
         }
@@ -2875,12 +2884,15 @@
      *
      * @param windowToken the window from which this request originates. If this doesn't match the
      *                    currently served view, the request is ignored.
+     * @param statsToken the token tracking the current IME show request or {@code null} otherwise.
      * @hide
      */
-    public void notifyImeHidden(IBinder windowToken) {
-        final ImeTracker.Token statsToken = new ImeTracker.Token();
-        ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
-                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+    public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
+        if (statsToken == null) {
+            statsToken = ImeTracker.get().onRequestHide(null /* component */,
+                    Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
+                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
+        }
 
         ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                 null /* icProto */);
@@ -3546,6 +3558,18 @@
     }
 
     /**
+     * A test API for CTS to check whether there are any pending IME visibility requests.
+     *
+     * @return {@code true} iff there are pending IME visibility requests.
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    public boolean hasPendingImeVisibilityRequests() {
+        return IInputMethodManagerGlobalInvoker.hasPendingImeVisibilityRequests();
+    }
+
+    /**
      * Show the settings for enabling subtypes of the specified input method.
      *
      * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
index 500c41c..05717dd 100644
--- a/core/java/android/view/inputmethod/TextAppearanceInfo.java
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -31,7 +31,10 @@
 import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.Spanned;
+import android.text.TextPaint;
 import android.text.method.TransformationMethod;
+import android.text.style.CharacterStyle;
 import android.widget.TextView;
 
 import java.util.Objects;
@@ -182,6 +185,70 @@
         mLinkTextColor = builder.mLinkTextColor;
     }
 
+    /**
+     * Creates a new instance of {@link TextAppearanceInfo} by extracting text appearance from the
+     * character before cursor in the target {@link TextView}.
+     * @param textView the target {@link TextView}.
+     * @return the new instance of {@link TextAppearanceInfo}.
+     * @hide
+     */
+    @NonNull
+    public static TextAppearanceInfo createFromTextView(@NonNull TextView textView) {
+        final int selectionStart = textView.getSelectionStart();
+        final CharSequence text = textView.getText();
+        TextPaint textPaint = new TextPaint();
+        textPaint.set(textView.getPaint());    // Copy from textView
+        if (text instanceof Spanned && text.length() > 0 && selectionStart > 0) {
+            // Extract the CharacterStyle spans that changes text appearance in the character before
+            // cursor.
+            Spanned spannedText = (Spanned) text;
+            int lastCh = selectionStart - 1;
+            CharacterStyle[] spans = spannedText.getSpans(lastCh, lastCh, CharacterStyle.class);
+            if (spans != null) {
+                for (CharacterStyle span: spans) {
+                    // Exclude spans that end at lastCh
+                    if (spannedText.getSpanStart(span) <= lastCh
+                            && lastCh < spannedText.getSpanEnd(span)) {
+                        span.updateDrawState(textPaint); // Override the TextPaint
+                    }
+                }
+            }
+        }
+        Typeface typeface = textPaint.getTypeface();
+        String systemFontFamilyName = null;
+        int textWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
+        int textStyle = Typeface.NORMAL;
+        if (typeface != null) {
+            systemFontFamilyName = typeface.getSystemFontFamilyName();
+            textWeight = typeface.getWeight();
+            textStyle = typeface.getStyle();
+        }
+        TextAppearanceInfo.Builder builder = new TextAppearanceInfo.Builder();
+        builder.setTextSize(textPaint.getTextSize())
+                .setTextLocales(textPaint.getTextLocales())
+                .setSystemFontFamilyName(systemFontFamilyName)
+                .setTextFontWeight(textWeight)
+                .setTextStyle(textStyle)
+                .setShadowDx(textPaint.getShadowLayerDx())
+                .setShadowDy(textPaint.getShadowLayerDy())
+                .setShadowRadius(textPaint.getShadowLayerRadius())
+                .setShadowColor(textPaint.getShadowLayerColor())
+                .setElegantTextHeight(textPaint.isElegantTextHeight())
+                .setLetterSpacing(textPaint.getLetterSpacing())
+                .setFontFeatureSettings(textPaint.getFontFeatureSettings())
+                .setFontVariationSettings(textPaint.getFontVariationSettings())
+                .setTextScaleX(textPaint.getTextScaleX())
+                .setTextColor(textPaint.getColor())
+                .setLinkTextColor(textPaint.linkColor)
+                .setAllCaps(textView.isAllCaps())
+                .setFallbackLineSpacing(textView.isFallbackLineSpacing())
+                .setLineBreakStyle(textView.getLineBreakStyle())
+                .setLineBreakWordStyle(textView.getLineBreakWordStyle())
+                .setHighlightTextColor(textView.getHighlightColor())
+                .setHintTextColor(textView.getCurrentHintTextColor());
+        return builder.build();
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0a3ea8a..e643080 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -21,7 +21,6 @@
 
 import android.R;
 import android.animation.ValueAnimator;
-import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,7 +37,6 @@
 import android.content.UndoOwner;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.ColorStateList;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Color;
@@ -51,10 +49,8 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.RenderNode;
-import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
-import android.graphics.fonts.FontStyle;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.LocaleList;
@@ -4774,41 +4770,7 @@
             }
 
             if (includeTextAppearance) {
-                Typeface typeface = mTextView.getPaint().getTypeface();
-                String systemFontFamilyName = null;
-                int textFontWeight = FontStyle.FONT_WEIGHT_UNSPECIFIED;
-                if (typeface != null) {
-                    systemFontFamilyName = typeface.getSystemFontFamilyName();
-                    textFontWeight = typeface.getWeight();
-                }
-                ColorStateList linkTextColors = mTextView.getLinkTextColors();
-                @ColorInt int linkTextColor = linkTextColors != null
-                        ? linkTextColors.getDefaultColor() : 0;
-
-                TextAppearanceInfo.Builder appearanceBuilder = new TextAppearanceInfo.Builder();
-                appearanceBuilder.setTextSize(mTextView.getTextSize())
-                        .setTextLocales(mTextView.getTextLocales())
-                        .setSystemFontFamilyName(systemFontFamilyName)
-                        .setTextFontWeight(textFontWeight)
-                        .setTextStyle(mTextView.getTypefaceStyle())
-                        .setAllCaps(mTextView.isAllCaps())
-                        .setShadowDx(mTextView.getShadowDx())
-                        .setShadowDy(mTextView.getShadowDy())
-                        .setShadowRadius(mTextView.getShadowRadius())
-                        .setShadowColor(mTextView.getShadowColor())
-                        .setElegantTextHeight(mTextView.isElegantTextHeight())
-                        .setFallbackLineSpacing(mTextView.isFallbackLineSpacing())
-                        .setLetterSpacing(mTextView.getLetterSpacing())
-                        .setFontFeatureSettings(mTextView.getFontFeatureSettings())
-                        .setFontVariationSettings(mTextView.getFontVariationSettings())
-                        .setLineBreakStyle(mTextView.getLineBreakStyle())
-                        .setLineBreakWordStyle(mTextView.getLineBreakWordStyle())
-                        .setTextScaleX(mTextView.getTextScaleX())
-                        .setHighlightTextColor(mTextView.getHighlightColor())
-                        .setTextColor(mTextView.getCurrentTextColor())
-                        .setHintTextColor(mTextView.getCurrentHintTextColor())
-                        .setLinkTextColor(linkTextColor);
-                builder.setTextAppearanceInfo(appearanceBuilder.build());
+                builder.setTextAppearanceInfo(TextAppearanceInfo.createFromTextView(mTextView));
             }
             imm.updateCursorAnchorInfo(mTextView, builder.build());
 
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index f74d294..be9cbff 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -123,7 +123,7 @@
 
     private void receive(
             int resultCode, Bundle resultData,
-            @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+            @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
         final int callbackId = resultData.getInt(RESULT_KEY_ID);
         if (resultCode == RESULT_CODE_REGISTER) {
             int priority = resultData.getInt(RESULT_KEY_PRIORITY);
@@ -140,11 +140,11 @@
             @NonNull IOnBackInvokedCallback iCallback,
             @OnBackInvokedDispatcher.Priority int priority,
             int callbackId,
-            @NonNull OnBackInvokedDispatcher receivingDispatcher) {
+            @NonNull WindowOnBackInvokedDispatcher receivingDispatcher) {
         final ImeOnBackInvokedCallback imeCallback =
                 new ImeOnBackInvokedCallback(iCallback, callbackId, priority);
         mImeCallbacks.add(imeCallback);
-        receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback);
+        receivingDispatcher.registerOnBackInvokedCallbackUnchecked(imeCallback, priority);
     }
 
     private void unregisterReceivedCallback(
diff --git a/core/java/com/android/internal/app/AppLocaleCollector.java b/core/java/com/android/internal/app/AppLocaleCollector.java
index 65e8c64..a50fbb8 100644
--- a/core/java/com/android/internal/app/AppLocaleCollector.java
+++ b/core/java/com/android/internal/app/AppLocaleCollector.java
@@ -19,49 +19,184 @@
 import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_ASSET;
 import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
 
+import android.app.LocaleManager;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.SystemProperties;
+import android.provider.Settings;
 import android.util.Log;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /** The Locale data collector for per-app language. */
-class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
+public class AppLocaleCollector implements LocalePickerWithRegion.LocaleCollectorBase {
     private static final String TAG = AppLocaleCollector.class.getSimpleName();
     private final Context mContext;
     private final String mAppPackageName;
-    private final LocaleStore.LocaleInfo mAppCurrentLocale;
+    private LocaleStore.LocaleInfo mAppCurrentLocale;
+    private Set<LocaleStore.LocaleInfo> mAllAppActiveLocales;
+    private Set<LocaleStore.LocaleInfo> mImeLocales;
+    private static final String PROP_APP_LANGUAGE_SUGGESTION =
+            "android.app.language.suggestion.enhanced";
+    private static final boolean ENABLED = true;
 
-    AppLocaleCollector(Context context, String appPackageName) {
+    public AppLocaleCollector(Context context, String appPackageName) {
         mContext = context;
         mAppPackageName = appPackageName;
-        mAppCurrentLocale = LocaleStore.getAppCurrentLocaleInfo(
-                mContext, mAppPackageName);
+    }
+
+    @VisibleForTesting
+    public LocaleStore.LocaleInfo getAppCurrentLocale() {
+        return LocaleStore.getAppActivatedLocaleInfo(mContext, mAppPackageName, true);
+    }
+
+    /**
+     * Get all applications' activated locales.
+     * @return A set which includes all applications' activated LocaleInfo.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getAllAppActiveLocales() {
+        PackageManager pm = mContext.getPackageManager();
+        LocaleManager lm = mContext.getSystemService(LocaleManager.class);
+        HashSet<LocaleStore.LocaleInfo> result = new HashSet<>();
+        if (pm != null && lm != null) {
+            HashMap<String, LocaleStore.LocaleInfo> map = new HashMap<>();
+            for (ApplicationInfo appInfo : pm.getInstalledApplications(
+                    PackageManager.ApplicationInfoFlags.of(0))) {
+                LocaleStore.LocaleInfo localeInfo = LocaleStore.getAppActivatedLocaleInfo(
+                        mContext, appInfo.packageName, false);
+                // For the locale to be added into the suggestion area, its country could not be
+                // empty.
+                if (localeInfo != null && localeInfo.getLocale().getCountry().length() > 0) {
+                    map.put(localeInfo.getId(), localeInfo);
+                }
+            }
+            map.forEach((language, localeInfo) -> result.add(localeInfo));
+        }
+        return result;
+    }
+
+    /**
+     * Get all locales that active IME supports.
+     *
+     * @return A set which includes all LocaleInfo that active IME supports.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getActiveImeLocales() {
+        Set<LocaleStore.LocaleInfo> activeImeLocales = null;
+        InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
+        if (imm != null) {
+            InputMethodInfo activeIme = getActiveIme(imm);
+            if (activeIme != null) {
+                activeImeLocales = LocaleStore.transformImeLanguageTagToLocaleInfo(
+                        imm.getEnabledInputMethodSubtypeList(activeIme, true));
+            }
+        }
+        if (activeImeLocales == null) {
+            return Set.of();
+        } else {
+            return activeImeLocales.stream().filter(
+                    // For the locale to be added into the suggestion area, its country could not be
+                    // empty.
+                    info -> info.getLocale().getCountry().length() > 0).collect(
+                    Collectors.toSet());
+        }
+    }
+
+    private InputMethodInfo getActiveIme(InputMethodManager imm) {
+        InputMethodInfo activeIme = null;
+        List<InputMethodInfo> infoList = imm.getEnabledInputMethodList();
+        String imeId = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.DEFAULT_INPUT_METHOD, mContext.getUserId());
+        for (InputMethodInfo method : infoList) {
+            if (method.getId().equals(imeId)) {
+                activeIme = method;
+            }
+        }
+        return activeIme;
+    }
+
+    /**
+     * Get the AppLocaleResult that the application supports.
+     * @return The AppLocaleResult that the application supports.
+     */
+    @VisibleForTesting
+    public AppLocaleStore.AppLocaleResult getAppSupportedLocales() {
+        return AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+    }
+
+    /**
+     * Get the locales that system supports excluding langTagsToIgnore.
+     *
+     * @param langTagsToIgnore A language set to be ignored.
+     * @param parent The parent locale.
+     * @param translatedOnly specified if is is only for translation.
+     * @return A set which includes the LocaleInfo that system supports, excluding langTagsToIgnore.
+     */
+    @VisibleForTesting
+    public Set<LocaleStore.LocaleInfo> getSystemSupportedLocale(Set<String> langTagsToIgnore,
+            LocaleStore.LocaleInfo parent, boolean translatedOnly) {
+        return LocaleStore.getLevelLocales(mContext, langTagsToIgnore, parent, translatedOnly);
+    }
+
+    /**
+     * Get the locales that system activates.
+     * @return A set which includes all the locales that system activates.
+     */
+    @VisibleForTesting
+    public List<LocaleStore.LocaleInfo> getSystemCurrentLocale() {
+        return LocaleStore.getSystemCurrentLocaleInfo();
     }
 
     @Override
     public HashSet<String> getIgnoredLocaleList(boolean translatedOnly) {
         HashSet<String> langTagsToIgnore = new HashSet<>();
 
-        LocaleList systemLangList = LocaleList.getDefault();
-        for(int i = 0; i < systemLangList.size(); i++) {
-            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
-        }
-
         if (mAppCurrentLocale != null) {
             langTagsToIgnore.add(mAppCurrentLocale.getLocale().toLanguageTag());
         }
+
+        if (SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION, ENABLED)) {
+            // Add the locale that other App activated
+            mAllAppActiveLocales.forEach(
+                    info -> langTagsToIgnore.add(info.getLocale().toLanguageTag()));
+            // Add the locale that active IME enabled
+            mImeLocales.forEach(info -> langTagsToIgnore.add(info.getLocale().toLanguageTag()));
+        }
+
+        // Add System locales
+        LocaleList systemLangList = LocaleList.getDefault();
+        for (int i = 0; i < systemLangList.size(); i++) {
+            langTagsToIgnore.add(systemLangList.get(i).toLanguageTag());
+        }
         return langTagsToIgnore;
     }
 
     @Override
     public Set<LocaleStore.LocaleInfo> getSupportedLocaleList(LocaleStore.LocaleInfo parent,
             boolean translatedOnly, boolean isForCountryMode) {
-        AppLocaleStore.AppLocaleResult result =
-                AppLocaleStore.getAppSupportedLocales(mContext, mAppPackageName);
+        if (mAppCurrentLocale == null) {
+            mAppCurrentLocale = getAppCurrentLocale();
+        }
+        if (mAllAppActiveLocales == null) {
+            mAllAppActiveLocales = getAllAppActiveLocales();
+        }
+        if (mImeLocales == null) {
+            mImeLocales = getActiveImeLocales();
+        }
+        AppLocaleStore.AppLocaleResult result = getAppSupportedLocales();
         Set<String> langTagsToIgnore = getIgnoredLocaleList(translatedOnly);
         Set<LocaleStore.LocaleInfo> appLocaleList = new HashSet<>();
         Set<LocaleStore.LocaleInfo> systemLocaleList;
@@ -71,11 +206,9 @@
 
         // Get system supported locale list
         if (isForCountryMode) {
-            systemLocaleList = LocaleStore.getLevelLocales(mContext,
-                    langTagsToIgnore, parent, translatedOnly);
+            systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, parent, translatedOnly);
         } else {
-            systemLocaleList = LocaleStore.getLevelLocales(mContext, langTagsToIgnore,
-                    null /* no parent */, translatedOnly);
+            systemLocaleList = getSystemSupportedLocale(langTagsToIgnore, null, translatedOnly);
         }
 
         // Add current app locale
@@ -84,19 +217,46 @@
         }
 
         // Add current system language into suggestion list
-        for(LocaleStore.LocaleInfo localeInfo:
-                LocaleStore.getSystemCurrentLocaleInfo()) {
-            boolean isNotCurrentLocale = mAppCurrentLocale == null
-                    || !localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
-            if (!isForCountryMode && isNotCurrentLocale) {
-                appLocaleList.add(localeInfo);
+        if (!isForCountryMode) {
+            boolean isCurrentLocale, isInAppOrIme;
+            for (LocaleStore.LocaleInfo localeInfo : getSystemCurrentLocale()) {
+                isCurrentLocale = mAppCurrentLocale != null
+                        && localeInfo.getLocale().equals(mAppCurrentLocale.getLocale());
+                isInAppOrIme = existsInAppOrIme(localeInfo.getLocale());
+                if (!isCurrentLocale && !isInAppOrIme) {
+                    appLocaleList.add(localeInfo);
+                }
             }
         }
 
-        // Add the languages that included in system supported locale
+        // Add the languages that are included in system supported locale
+        Set<LocaleStore.LocaleInfo> suggestedSet = null;
         if (shouldShowList) {
-            appLocaleList.addAll(filterTheLanguagesNotIncludedInSystemLocale(
-                    systemLocaleList, result.mAppSupportedLocales));
+            appLocaleList.addAll(filterSupportedLocales(systemLocaleList,
+                    result.mAppSupportedLocales));
+            suggestedSet = getSuggestedLocales(appLocaleList);
+        }
+
+        if (!isForCountryMode && SystemProperties.getBoolean(PROP_APP_LANGUAGE_SUGGESTION,
+                ENABLED)) {
+            // Add the language that other apps activate into the suggestion list.
+            Set<LocaleStore.LocaleInfo> localeSet = filterSupportedLocales(mAllAppActiveLocales,
+                    result.mAppSupportedLocales);
+            if (suggestedSet != null) {
+                // Filter out the locale with the same language and country
+                // like zh-TW vs zh-Hant-TW.
+                localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet);
+            }
+            appLocaleList.addAll(localeSet);
+            suggestedSet.addAll(localeSet);
+
+            // Add the language that the active IME enables into the suggestion list.
+            localeSet = filterSupportedLocales(mImeLocales, result.mAppSupportedLocales);
+            if (suggestedSet != null) {
+                localeSet = filterSameLanguageAndCountry(localeSet, suggestedSet);
+            }
+            appLocaleList.addAll(localeSet);
+            suggestedSet.addAll(localeSet);
         }
 
         // Add "system language" option
@@ -117,17 +277,55 @@
         return true;
     }
 
-    private Set<LocaleStore.LocaleInfo> filterTheLanguagesNotIncludedInSystemLocale(
-            Set<LocaleStore.LocaleInfo> systemLocaleList,
+    private Set<LocaleStore.LocaleInfo> getSuggestedLocales(Set<LocaleStore.LocaleInfo> localeSet) {
+        return localeSet.stream().filter(localeInfo -> localeInfo.isSuggested()).collect(
+                Collectors.toSet());
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterSameLanguageAndCountry(
+            Set<LocaleStore.LocaleInfo> newLocaleList,
+            Set<LocaleStore.LocaleInfo> existingLocaleList) {
+        Set<LocaleStore.LocaleInfo> result = new HashSet<>(newLocaleList.size());
+        for (LocaleStore.LocaleInfo appLocaleInfo : newLocaleList) {
+            boolean same = false;
+            Locale appLocale = appLocaleInfo.getLocale();
+            for (LocaleStore.LocaleInfo localeInfo : existingLocaleList) {
+                Locale suggested = localeInfo.getLocale();
+                if (appLocale.getLanguage().equals(suggested.getLanguage())
+                        && appLocale.getCountry().equals(suggested.getCountry())) {
+                    same = true;
+                    break;
+                }
+            }
+            if (!same) {
+                result.add(appLocaleInfo);
+            }
+        }
+        return result;
+    }
+
+    private boolean existsInAppOrIme(Locale locale) {
+        boolean existInApp = mAllAppActiveLocales.stream().anyMatch(
+                localeInfo -> localeInfo.getLocale().equals(locale));
+        if (existInApp) {
+            return true;
+        } else {
+            return mImeLocales.stream().anyMatch(
+                    localeInfo -> localeInfo.getLocale().equals(locale));
+        }
+    }
+
+    private Set<LocaleStore.LocaleInfo> filterSupportedLocales(
+            Set<LocaleStore.LocaleInfo> suggestedLocales,
             HashSet<Locale> appSupportedLocales) {
         Set<LocaleStore.LocaleInfo> filteredList = new HashSet<>();
 
-        for(LocaleStore.LocaleInfo li: systemLocaleList) {
+        for (LocaleStore.LocaleInfo li : suggestedLocales) {
             if (appSupportedLocales.contains(li.getLocale())) {
                 filteredList.add(li);
             } else {
-                for(Locale l: appSupportedLocales) {
-                    if(LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
+                for (Locale l : appSupportedLocales) {
+                    if (LocaleList.matchesLanguageAndScript(li.getLocale(), l)) {
                         filteredList.add(li);
                         break;
                     }
diff --git a/core/java/com/android/internal/app/AppLocaleStore.java b/core/java/com/android/internal/app/AppLocaleStore.java
index f3a322c..a450a05 100644
--- a/core/java/com/android/internal/app/AppLocaleStore.java
+++ b/core/java/com/android/internal/app/AppLocaleStore.java
@@ -30,7 +30,10 @@
 import java.util.Locale;
 import java.util.stream.Collectors;
 
-class AppLocaleStore {
+/**
+ * A class used to access an application's supporting locales.
+ */
+public class AppLocaleStore {
     private static final String TAG = AppLocaleStore.class.getSimpleName();
 
     public static AppLocaleResult getAppSupportedLocales(
@@ -148,8 +151,11 @@
         return false;
     }
 
-    static class AppLocaleResult {
-        enum LocaleStatus {
+    /**
+     * A class used to store an application's supporting locales.
+     */
+    public static class AppLocaleResult {
+        public enum LocaleStatus {
             UNKNOWN_FAILURE,
             NO_SUPPORTED_LANGUAGE_IN_APP,
             ASSET_LOCALE_IS_EMPTY,
@@ -158,7 +164,7 @@
         }
 
         LocaleStatus mLocaleStatus;
-        HashSet<Locale> mAppSupportedLocales;
+        public HashSet<Locale> mAppSupportedLocales;
 
         public AppLocaleResult(LocaleStatus localeStatus, HashSet<Locale> appSupportedLocales) {
             this.mLocaleStatus = localeStatus;
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 689dec4..d2eee91 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -23,6 +23,7 @@
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -45,10 +46,11 @@
         @VisibleForTesting static final int SUGGESTION_TYPE_SIM = 1 << 0;
         @VisibleForTesting static final int SUGGESTION_TYPE_CFG = 1 << 1;
         // Only for per-app language picker
-        private static final int SUGGESTION_TYPE_CURRENT = 1 << 2;
+        @VisibleForTesting static final int SUGGESTION_TYPE_CURRENT = 1 << 2;
         // Only for per-app language picker
-        private static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3;
-
+        @VisibleForTesting  static final int SUGGESTION_TYPE_SYSTEM_LANGUAGE = 1 << 3;
+        // Only for per-app language picker
+        @VisibleForTesting static final int SUGGESTION_TYPE_OTHER_APP_LANGUAGE = 1 << 4;
         private final Locale mLocale;
         private final Locale mParent;
         private final String mId;
@@ -259,7 +261,16 @@
         }
     }
 
-    public static LocaleInfo getAppCurrentLocaleInfo(Context context, String appPackageName) {
+    /**
+     * Get the application's activated locale.
+     *
+     * @param context UI activity's context.
+     * @param appPackageName The application's package name.
+     * @param isAppSelected True if the application is selected in the UI; false otherwise.
+     * @return A LocaleInfo with the application's activated locale.
+     */
+    public static LocaleInfo getAppActivatedLocaleInfo(Context context, String appPackageName,
+            boolean isAppSelected) {
         if (appPackageName == null) {
             return null;
         }
@@ -272,7 +283,11 @@
 
             if (locale != null) {
                 LocaleInfo localeInfo = new LocaleInfo(locale);
-                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
+                if (isAppSelected) {
+                    localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_CURRENT;
+                } else {
+                    localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+                }
                 localeInfo.mIsTranslated = true;
                 return localeInfo;
             }
@@ -283,6 +298,24 @@
     }
 
     /**
+     * Transform IME's language tag to LocaleInfo.
+     *
+     * @param list A list which includes IME's subtype.
+     * @return A LocaleInfo set which includes IME's language tags.
+     */
+    public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
+            List<InputMethodSubtype> list) {
+        Set<LocaleInfo> imeLocales = new HashSet<>();
+        for (InputMethodSubtype subtype : list) {
+            LocaleInfo localeInfo = new LocaleInfo(subtype.getLanguageTag());
+            localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+            localeInfo.mIsTranslated = true;
+            imeLocales.add(localeInfo);
+        }
+        return imeLocales;
+    }
+
+    /**
      * Returns a list of system languages with LocaleInfo
      */
     public static List<LocaleInfo> getSystemCurrentLocaleInfo() {
@@ -458,4 +491,13 @@
         }
         return result;
     }
+
+    /**
+     * API for testing.
+     */
+    @UnsupportedAppUsage
+    @VisibleForTesting
+    public static LocaleInfo fromLocale(Locale locale) {
+        return new LocaleInfo(locale);
+    }
 }
diff --git a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
index f1635eb..ec9184b 100644
--- a/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
+++ b/core/java/com/android/internal/inputmethod/SoftInputShowHideReason.java
@@ -23,6 +23,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeProtoEnums;
 import android.view.inputmethod.InputMethodManager;
 
 import java.lang.annotation.Retention;
@@ -64,113 +65,114 @@
         SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
         SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED,
         SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION,
-        SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR})
+        SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR
+})
 public @interface SoftInputShowHideReason {
     /** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
-    int SHOW_SOFT_INPUT = 0;
+    int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT;
 
     /** Show soft input when {@code InputMethodManagerService#attachNewInputLocked} called. */
-    int ATTACH_NEW_INPUT = 1;
+    int ATTACH_NEW_INPUT = ImeProtoEnums.REASON_ATTACH_NEW_INPUT;
 
     /** Show soft input by {@code InputMethodManagerService#showMySoftInput}. This is triggered when
      *  the IME process try to show the keyboard.
      *
      * @see android.inputmethodservice.InputMethodService#requestShowSelf(int)
      */
-    int SHOW_SOFT_INPUT_FROM_IME = 2;
+    int SHOW_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_FROM_IME;
 
     /**
      * Hide soft input by
      * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow}.
      */
-    int HIDE_SOFT_INPUT = 3;
+    int HIDE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
      */
-    int HIDE_SOFT_INPUT_FROM_IME = 4;
+    int HIDE_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_IME;
 
     /**
      * Show soft input when navigated forward to the window (with
-     * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}} which the focused view is text
+     * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}) which the focused view is text
      * editor and system will auto-show the IME when the window can resize or running on a large
      * screen.
      */
-    int SHOW_AUTO_EDITOR_FORWARD_NAV = 5;
+    int SHOW_AUTO_EDITOR_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_AUTO_EDITOR_FORWARD_NAV;
 
     /**
      * Show soft input when navigated forward to the window with
      * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
      * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE}.
      */
-    int SHOW_STATE_VISIBLE_FORWARD_NAV = 6;
+    int SHOW_STATE_VISIBLE_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_STATE_VISIBLE_FORWARD_NAV;
 
     /**
      * Show soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
      */
-    int SHOW_STATE_ALWAYS_VISIBLE = 7;
+    int SHOW_STATE_ALWAYS_VISIBLE = ImeProtoEnums.REASON_SHOW_STATE_ALWAYS_VISIBLE;
 
     /**
      * Show soft input during {@code InputMethodManagerService} receive changes from
      * {@code SettingsProvider}.
      */
-    int SHOW_SETTINGS_ON_CHANGE = 8;
+    int SHOW_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_SHOW_SETTINGS_ON_CHANGE;
 
     /** Hide soft input during switching user. */
-    int HIDE_SWITCH_USER = 9;
+    int HIDE_SWITCH_USER = ImeProtoEnums.REASON_HIDE_SWITCH_USER;
 
     /** Hide soft input when the user is invalid. */
-    int HIDE_INVALID_USER = 10;
+    int HIDE_INVALID_USER = ImeProtoEnums.REASON_HIDE_INVALID_USER;
 
     /**
      * Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_UNSPECIFIED} which
      * the focused view is not text editor.
      */
-    int HIDE_UNSPECIFIED_WINDOW = 11;
+    int HIDE_UNSPECIFIED_WINDOW = ImeProtoEnums.REASON_HIDE_UNSPECIFIED_WINDOW;
 
     /**
      * Hide soft input when navigated forward to the window with
      * {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
      * {@link LayoutParams#SOFT_INPUT_STATE_HIDDEN}.
      */
-    int HIDE_STATE_HIDDEN_FORWARD_NAV = 12;
+    int HIDE_STATE_HIDDEN_FORWARD_NAV = ImeProtoEnums.REASON_HIDE_STATE_HIDDEN_FORWARD_NAV;
 
     /**
      * Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}.
      */
-    int HIDE_ALWAYS_HIDDEN_STATE = 13;
+    int HIDE_ALWAYS_HIDDEN_STATE = ImeProtoEnums.REASON_HIDE_ALWAYS_HIDDEN_STATE;
 
     /** Hide soft input when "adb shell ime <command>" called. */
-    int HIDE_RESET_SHELL_COMMAND = 14;
+    int HIDE_RESET_SHELL_COMMAND = ImeProtoEnums.REASON_HIDE_RESET_SHELL_COMMAND;
 
     /**
      * Hide soft input during {@code InputMethodManagerService} receive changes from
      * {@code SettingsProvider}.
      */
-    int HIDE_SETTINGS_ON_CHANGE = 15;
+    int HIDE_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_HIDE_SETTINGS_ON_CHANGE;
 
     /**
      * Hide soft input from {@link com.android.server.policy.PhoneWindowManager} when setting
      * {@link com.android.internal.R.integer#config_shortPressOnPowerBehavior} in config.xml as
      * dismiss IME.
      */
-    int HIDE_POWER_BUTTON_GO_HOME = 16;
+    int HIDE_POWER_BUTTON_GO_HOME = ImeProtoEnums.REASON_HIDE_POWER_BUTTON_GO_HOME;
 
     /** Hide soft input when attaching docked stack. */
-    int HIDE_DOCKED_STACK_ATTACHED = 17;
+    int HIDE_DOCKED_STACK_ATTACHED = ImeProtoEnums.REASON_HIDE_DOCKED_STACK_ATTACHED;
 
     /**
      * Hide soft input when {@link com.android.server.wm.RecentsAnimationController} starts
      * intercept touch from app window.
      */
-    int HIDE_RECENTS_ANIMATION = 18;
+    int HIDE_RECENTS_ANIMATION = ImeProtoEnums.REASON_HIDE_RECENTS_ANIMATION;
 
     /**
      * Hide soft input when {@link com.android.wm.shell.bubbles.BubbleController} is expanding,
      * switching, or collapsing Bubbles.
      */
-    int HIDE_BUBBLES = 19;
+    int HIDE_BUBBLES = ImeProtoEnums.REASON_HIDE_BUBBLES;
 
     /**
      * Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
@@ -183,74 +185,78 @@
      * only the dialog focused as it's the latest window with input focus) makes we need to hide
      * soft-input when the same window focused again to align with the same behavior prior to R.
      */
-    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
+    int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR =
+            ImeProtoEnums.REASON_HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR;
 
     /**
      * Hide soft input when a {@link com.android.internal.inputmethod.IInputMethodClient} is
      * removed.
      */
-    int HIDE_REMOVE_CLIENT = 21;
+    int HIDE_REMOVE_CLIENT = ImeProtoEnums.REASON_HIDE_REMOVE_CLIENT;
 
     /**
      * Show soft input when the system invoking
      * {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}.
      */
-    int SHOW_RESTORE_IME_VISIBILITY = 22;
+    int SHOW_RESTORE_IME_VISIBILITY = ImeProtoEnums.REASON_SHOW_RESTORE_IME_VISIBILITY;
 
     /**
      * Show soft input by
      * {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
      */
-    int SHOW_TOGGLE_SOFT_INPUT = 23;
+    int SHOW_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_TOGGLE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
      */
-    int HIDE_TOGGLE_SOFT_INPUT = 24;
+    int HIDE_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_TOGGLE_SOFT_INPUT;
 
     /**
      * Show soft input by
      * {@link android.view.InsetsController#show(int)};
      */
-    int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
+    int SHOW_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_BY_INSETS_API;
 
     /**
      * Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
      * See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
      */
-    int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
+    int HIDE_DISPLAY_IME_POLICY_HIDE = ImeProtoEnums.REASON_HIDE_DISPLAY_IME_POLICY_HIDE;
 
     /**
      * Hide soft input by {@link android.view.InsetsController#hide(int)}.
      */
-    int HIDE_SOFT_INPUT_BY_INSETS_API = 27;
+    int HIDE_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_INSETS_API;
 
     /**
      * Hide soft input by {@link android.inputmethodservice.InputMethodService#handleBack(boolean)}.
      */
-    int HIDE_SOFT_INPUT_BY_BACK_KEY = 28;
+    int HIDE_SOFT_INPUT_BY_BACK_KEY = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_BACK_KEY;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
      */
-    int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = 29;
+    int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT;
 
     /**
      * Hide soft input by
      * {@link android.inputmethodservice.InputMethodService#onExtractingInputChanged(EditorInfo)})}.
      */
-    int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED = 30;
+    int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED =
+            ImeProtoEnums.REASON_HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED;
 
     /**
      * Hide soft input by the deprecated
      * {@link InputMethodManager#hideSoftInputFromInputMethod(IBinder, int)}.
      */
-    int HIDE_SOFT_INPUT_IMM_DEPRECATION = 31;
+    int HIDE_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IMM_DEPRECATION;
 
     /**
      * Hide soft input when the window gained focus without an editor from the IME shown window.
      */
-    int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR = 32;
+    int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR =
+            ImeProtoEnums.REASON_HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR;
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 66d64c4..3d8982b 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -22,10 +22,10 @@
 import android.os.BatteryStats;
 import android.os.BatteryStats.BitDescription;
 import android.os.BatteryStats.CpuUsageDetails;
+import android.os.BatteryStats.EnergyConsumerDetails;
 import android.os.BatteryStats.HistoryItem;
 import android.os.BatteryStats.HistoryStepDetails;
 import android.os.BatteryStats.HistoryTag;
-import android.os.BatteryStats.MeasuredEnergyDetails;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.ParcelFormatException;
@@ -984,9 +984,9 @@
     /**
      * Records measured energy data.
      */
-    public void recordMeasuredEnergyDetails(long elapsedRealtimeMs, long uptimeMs,
-            MeasuredEnergyDetails measuredEnergyDetails) {
-        mHistoryCur.measuredEnergyDetails = measuredEnergyDetails;
+    public void recordEnergyConsumerDetails(long elapsedRealtimeMs, long uptimeMs,
+            EnergyConsumerDetails energyConsumerDetails) {
+        mHistoryCur.energyConsumerDetails = energyConsumerDetails;
         mHistoryCur.states2 |= HistoryItem.STATE2_EXTENSIONS_FLAG;
         writeHistoryItem(elapsedRealtimeMs, uptimeMs);
     }
@@ -1293,7 +1293,7 @@
                 && mHistoryLastWritten.batteryPlugType == cur.batteryPlugType
                 && mHistoryLastWritten.batteryTemperature == cur.batteryTemperature
                 && mHistoryLastWritten.batteryVoltage == cur.batteryVoltage
-                && mHistoryLastWritten.measuredEnergyDetails == null
+                && mHistoryLastWritten.energyConsumerDetails == null
                 && mHistoryLastWritten.cpuUsageDetails == null) {
             // We can merge this new change in with the last one.  Merging is
             // allowed as long as only the states have changed, and within those states
@@ -1396,7 +1396,7 @@
         cur.eventCode = HistoryItem.EVENT_NONE;
         cur.eventTag = null;
         cur.tagsFirstOccurrence = false;
-        cur.measuredEnergyDetails = null;
+        cur.energyConsumerDetails = null;
         cur.cpuUsageDetails = null;
         if (DEBUG) {
             Slog.i(TAG, "Writing history buffer: was " + mHistoryBufferLastPos
@@ -1517,7 +1517,7 @@
         if (stateIntChanged) {
             firstToken |= BatteryStatsHistory.DELTA_STATE_FLAG;
         }
-        if (cur.measuredEnergyDetails != null) {
+        if (cur.energyConsumerDetails != null) {
             extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG;
             if (!mMeasuredEnergyHeaderWritten) {
                 extensionFlags |= BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG;
@@ -1653,22 +1653,22 @@
         dest.writeDouble(cur.wifiRailChargeMah);
         if (extensionFlags != 0) {
             dest.writeInt(extensionFlags);
-            if (cur.measuredEnergyDetails != null) {
+            if (cur.energyConsumerDetails != null) {
                 if (DEBUG) {
-                    Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.measuredEnergyDetails);
+                    Slog.i(TAG, "WRITE DELTA: measuredEnergyDetails=" + cur.energyConsumerDetails);
                 }
                 if (!mMeasuredEnergyHeaderWritten) {
-                    MeasuredEnergyDetails.EnergyConsumer[] consumers =
-                            cur.measuredEnergyDetails.consumers;
+                    EnergyConsumerDetails.EnergyConsumer[] consumers =
+                            cur.energyConsumerDetails.consumers;
                     dest.writeInt(consumers.length);
-                    for (MeasuredEnergyDetails.EnergyConsumer consumer : consumers) {
+                    for (EnergyConsumerDetails.EnergyConsumer consumer : consumers) {
                         dest.writeInt(consumer.type);
                         dest.writeInt(consumer.ordinal);
                         dest.writeString(consumer.name);
                     }
                     mMeasuredEnergyHeaderWritten = true;
                 }
-                mVarintParceler.writeLongArray(dest, cur.measuredEnergyDetails.chargeUC);
+                mVarintParceler.writeLongArray(dest, cur.energyConsumerDetails.chargeUC);
             }
 
             if (cur.cpuUsageDetails != null) {
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index b88116d..67eee4f 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -36,7 +36,7 @@
     private final BatteryStats.HistoryStepDetails mReadHistoryStepDetails =
             new BatteryStats.HistoryStepDetails();
     private final SparseArray<BatteryStats.HistoryTag> mHistoryTags = new SparseArray<>();
-    private BatteryStats.MeasuredEnergyDetails mMeasuredEnergyDetails;
+    private BatteryStats.EnergyConsumerDetails mEnergyConsumerDetails;
     private BatteryStats.CpuUsageDetails mCpuUsageDetails;
     private final BatteryStatsHistory.VarintParceler mVarintParceler =
             new BatteryStatsHistory.VarintParceler();
@@ -230,8 +230,8 @@
         if ((cur.states2 & BatteryStats.HistoryItem.STATE2_EXTENSIONS_FLAG) != 0) {
             final int extensionFlags = src.readInt();
             if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_HEADER_FLAG) != 0) {
-                if (mMeasuredEnergyDetails == null) {
-                    mMeasuredEnergyDetails = new BatteryStats.MeasuredEnergyDetails();
+                if (mEnergyConsumerDetails == null) {
+                    mEnergyConsumerDetails = new BatteryStats.EnergyConsumerDetails();
                 }
 
                 final int consumerCount = src.readInt();
@@ -241,28 +241,28 @@
                             "EnergyConsumer count too high: " + consumerCount
                                     + ". Max = " + MAX_ENERGY_CONSUMER_COUNT);
                 }
-                mMeasuredEnergyDetails.consumers =
-                        new BatteryStats.MeasuredEnergyDetails.EnergyConsumer[consumerCount];
-                mMeasuredEnergyDetails.chargeUC = new long[consumerCount];
+                mEnergyConsumerDetails.consumers =
+                        new BatteryStats.EnergyConsumerDetails.EnergyConsumer[consumerCount];
+                mEnergyConsumerDetails.chargeUC = new long[consumerCount];
                 for (int i = 0; i < consumerCount; i++) {
-                    BatteryStats.MeasuredEnergyDetails.EnergyConsumer consumer =
-                            new BatteryStats.MeasuredEnergyDetails.EnergyConsumer();
+                    BatteryStats.EnergyConsumerDetails.EnergyConsumer consumer =
+                            new BatteryStats.EnergyConsumerDetails.EnergyConsumer();
                     consumer.type = src.readInt();
                     consumer.ordinal = src.readInt();
                     consumer.name = src.readString();
-                    mMeasuredEnergyDetails.consumers[i] = consumer;
+                    mEnergyConsumerDetails.consumers[i] = consumer;
                 }
             }
 
             if ((extensionFlags & BatteryStatsHistory.EXTENSION_MEASURED_ENERGY_FLAG) != 0) {
-                if (mMeasuredEnergyDetails == null) {
+                if (mEnergyConsumerDetails == null) {
                     throw new IllegalStateException("MeasuredEnergyDetails without a header");
                 }
 
-                mVarintParceler.readLongArray(src, mMeasuredEnergyDetails.chargeUC);
-                cur.measuredEnergyDetails = mMeasuredEnergyDetails;
+                mVarintParceler.readLongArray(src, mEnergyConsumerDetails.chargeUC);
+                cur.energyConsumerDetails = mEnergyConsumerDetails;
             } else {
-                cur.measuredEnergyDetails = null;
+                cur.energyConsumerDetails = null;
             }
 
             if ((extensionFlags & BatteryStatsHistory.EXTENSION_CPU_USAGE_HEADER_FLAG) != 0) {
@@ -295,7 +295,7 @@
                 cur.cpuUsageDetails = null;
             }
         } else {
-            cur.measuredEnergyDetails = null;
+            cur.energyConsumerDetails = null;
             cur.cpuUsageDetails = null;
         }
     }
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index 0df006d..65655b7 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -841,7 +841,19 @@
         return sw.toString();
     }
 
-    final public String printCurrentState(long now) {
+    /**
+     * Returns current CPU state with all the processes as a String, sorted by load
+     * in descending order.
+     */
+    public final String printCurrentState(long now) {
+        return printCurrentState(now, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns current CPU state with the top {@code maxProcessesToDump} highest load
+     * processes as a String, sorted by load in descending order.
+     */
+    public final String printCurrentState(long now, int maxProcessesToDump) {
         final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
         buildWorkingProcs();
@@ -883,8 +895,8 @@
         if (DEBUG) Slog.i(TAG, "totalTime " + totalTime + " over sample time "
                 + (mCurrentSampleTime-mLastSampleTime));
 
-        int N = mWorkingProcs.size();
-        for (int i=0; i<N; i++) {
+        int dumpedProcessCount = Math.min(maxProcessesToDump, mWorkingProcs.size());
+        for (int i = 0; i < dumpedProcessCount; i++) {
             Stats st = mWorkingProcs.get(i);
             printProcessCPU(pw, st.added ? " +" : (st.removed ? " -": "  "),
                     st.pid, st.name, (int)st.rel_uptime,
diff --git a/core/java/com/android/internal/policy/DecorContext.java b/core/java/com/android/internal/policy/DecorContext.java
index 134a917..63785f2 100644
--- a/core/java/com/android/internal/policy/DecorContext.java
+++ b/core/java/com/android/internal/policy/DecorContext.java
@@ -139,6 +139,15 @@
     }
 
     @Override
+    public boolean isDeviceContext() {
+        Context context = mContext.get();
+        if (context != null) {
+            return context.isDeviceContext();
+        }
+        return false;
+    }
+
+    @Override
     public boolean isConfigurationContext() {
         Context context = mContext.get();
         if (context != null) {
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 145aeaf..b5b27f52 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -274,7 +274,6 @@
     private boolean mApplyFloatingVerticalInsets = false;
     private boolean mApplyFloatingHorizontalInsets = false;
 
-    private int mResizeMode = RESIZE_MODE_INVALID;
     private final int mResizeShadowSize;
     private final Paint mVerticalResizeShadowPaint = new Paint();
     private final Paint mHorizontalResizeShadowPaint = new Paint();
@@ -808,9 +807,7 @@
         updateElevation();
         mAllowUpdateElevation = true;
 
-        if (changed
-                && (mResizeMode == RESIZE_MODE_DOCKED_DIVIDER
-                    || mDrawLegacyNavigationBarBackground)) {
+        if (changed && mDrawLegacyNavigationBarBackground) {
             getViewRootImpl().requestInvalidateRootRenderNode();
         }
     }
@@ -2392,7 +2389,7 @@
 
     @Override
     public void onWindowDragResizeStart(Rect initialBounds, boolean fullscreen, Rect systemInsets,
-            Rect stableInsets, int resizeMode) {
+            Rect stableInsets) {
         if (mWindow.isDestroyed()) {
             // If the owner's window is gone, we should not be able to come here anymore.
             releaseThreadedRenderer();
@@ -2418,7 +2415,6 @@
 
             updateColorViews(null /* insets */, false);
         }
-        mResizeMode = resizeMode;
         getViewRootImpl().requestInvalidateRootRenderNode();
     }
 
@@ -2426,7 +2422,6 @@
     public void onWindowDragResizeEnd() {
         releaseThreadedRenderer();
         updateColorViews(null /* insets */, false);
-        mResizeMode = RESIZE_MODE_INVALID;
         getViewRootImpl().requestInvalidateRootRenderNode();
     }
 
@@ -2471,9 +2466,7 @@
     }
 
     private void drawResizingShadowIfNeeded(RecordingCanvas canvas) {
-        if (mResizeMode != RESIZE_MODE_DOCKED_DIVIDER || mWindow.mIsFloating
-                || mWindow.isTranslucent()
-                || mWindow.isShowingWallpaper()) {
+        if (mWindow.mIsFloating || mWindow.isTranslucent() || mWindow.isShowingWallpaper()) {
             return;
         }
         canvas.save();
diff --git a/core/java/com/android/internal/power/MeasuredEnergyStats.java b/core/java/com/android/internal/power/EnergyConsumerStats.java
similarity index 96%
rename from core/java/com/android/internal/power/MeasuredEnergyStats.java
rename to core/java/com/android/internal/power/EnergyConsumerStats.java
index 290cba0..325df57 100644
--- a/core/java/com/android/internal/power/MeasuredEnergyStats.java
+++ b/core/java/com/android/internal/power/EnergyConsumerStats.java
@@ -37,15 +37,14 @@
 import java.util.Arrays;
 
 /**
- * Tracks the measured charge consumption of various subsystems according to their
+ * Tracks the charge consumption of various subsystems according to their
  * {@link StandardPowerBucket} or custom power bucket (which is tied to
  * {@link android.hardware.power.stats.EnergyConsumer.ordinal}).
  *
- * This class doesn't use a TimeBase, and instead requires manually decisions about when to
+ * This class doesn't use a TimeBase, and instead requires manual decisions about when to
  * accumulate since it is trivial. However, in the future, a TimeBase could be used instead.
  */
-@VisibleForTesting
-public class MeasuredEnergyStats {
+public class EnergyConsumerStats {
     private static final String TAG = "MeasuredEnergyStats";
 
     // Note: {@link BatteryStats#VERSION} MUST be updated if standard
@@ -205,7 +204,7 @@
          */
         private String getBucketName(int index) {
             if (isValidStandardBucket(index)) {
-                return DebugUtils.valueToString(MeasuredEnergyStats.class, "POWER_BUCKET_", index);
+                return DebugUtils.valueToString(EnergyConsumerStats.class, "POWER_BUCKET_", index);
             }
             final int customBucket = indexToCustomBucket(index);
             StringBuilder name = new StringBuilder().append("CUSTOM_").append(customBucket);
@@ -242,7 +241,7 @@
      * supportedStandardBuckets must be of size {@link #NUMBER_STANDARD_POWER_BUCKETS}.
      * numCustomBuckets >= 0 is the number of (non-standard) custom power buckets on the device.
      */
-    public MeasuredEnergyStats(MeasuredEnergyStats.Config config) {
+    public EnergyConsumerStats(EnergyConsumerStats.Config config) {
         mConfig = config;
         final int numTotalBuckets = config.getNumberOfBuckets();
         mAccumulatedChargeMicroCoulomb = new long[numTotalBuckets];
@@ -259,15 +258,15 @@
      * Reads a MeasuredEnergyStats from the supplied Parcel.
      */
     @Nullable
-    public static MeasuredEnergyStats createFromParcel(Config config, Parcel in) {
+    public static EnergyConsumerStats createFromParcel(Config config, Parcel in) {
         if (!in.readBoolean()) {
             return null;
         }
-        return new MeasuredEnergyStats(config, in);
+        return new EnergyConsumerStats(config, in);
     }
 
     /** Construct from parcel. */
-    public MeasuredEnergyStats(MeasuredEnergyStats.Config config, Parcel in) {
+    public EnergyConsumerStats(EnergyConsumerStats.Config config, Parcel in) {
         mConfig = config;
 
         final int size = in.readInt();
@@ -533,21 +532,22 @@
      * possible (not necessarily supported) standard and custom buckets.
      *
      * Corresponding write performed by
-     * {@link #writeSummaryToParcel(MeasuredEnergyStats, Parcel)}.
+     * {@link #writeSummaryToParcel(EnergyConsumerStats, Parcel)}.
      *
      * @return a new MeasuredEnergyStats object as described.
      *         Returns null if the stats contain no non-0 information (such as if template is null
      *         or if the parcel indicates there is no data to populate).
      */
-    public static @Nullable MeasuredEnergyStats createAndReadSummaryFromParcel(
-            @Nullable Config config, Parcel in) {
+    @Nullable
+    public static EnergyConsumerStats createAndReadSummaryFromParcel(@Nullable Config config,
+            Parcel in) {
         final int arraySize = in.readInt();
         // Check if any MeasuredEnergyStats exists on the parcel
         if (arraySize == 0) return null;
 
         if (config == null) {
             // Nothing supported anymore. Create placeholder object just to consume the parcel data.
-            final MeasuredEnergyStats mes = new MeasuredEnergyStats(
+            final EnergyConsumerStats mes = new EnergyConsumerStats(
                     new Config(new boolean[arraySize], null, new int[0], new String[]{""}));
             mes.readSummaryFromParcel(in);
             return null;
@@ -557,12 +557,12 @@
             Slog.wtf(TAG, "Size of MeasuredEnergyStats parcel (" + arraySize
                     + ") does not match config (" + config.getNumberOfBuckets() + ").");
             // Something is horribly wrong. Just consume the parcel and return null.
-            final MeasuredEnergyStats mes = new MeasuredEnergyStats(config);
+            final EnergyConsumerStats mes = new EnergyConsumerStats(config);
             mes.readSummaryFromParcel(in);
             return null;
         }
 
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
         stats.readSummaryFromParcel(in);
         if (stats.containsInterestingData()) {
             return stats;
@@ -585,7 +585,7 @@
      *
      * Corresponding read performed by {@link #createAndReadSummaryFromParcel}.
      */
-    public static void writeSummaryToParcel(@Nullable MeasuredEnergyStats stats, Parcel dest) {
+    public static void writeSummaryToParcel(@Nullable EnergyConsumerStats stats, Parcel dest) {
         if (stats == null) {
             dest.writeInt(0);
             return;
@@ -607,7 +607,7 @@
     }
 
     /** Reset accumulated charges of the given stats. */
-    public static void resetIfNotNull(@Nullable MeasuredEnergyStats stats) {
+    public static void resetIfNotNull(@Nullable EnergyConsumerStats stats) {
         if (stats != null) stats.reset();
     }
 
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 2ac4309..4a5ed7e 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -53,7 +53,7 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode) {
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing) {
         if (reportDraw) {
             try {
                 mSession.finishDrawing(this, null /* postDrawTransaction */, seqId);
diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/view/IImeTracker.aidl
new file mode 100644
index 0000000..b062ca7
--- /dev/null
+++ b/core/java/com/android/internal/view/IImeTracker.aidl
@@ -0,0 +1,94 @@
+/*
+ * 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.view;
+
+import android.view.inputmethod.ImeTracker;
+
+/**
+ * Interface to the global Ime tracker, used by all client applications.
+ * {@hide}
+ */
+interface IImeTracker {
+
+    /**
+     * Called when an IME show request is created,
+     * returns a new Binder to be associated with the IME tracking token.
+     *
+     * @param uid the uid of the client that requested the IME.
+     * @param origin the origin of the IME show request.
+     * @param reason the reason why the IME show request was created.
+     */
+    IBinder onRequestShow(int uid, int origin, int reason);
+
+    /**
+     * Called when an IME hide request is created,
+     * returns a new Binder to be associated with the IME tracking token.
+     *
+     * @param uid the uid of the client that requested the IME.
+     * @param origin the origin of the IME hide request.
+     * @param reason the reason why the IME hide request was created.
+     */
+    IBinder onRequestHide(int uid, int origin, int reason);
+
+    /**
+     * Called when the IME request progresses to a further phase.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the new phase the IME request reached.
+     */
+    oneway void onProgress(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME request fails.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the phase the IME request failed at.
+     */
+    oneway void onFailed(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME request is cancelled.
+     *
+     * @param statsToken the token tracking the current IME request.
+     * @param phase the phase the IME request was cancelled at.
+     */
+    oneway void onCancelled(in IBinder statsToken, int phase);
+
+    /**
+     * Called when the IME show request is successful.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onShown(in IBinder statsToken);
+
+    /**
+     * Called when the IME hide request is successful.
+     *
+     * @param statsToken the token tracking the current IME request.
+     */
+    oneway void onHidden(in IBinder statsToken);
+
+    /**
+     * Checks whether there are any pending IME visibility requests.
+     *
+     * @return {@code true} iff there are pending IME visibility requests.
+     */
+    @EnforcePermission("TEST_INPUT_METHOD")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.TEST_INPUT_METHOD)")
+    boolean hasPendingImeVisibilityRequests();
+}
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 00bc3f2..2106426 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -27,6 +27,7 @@
 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
 import com.android.internal.inputmethod.IRemoteInputConnection;
 import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.view.IImeTracker;
 
 /**
  * Public interface to the global input method manager, used by all client
@@ -158,4 +159,10 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.TEST_INPUT_METHOD)")
     void setStylusWindowIdleTimeoutForTest(in IInputMethodClient client, long timeout);
+
+    /**
+     * Returns the singleton instance for the Ime Tracker Service.
+     * {@hide}
+     */
+    IImeTracker getImeTrackerService();
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 65f5522..91a5d3a 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1170,24 +1170,6 @@
         getTrustManager().reportEnabledTrustAgentsChanged(userHandle);
     }
 
-    public boolean isCredentialRequiredToDecrypt(boolean defaultValue) {
-        final int value = Settings.Global.getInt(mContentResolver,
-                Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, -1);
-        return value == -1 ? defaultValue : (value != 0);
-    }
-
-    public void setCredentialRequiredToDecrypt(boolean required) {
-        if (!(getUserManager().isSystemUser() || getUserManager().isPrimaryUser())) {
-            throw new IllegalStateException(
-                    "Only the system or primary user may call setCredentialRequiredForDecrypt()");
-        }
-
-        if (isDeviceEncryptionEnabled()){
-            Settings.Global.putInt(mContext.getContentResolver(),
-               Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, required ? 1 : 0);
-        }
-    }
-
     private void throwIfCalledOnMainThread() {
         if (Looper.getMainLooper().isCurrentThread()) {
             throw new IllegalStateException("should not be called from the main thread.");
diff --git a/core/jni/android_media_AudioDescriptor.h b/core/jni/android_media_AudioDescriptor.h
index 680ac3f..bac761c 100644
--- a/core/jni/android_media_AudioDescriptor.h
+++ b/core/jni/android_media_AudioDescriptor.h
@@ -25,6 +25,8 @@
 // keep these values in sync with ExtraAudioDescriptor.java
 #define STANDARD_NONE 0
 #define STANDARD_EDID 1
+#define STANDARD_SADB 2
+#define STANDARD_VSADB 3
 
 static inline status_t audioStandardFromNative(audio_standard_t nStandard, int* standard) {
     status_t result = NO_ERROR;
@@ -35,6 +37,12 @@
         case AUDIO_STANDARD_EDID:
             *standard = STANDARD_EDID;
             break;
+        case AUDIO_STANDARD_SADB:
+            *standard = STANDARD_SADB;
+            break;
+        case AUDIO_STANDARD_VSADB:
+            *standard = STANDARD_VSADB;
+            break;
         default:
             result = BAD_VALUE;
     }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 28ac464..19cb30e 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -3201,6 +3201,30 @@
     return nativeToJavaStatus(status);
 }
 
+static jboolean android_media_AudioSystem_supportsBluetoothVariableLatency(JNIEnv *env,
+                                                                           jobject thiz) {
+    bool supports;
+    if (AudioSystem::supportsBluetoothVariableLatency(&supports) != NO_ERROR) {
+        supports = false;
+    }
+    return supports;
+}
+
+static int android_media_AudioSystem_setBluetoothVariableLatencyEnabled(JNIEnv *env, jobject thiz,
+                                                                        jboolean enabled) {
+    return (jint)check_AudioSystem_Command(
+            AudioSystem::setBluetoothVariableLatencyEnabled(enabled));
+}
+
+static jboolean android_media_AudioSystem_isBluetoothVariableLatencyEnabled(JNIEnv *env,
+                                                                            jobject thiz) {
+    bool enabled;
+    if (AudioSystem::isBluetoothVariableLatencyEnabled(&enabled) != NO_ERROR) {
+        enabled = false;
+    }
+    return enabled;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] =
@@ -3364,7 +3388,13 @@
          {"getPreferredMixerAttributes", "(Landroid/media/AudioAttributes;ILjava/util/List;)I",
           (void *)android_media_AudioSystem_getPreferredMixerAttributes},
          {"clearPreferredMixerAttributes", "(Landroid/media/AudioAttributes;II)I",
-          (void *)android_media_AudioSystem_clearPreferredMixerAttributes}};
+          (void *)android_media_AudioSystem_clearPreferredMixerAttributes},
+         {"supportsBluetoothVariableLatency", "()Z",
+          (void *)android_media_AudioSystem_supportsBluetoothVariableLatency},
+         {"setBluetoothVariableLatencyEnabled", "(Z)I",
+          (void *)android_media_AudioSystem_setBluetoothVariableLatencyEnabled},
+         {"isBluetoothVariableLatencyEnabled", "()Z",
+          (void *)android_media_AudioSystem_isBluetoothVariableLatencyEnabled}};
 
 static const JNINativeMethod gEventHandlerMethods[] = {
     {"native_setup",
diff --git a/core/jni/com_android_internal_os_KernelAllocationStats.cpp b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
index 5b10497..35c9487 100644
--- a/core/jni/com_android_internal_os_KernelAllocationStats.cpp
+++ b/core/jni/com_android_internal_os_KernelAllocationStats.cpp
@@ -44,7 +44,7 @@
 static jobjectArray KernelAllocationStats_getDmabufAllocations(JNIEnv *env, jobject) {
     std::vector<DmaBuffer> buffers;
 
-    if (!dmabufinfo::ReadDmaBufs(&buffers)) {
+    if (!dmabufinfo::ReadProcfsDmaBufs(&buffers)) {
         return nullptr;
     }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78a6a66..3274e85 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3632,7 +3632,7 @@
     <permission android:name="android.permission.SET_APP_SPECIFIC_LOCALECONFIG"
         android:protectionLevel="signature" />
 
-    <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
+    <!-- @SystemApi @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
         android:protectionLevel="signature"/>
@@ -4984,7 +4984,7 @@
     <permission android:name="android.permission.ROTATE_SURFACE_FLINGER"
         android:protectionLevel="signature|recents" />
 
-    <!-- @SystemApi Allows an application to provide hints to SurfaceFlinger that can influence
+    <!-- Allows an application to provide hints to SurfaceFlinger that can influence
          its wakes up time to compose the next frame. This is a subset of the capabilities granted
          by {@link #ACCESS_SURFACE_FLINGER}.
          <p>Not for use by third-party applications.
@@ -7429,6 +7429,14 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.companion.datatransfer.contextsync.CallMetadataSyncInCallService"
+                 android:permission="android.permission.BIND_INCALL_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.InCallService"/>
+            </intent-filter>
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1072f57..1d1c02d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -9401,8 +9401,34 @@
         <attr name="label" />
         <!-- The key character map file resource. -->
         <attr name="keyboardLayout" format="reference" />
-        <!-- The locales the given keyboard layout corresponds to. -->
-        <attr name="locale" format="string" />
+        <!-- The locales the given keyboard layout corresponds to. This is a list of
+             BCP-47 conformant language tags separated by the delimiter ',' or '|'.
+             Some examples of language tags are: en-US, zh-Hans-CN, el-Grek-polyton.
+             It includes information for language code, country code, variant, and script
+             code like ‘Latn’, ‘Cyrl’, etc. -->
+        <attr name="keyboardLocale" format="string"/>
+        <!-- The layout type of the given keyboardLayout.
+             NOTE: The enum to int value mapping must remain stable -->
+        <attr name="keyboardLayoutType" format="enum">
+            <!-- Qwerty-based keyboard layout. -->
+            <enum name="qwerty" value="1" />
+            <!-- Qwertz-based keyboard layout. -->
+            <enum name="qwertz" value="2" />
+            <!-- Azerty-based keyboard layout. -->
+            <enum name="azerty" value="3" />
+            <!-- Dvorak keyboard layout. -->
+            <enum name="dvorak" value="4" />
+            <!-- Colemak keyboard layout. -->
+            <enum name="colemak" value="5" />
+            <!-- Workman keyboard layout. -->
+            <enum name="workman" value="6" />
+            <!-- Turkish-Q keyboard layout. -->
+            <enum name="turkish_q" value="7" />
+            <!-- Turkish-F keyboard layout. -->
+            <enum name="turkish_f" value="8" />
+            <!-- Keyboard layout that has been enhanced with a large number of extra characters. -->
+            <enum name="extended" value="9" />
+        </attr>
         <!-- The vendor ID of the hardware the given layout corresponds to. @hide -->
         <attr name="vendorId" format="integer" />
         <!-- The product ID of the hardware the given layout corresponds to. @hide -->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d4644c5..57eff9a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1027,6 +1027,9 @@
         <flag name="layoutDirection" value="0x2000" />
         <!-- The color mode of the screen has changed (color gamut or dynamic range). -->
         <flag name="colorMode" value="0x4000" />
+        <!-- The grammatical gender has changed, for example the user set the grammatical gender
+             from the UI. -->
+        <flag name="grammaticalGender" value="0x8000" />
         <!-- The font scaling factor has changed, that is the user has
              selected a new global font size. -->
         <flag name="fontScale" value="0x40000000" />
@@ -2963,6 +2966,18 @@
              Context.createAttributionContext() using the first attribution tag
              contained here. -->
         <attr name="attributionTags" />
+        <!-- If true, and this is an {@link android.R.attr#isolatedProcess} service, the service
+             is allowed to be bound in a shared isolated process with other isolated services.
+             Note that these other isolated services can also belong to other apps from different
+             vendors.
+             <p>
+             Shared isolated processes are created when using the
+             {@link android.content.Context#BIND_SHARED_ISOLATED_PROCESS) during service binding.
+             <p>
+             Note that when this flag is used, the {@link android.R.attr#process} attribute is
+             ignored when the process is bound into a shared isolated process by a client.
+        -->
+        <attr name="allowSharedIsolatedProcess" format="boolean" />
     </declare-styleable>
 
     <!-- @hide The <code>apex-system-service</code> tag declares an apex system service
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fb10d3d..92b4ba1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2011,6 +2011,9 @@
     <!-- The default volume for the ring stream -->
     <integer name="config_audio_ring_vol_default">5</integer>
 
+    <!-- Enable sound dose computation and warnings -->
+    <bool name="config_audio_csd_enabled_default">false</bool>
+
     <!-- The default value for whether head tracking for
          spatial audio is enabled for a newly connected audio device -->
     <bool name="config_spatial_audio_head_tracking_enabled_default">false</bool>
@@ -2130,6 +2133,8 @@
     <string name="config_systemAutomotiveCalendarSyncManager" translatable="false"></string>
     <!-- The name of the package that will hold the default automotive navigation role. -->
     <string name="config_defaultAutomotiveNavigation" translatable="false"></string>
+    <!-- The name of the package that will hold the system wear health service role. -->
+    <string name="config_systemWearHealthService" translatable="false"></string>
 
     <!-- The name of the package that will handle updating the device management role. -->
     <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
@@ -2667,9 +2672,9 @@
          will be locked. -->
     <bool name="config_multiuserDelayUserDataLocking">false</bool>
 
-    <!-- Whether the device allows users to start in background on secondary displays.
+    <!-- Whether the device allows users to start in background visible on displays.
          Should be false for most devices, except automotive vehicle with passenger displays. -->
-    <bool name="config_multiuserUsersOnSecondaryDisplays">false</bool>
+    <bool name="config_multiuserVisibleBackgroundUsers">false</bool>
 
     <!-- Whether to automatically switch to the designated Dock User (the user chosen for
          displaying dreams, etc.) after a timeout when the device is docked.  -->
@@ -2696,6 +2701,10 @@
     <!-- Whether UI for multi user should be shown -->
     <bool name="config_enableMultiUserUI">false</bool>
 
+    <!-- Whether multiple admins are allowed on the device. If set to true, new users can be created
+     with admin privileges and admin privileges can be granted/revoked from existing users. -->
+    <bool name="config_enableMultipleAdmins">false</bool>
+
     <!-- Whether the new Auto Selection Network UI should be shown -->
     <bool name="config_enableNewAutoSelectNetworkUI">false</bool>
 
@@ -4149,12 +4158,34 @@
    -->
     <string name="config_defaultAmbientContextDetectionService" translatable="false"></string>
 
+    <!-- The component names for the system's AmbientContextEvent detection services.
+      These services must be trusted, as it can be activated without explicit consent of the user.
+      See android.service.ambientcontext.AmbientContextDetectionService and
+      android.service.wearable.WearableSensingService.
+      -->
+    <string-array name="config_defaultAmbientContextServices">
+        <item>@string/config_defaultAmbientContextDetectionService</item>
+        <item>@string/config_defaultWearableSensingService</item>
+    </string-array>
+
     <!-- The component name for the default system wearable sensing service.
         This service must be trusted, as it can be activated without explicit consent of the user.
         See android.service.wearable.WearableSensingService.
    -->
     <string name="config_defaultWearableSensingService" translatable="false"></string>
 
+    <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for
+         wearable sensing. -->
+    <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string>
+
+    <!-- Intent extra key for the caller's package name while requesting
+          ambient context consent for wearable sensing. -->
+    <string translatable="false" name="config_wearableAmbientContextPackageNameExtraKey"></string>
+
+    <!-- Intent extra key for the event code int array while requesting
+          ambient context consent for wearable sensing. -->
+    <string translatable="false" name="config_wearableAmbientContextEventArrayExtraKey"></string>
+
     <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent. -->
     <string translatable="false" name="config_defaultAmbientContextConsentComponent"></string>
 
@@ -5199,6 +5230,15 @@
          the format [System DeviceState]:[WM Jetpack Posture], for example: "0:1". -->
     <string-array name="config_device_state_postures" translatable="false" />
 
+    <!-- Which Surface rotations are considered as tabletop posture (horizontal hinge) when the
+         device is half-folded. Other half-folded postures will be assumed to be book (vertical
+         hinge) mode. Units: degrees; valid values: 0, 90, 180, 270. -->
+    <integer-array name="config_deviceTabletopRotations" />
+
+    <!-- This flag indicates that a display with fold-state FLAT should always be considered as
+         having a separating hinge. -->
+    <bool name="config_isDisplayHingeAlwaysSeparating">false</bool>
+
     <!-- Aspect ratio of letterboxing for fixed orientation. Values <= 1.0 will be ignored.
          Note: Activity min/max aspect ratio restrictions will still be respected.
          Therefore this override can control the maximum screen area that can be occupied by
@@ -5247,14 +5287,26 @@
 
     <!-- Horizontal position of a center of the letterboxed app window.
         0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
-        or > 1, it is ignored and central position is used (0.5). -->
+        or > 1 it is ignored and for non-book mode central position is used (0.5); for book mode
+         left is used (0.0). -->
     <item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
 
     <!-- Vertical position of a center of the letterboxed app window.
         0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
-        or > 1, it is ignored and central position is used (0.5). -->
+        or > 1 it is ignored and for non-tabletop mode central position is used (0.5); for
+         tabletop mode top (0.0) is used. -->
     <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.0</item>
 
+    <!-- Horizontal position of a center of the letterboxed app window when in book mode.
+    0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
+    or > 1, it is ignored and left position is used (0.0). -->
+    <item name="config_letterboxBookModePositionMultiplier" format="float" type="dimen">0.0</item>
+
+    <!-- Vertical position of a center of the letterboxed app window when in tabletop mode.
+        0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
+        or > 1, it is ignored and top position is used (0.0). -->
+    <item name="config_letterboxTabletopModePositionMultiplier" format="float" type="dimen">0.0</item>
+
     <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps.
     -->
     <bool name="config_letterboxIsHorizontalReachabilityEnabled">false</bool>
@@ -5282,6 +5334,26 @@
         If given value is outside of this range, the option 1 (center) is assummed. -->
     <integer name="config_letterboxDefaultPositionForVerticalReachability">1</integer>
 
+    <!-- Default horizontal position of the letterboxed app window when reachability is
+    enabled and an app is fullscreen in landscape device orientation and in book mode. When
+    reachability is enabled, the position can change between left, center and right. This config
+    defines the default one:
+        - Option 0 - Left.
+        - Option 1 - Center.
+        - Option 2 - Right.
+    If given value is outside of this range, the option 0 (left) is assummed. -->
+    <integer name="config_letterboxDefaultPositionForBookModeReachability">0</integer>
+
+    <!-- Default vertical position of the letterboxed app window when reachability is
+        enabled and an app is fullscreen in portrait device orientation and in tabletop mode. When
+        reachability is enabled, the position can change between top, center and bottom. This config
+        defines the default one:
+            - Option 0 - Top.
+            - Option 1 - Center.
+            - Option 2 - Bottom.
+        If given value is outside of this range, the option 0 (top) is assummed. -->
+    <integer name="config_letterboxDefaultPositionForTabletopModeReachability">0</integer>
+
     <!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
     <bool name="config_letterboxIsEducationEnabled">false</bool>
 
@@ -5298,6 +5370,12 @@
          TODO(b/255532890) Enable when ignoreOrientationRequest is set -->
     <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool>
 
+    <!-- Whether camera compat treatment is enabled for issues caused by orientation mismatch
+        between camera buffers and an app window. This includes force rotation of fixed
+        orientation activities connected to the camera in fullscreen and showing a tooltip in
+        split screen. -->
+    <bool name="config_isWindowManagerCameraCompatTreatmentEnabled">false</bool>
+
     <!-- Whether a camera compat controller is enabled to allow the user to apply or revert
          treatment for stretched issues in camera viewfinder. -->
     <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index d7354ea..97feaac 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,9 @@
     <public name="visualQueryDetectionService" />
     <public name="physicalKeyboardHintLanguageTag" />
     <public name="physicalKeyboardHintLayoutType" />
+    <public name="allowSharedIsolatedProcess" />
+    <public name="keyboardLocale" />
+    <public name="keyboardLayoutType" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
@@ -134,6 +137,8 @@
   </staging-public-group>
 
   <staging-public-group type="string" first-id="0x01cb0000">
+    <!-- @hide @SystemApi -->
+    <public name="config_systemWearHealthService" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01ca0000">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 87298d5..388bc95 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4572,6 +4572,28 @@
        "Raise volume above recommended level?\n\nListening at high volume for long periods may damage your hearing."
     </string>
 
+    <!-- Message shown in dialog when user goes over a multiple of 100% of the safe weekly dose -->
+    <string name="csd_dose_reached_warning" product="default">
+        "Warning,\nYou have exceeded the amount of loud sound signals one can safely listen to in a week over headphones.\n\nGoing over this limit will permanently damage your hearing."
+    </string>
+
+    <!-- Message shown in dialog when user goes over 500% of the safe weekly dose and volume is
+    automatically lowered -->
+    <string name="csd_dose_repeat_warning" product="default">
+        "Warning,\nYou have exceeded 5 times the amount of loud sound signals one can safely listen to in a week over headphones.\n\nVolume has been lowered to protect your hearing."
+    </string>
+
+    <!-- Message shown in dialog when user's dose enters RS2 -->
+    <string name="csd_entering_RS2_warning" product="default">
+        "The level at which you are listening to media can result in hearing damage when sustained over long periods of time.\n\nContinuing to play at this level for long periods of time could damage your hearing."
+    </string>
+
+    <!-- Message shown in dialog when user is momentarily listening to unsafely loud content
+    over headphones -->
+    <string name="csd_momentary_exposure_warning" product="default">
+        "Warning,\nYou are currently listening to loud content played at an unsafe level.\n\nContinuing to listen this loud will permanently damage your hearing."
+    </string>
+
     <!-- Dialog title for dialog shown when the accessibility shortcut is activated, and we want
      to confirm that the user understands what's going to happen-->
     <string name="accessibility_shortcut_warning_dialog_title">Use Accessibility Shortcut?</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index abf1ed9..f54335a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -283,6 +283,7 @@
   <java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
   <java-symbol type="bool" name="action_bar_embed_tabs" />
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
+  <java-symbol type="bool" name="config_audio_csd_enabled_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_steps" />
   <java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -352,6 +353,7 @@
   <java-symbol type="bool" name="config_speed_up_audio_on_mt_calls" />
   <java-symbol type="bool" name="config_useFixedVolume" />
   <java-symbol type="bool" name="config_enableMultiUserUI"/>
+  <java-symbol type="bool" name="config_enableMultipleAdmins"/>
   <java-symbol type="bool" name="config_enableNewAutoSelectNetworkUI"/>
   <java-symbol type="bool" name="config_disableUsbPermissionDialogs"/>
   <java-symbol type="dimen" name="config_highResTaskSnapshotScale" />
@@ -467,7 +469,7 @@
   <java-symbol type="integer" name="config_multiuserMaximumUsers" />
   <java-symbol type="integer" name="config_multiuserMaxRunningUsers" />
   <java-symbol type="bool" name="config_multiuserDelayUserDataLocking" />
-  <java-symbol type="bool" name="config_multiuserUsersOnSecondaryDisplays" />
+  <java-symbol type="bool" name="config_multiuserVisibleBackgroundUsers" />
   <java-symbol type="bool" name="config_enableTimeoutToDockUserWhenDocked" />
   <java-symbol type="integer" name="config_userTypePackageWhitelistMode"/>
   <java-symbol type="xml" name="config_user_types" />
@@ -659,6 +661,10 @@
   <java-symbol type="string" name="contentServiceSync" />
   <java-symbol type="string" name="contentServiceSyncNotificationTitle" />
   <java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
+  <java-symbol type="string" name="csd_dose_reached_warning" />
+  <java-symbol type="string" name="csd_dose_repeat_warning" />
+  <java-symbol type="string" name="csd_entering_RS2_warning" />
+  <java-symbol type="string" name="csd_momentary_exposure_warning" />
   <java-symbol type="string" name="date_and_time" />
   <java-symbol type="string" name="date_picker_decrement_day_button" />
   <java-symbol type="string" name="date_picker_decrement_month_button" />
@@ -3699,6 +3705,10 @@
   <java-symbol type="string" name="config_defaultSystemCaptionsManagerService" />
   <java-symbol type="string" name="config_defaultAmbientContextDetectionService" />
   <java-symbol type="string" name="config_defaultAmbientContextConsentComponent" />
+  <java-symbol type="string" name="config_defaultWearableSensingConsentComponent" />
+  <java-symbol type="string" name="config_wearableAmbientContextPackageNameExtraKey" />
+  <java-symbol type="string" name="config_wearableAmbientContextEventArrayExtraKey" />
+  <java-symbol type="array" name="config_defaultAmbientContextServices" />
   <java-symbol type="string" name="config_ambientContextPackageNameExtraKey" />
   <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" />
   <java-symbol type="string" name="config_defaultWearableSensingService" />
@@ -4398,6 +4408,8 @@
   <java-symbol type="array" name="config_keep_warming_services" />
   <java-symbol type="string" name="config_display_features" />
   <java-symbol type="array" name="config_device_state_postures" />
+  <java-symbol type="array" name="config_deviceTabletopRotations" />
+  <java-symbol type="bool" name="config_isDisplayHingeAlwaysSeparating" />
 
   <java-symbol type="dimen" name="controls_thumbnail_image_max_height" />
   <java-symbol type="dimen" name="controls_thumbnail_image_max_width" />
@@ -4410,13 +4422,18 @@
   <java-symbol type="color" name="config_letterboxBackgroundColor" />
   <java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
   <java-symbol type="dimen" name="config_letterboxVerticalPositionMultiplier" />
+  <java-symbol type="dimen" name="config_letterboxBookModePositionMultiplier" />
+  <java-symbol type="dimen" name="config_letterboxTabletopModePositionMultiplier" />
   <java-symbol type="bool" name="config_letterboxIsHorizontalReachabilityEnabled" />
   <java-symbol type="bool" name="config_letterboxIsVerticalReachabilityEnabled" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
   <java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
+  <java-symbol type="integer" name="config_letterboxDefaultPositionForBookModeReachability" />
+  <java-symbol type="integer" name="config_letterboxDefaultPositionForTabletopModeReachability" />
   <java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
   <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
   <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
+  <java-symbol type="bool" name="config_isWindowManagerCameraCompatTreatmentEnabled" />
   <java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
 
   <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
index 87f91fa..9a999e4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramListTest.java
@@ -72,9 +72,9 @@
                     /* value= */ 94300);
     private static final ProgramSelector.Identifier RDS_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_RDS_PI, 15019);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000111);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1013);
@@ -89,7 +89,7 @@
 
     private static final ProgramList.Chunk FM_RDS_ADD_CHUNK = new ProgramList.Chunk(IS_PURGE,
             IS_COMPLETE, Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-            Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+            Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
     private static final ProgramList.Chunk FM_ADD_INCOMPLETE_CHUNK = new ProgramList.Chunk(IS_PURGE,
             /* complete= */ false, Set.of(FM_PROGRAM_INFO), new ArraySet<>());
     private static final ProgramList.Filter TEST_FILTER = new ProgramList.Filter(
@@ -216,7 +216,7 @@
     public void isPurge_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Puring chunk").that(chunk.isPurge()).isEqualTo(IS_PURGE);
     }
@@ -225,7 +225,7 @@
     public void isComplete_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Complete chunk").that(chunk.isComplete()).isEqualTo(IS_COMPLETE);
     }
@@ -234,7 +234,7 @@
     public void getModified_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Modified program info in chunk")
                 .that(chunk.getModified()).containsExactly(FM_PROGRAM_INFO, RDS_PROGRAM_INFO);
@@ -244,10 +244,10 @@
     public void getRemoved_forChunk() {
         ProgramList.Chunk chunk = new ProgramList.Chunk(IS_PURGE, IS_COMPLETE,
                 Set.of(FM_PROGRAM_INFO, RDS_PROGRAM_INFO),
-                Set.of(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
+                Set.of(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER));
 
         assertWithMessage("Removed program identifiers in chunk").that(chunk.getRemoved())
-                .containsExactly(DAB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
+                .containsExactly(DAB_DMB_SID_EXT_IDENTIFIER, DAB_ENSEMBLE_IDENTIFIER);
     }
 
     @Test
@@ -276,6 +276,19 @@
     }
 
     @Test
+    public void getProgramList_forTunerAdapterWhenServiceDied_fails() throws Exception {
+        Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+        createRadioTuner();
+        doThrow(new RemoteException()).when(mTunerMock).startProgramListUpdates(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getProgramList(parameters));
+
+        assertWithMessage("Exception for getting program list when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getDynamicProgramList_forTunerAdapter() throws Exception {
         createRadioTuner();
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
index 5bd018b..9399907 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -36,18 +36,18 @@
     private static final long AM_FREQUENCY = 700;
     private static final ProgramSelector.Identifier FM_IDENTIFIER = new ProgramSelector.Identifier(
             ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, FM_FREQUENCY);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_1 =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x1000011);
-    private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_2 =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000112);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_1 =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
+    private static final ProgramSelector.Identifier DAB_DMB_SID_EXT_IDENTIFIER_2 =
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000112L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1001);
     private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
-                    /* value= */ 94500);
+                    /* value= */ 220352);
 
     @Test
     public void getType_forIdentifier() {
@@ -80,13 +80,13 @@
     @Test
     public void equals_withDifferentTypesForIdentifiers_returnsFalse() {
         assertWithMessage("Identifier with different identifier type")
-                .that(FM_IDENTIFIER).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+                .that(FM_IDENTIFIER).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
     }
 
     @Test
     public void equals_withDifferentValuesForIdentifiers_returnsFalse() {
         assertWithMessage("Identifier with different identifier value")
-                .that(DAB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_SID_EXT_IDENTIFIER_1);
+                .that(DAB_DMB_SID_EXT_IDENTIFIER_2).isNotEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1);
     }
 
     @Test
@@ -168,19 +168,19 @@
     @Test
     public void getFirstId_withIdInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
-        long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+        long firstIdValue = selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
 
         assertWithMessage("Value of the first DAB_SID_EXT identifier")
-                .that(firstIdValue).isEqualTo(DAB_SID_EXT_IDENTIFIER_1.getValue());
+                .that(firstIdValue).isEqualTo(DAB_DMB_SID_EXT_IDENTIFIER_1.getValue());
     }
 
     @Test
     public void getFirstId_withIdNotInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
         int idType = ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY;
@@ -195,13 +195,13 @@
     @Test
     public void getAllIds_withIdInSelector() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         ProgramSelector.Identifier[] allIdsExpected =
-                {DAB_SID_EXT_IDENTIFIER_1, DAB_SID_EXT_IDENTIFIER_2};
+                {DAB_DMB_SID_EXT_IDENTIFIER_1, DAB_DMB_SID_EXT_IDENTIFIER_2};
         ProgramSelector selector = getDabSelector(secondaryIds, /* vendorIds= */ null);
 
         ProgramSelector.Identifier[] allIds =
-                selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT);
+                selector.getAllIds(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT);
 
         assertWithMessage("All DAB_SID_EXT identifiers in selector")
                 .that(allIds).isEqualTo(allIdsExpected);
@@ -244,14 +244,14 @@
     @Test
     public void withSecondaryPreferred() {
         ProgramSelector.Identifier[] secondaryIds = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_2, DAB_FREQUENCY_IDENTIFIER};
         long[] vendorIdsExpected = {12345, 678};
         ProgramSelector selector = getDabSelector(secondaryIds, vendorIdsExpected);
         ProgramSelector.Identifier[] secondaryIdsExpected = new ProgramSelector.Identifier[]{
-                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_SID_EXT_IDENTIFIER_1};
+                DAB_ENSEMBLE_IDENTIFIER, DAB_FREQUENCY_IDENTIFIER, DAB_DMB_SID_EXT_IDENTIFIER_1};
 
         ProgramSelector selectorPreferred =
-                selector.withSecondaryPreferred(DAB_SID_EXT_IDENTIFIER_1);
+                selector.withSecondaryPreferred(DAB_DMB_SID_EXT_IDENTIFIER_1);
 
         assertWithMessage("Program type")
                 .that(selectorPreferred.getProgramType()).isEqualTo(selector.getProgramType());
@@ -458,7 +458,7 @@
 
     private ProgramSelector getDabSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
             @Nullable long[] vendorIds) {
-        return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_SID_EXT_IDENTIFIER_1, secondaryIds,
+        return new ProgramSelector(DAB_PROGRAM_TYPE, DAB_DMB_SID_EXT_IDENTIFIER_1, secondaryIds,
                 vendorIds);
     }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index 03742eb..afbf8c3 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -18,7 +18,9 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 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.never;
@@ -105,17 +107,17 @@
     private static final int INFO_FLAGS = 0b110001;
     private static final int SIGNAL_QUALITY = 2;
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000111);
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000111L);
     private static final ProgramSelector.Identifier DAB_SID_EXT_IDENTIFIER_RELATED =
-            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
-                    /* value= */ 0x10000113);
+            new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                    /* value= */ 0xA000000113L);
     private static final ProgramSelector.Identifier DAB_ENSEMBLE_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
                     /* value= */ 0x1013);
     private static final ProgramSelector.Identifier DAB_FREQUENCY_IDENTIFIER =
             new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
-                    /* value= */ 95500);
+                    /* value= */ 220352);
     private static final ProgramSelector DAB_SELECTOR =
             new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB, DAB_SID_EXT_IDENTIFIER,
                     new ProgramSelector.Identifier[]{
@@ -859,13 +861,13 @@
     @Test
     public void getLogicallyTunedTo_forProgramInfo() {
         assertWithMessage("Identifier logically tuned to in DAB program info")
-                .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
+                .that(DAB_PROGRAM_INFO.getLogicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
     }
 
     @Test
     public void getPhysicallyTunedTo_forProgramInfo() {
         assertWithMessage("Identifier physically tuned to DAB program info")
-                .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_SID_EXT_IDENTIFIER);
+                .that(DAB_PROGRAM_INFO.getPhysicallyTunedTo()).isEqualTo(DAB_FREQUENCY_IDENTIFIER);
     }
 
     @Test
@@ -1006,6 +1008,35 @@
     }
 
     @Test
+    public void listModules_forRadioManagerWithNullListAsInput_fails() throws Exception {
+        createRadioManager();
+
+        assertWithMessage("Status when listing module with empty list input")
+                .that(mRadioManager.listModules(null)).isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void listModules_withNullListFromService_fails() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.listModules()).thenReturn(null);
+        List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+        assertWithMessage("Status for listing module when getting null list from HAL client")
+                .that(mRadioManager.listModules(modules)).isEqualTo(RadioManager.STATUS_ERROR);
+    }
+
+    @Test
+    public void listModules_whenServiceDied_fails() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.listModules()).thenThrow(new RemoteException());
+        List<RadioManager.ModuleProperties> modules = new ArrayList<>();
+
+        assertWithMessage("Status for listing module when HAL client service is dead")
+                .that(mRadioManager.listModules(modules))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void openTuner_forRadioModule() throws Exception {
         createRadioManager();
         int moduleId = 0;
@@ -1019,6 +1050,18 @@
     }
 
     @Test
+    public void openTuner_whenServiceDied_returnsNull() throws Exception {
+        createRadioManager();
+        when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()))
+                .thenThrow(new RemoteException());
+
+        RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG,
+                /* withAudio= */ true, mCallbackMock, /* handler= */ null);
+
+        assertWithMessage("Radio tuner when service is dead").that(nullTuner).isNull();
+    }
+
+    @Test
     public void addAnnouncementListener_withListenerNotAddedBefore() throws Exception {
         createRadioManager();
         Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
@@ -1049,6 +1092,21 @@
     }
 
     @Test
+    public void addAnnouncementListener_whenServiceDied_throwException() throws Exception {
+        createRadioManager();
+        String exceptionMessage = "service is dead";
+        when(mRadioServiceMock.addAnnouncementListener(any(), any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+        Set<Integer> enableTypeSet = createAnnouncementTypeSet(EVENT_ANNOUNCEMENT_TYPE);
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioManager.addAnnouncementListener(enableTypeSet, mEventListener));
+
+        assertWithMessage("Exception for adding announcement listener with dead service")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void removeAnnouncementListener_withListenerNotAddedBefore_ignores() throws Exception {
         createRadioManager();
 
@@ -1104,8 +1162,8 @@
     }
 
     private static RadioManager.ProgramInfo createDabProgramInfo(ProgramSelector selector) {
-        return new RadioManager.ProgramInfo(selector, DAB_FREQUENCY_IDENTIFIER,
-                DAB_SID_EXT_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
+        return new RadioManager.ProgramInfo(selector, DAB_SID_EXT_IDENTIFIER,
+                DAB_FREQUENCY_IDENTIFIER, Arrays.asList(DAB_SID_EXT_IDENTIFIER_RELATED), INFO_FLAGS,
                 SIGNAL_QUALITY, METADATA, /* vendorInfo= */ null);
     }
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
index d851a7724..c8b4493 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/TunerAdapterTest.java
@@ -18,10 +18,13 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertThrows;
 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.doThrow;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -37,6 +40,7 @@
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
 import android.os.Build;
+import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
@@ -46,7 +50,6 @@
 import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
-import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
@@ -131,6 +134,24 @@
     }
 
     @Test
+    public void setConfiguration_withInvalidParameters_fails() throws Exception {
+        doThrow(new IllegalArgumentException()).when(mTunerMock).setConfiguration(any());
+
+        assertWithMessage("Status for setting configuration with invalid parameters")
+                .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+                .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void setConfiguration_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setConfiguration(any());
+
+        assertWithMessage("Status for setting configuration when service is dead")
+                .that(mRadioTuner.setConfiguration(TEST_BAND_CONFIG))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void getConfiguration_forTunerAdapter() throws Exception {
         when(mTunerMock.getConfiguration()).thenReturn(TEST_BAND_CONFIG);
         RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
@@ -144,14 +165,52 @@
     }
 
     @Test
+    public void getConfiguration_withInvalidParameters_fails() throws Exception {
+        RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[0];
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mRadioTuner.getConfiguration(bandConfigs));
+
+        assertWithMessage("Exception for getting configuration with invalid parameters")
+                .that(thrown).hasMessageThat().contains("must be an array of length 1");
+    }
+
+    @Test
+    public void getConfiguration_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).getConfiguration();
+        RadioManager.BandConfig[] bandConfigs = new RadioManager.BandConfig[1];
+
+        assertWithMessage("Status for getting configuration when service is dead")
+                .that(mRadioTuner.getConfiguration(bandConfigs))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void setMute_forTunerAdapter() {
-        int status = mRadioTuner.setMute(/* mute= */ true);
+        int status = mRadioTuner.setMute(true);
 
         assertWithMessage("Status for setting mute")
                 .that(status).isEqualTo(RadioManager.STATUS_OK);
     }
 
     @Test
+    public void setMute_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).setMuted(anyBoolean());
+
+        assertWithMessage("Status for setting muted when service is in illegal state")
+                .that(mRadioTuner.setMute(true)).isEqualTo(RadioManager.STATUS_ERROR);
+    }
+
+    @Test
+    public void setMute_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setMuted(anyBoolean());
+
+        assertWithMessage("Status for setting muted when service is dead")
+                .that(mRadioTuner.setMute(true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void getMute_forTunerAdapter() throws Exception {
         when(mTunerMock.isMuted()).thenReturn(true);
 
@@ -161,6 +220,14 @@
     }
 
     @Test
+    public void getMute_whenServiceDied_returnsTrue() throws Exception {
+        when(mTunerMock.isMuted()).thenThrow(new RemoteException());
+
+        assertWithMessage("Status for getting muted when service is dead")
+                .that(mRadioTuner.getMute()).isEqualTo(true);
+    }
+
+    @Test
     public void step_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -176,6 +243,24 @@
     }
 
     @Test
+    public void step_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for stepping when service is in illegal state")
+                .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void step_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).step(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for stepping when service is dead")
+                .that(mRadioTuner.step(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void scan_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
@@ -191,13 +276,31 @@
     }
 
     @Test
+    public void scan_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for scanning when service is in illegal state")
+                .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void scan_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for scan when service is dead")
+                .that(mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void seek_forTunerAdapter_succeeds() throws Exception {
         doAnswer(invocation -> {
             mTunerCallback.onCurrentProgramInfoChanged(FM_PROGRAM_INFO);
             return RadioManager.STATUS_OK;
         }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
-        int scanStatus = mRadioTuner.scan(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
+        int scanStatus = mRadioTuner.seek(RadioTuner.DIRECTION_DOWN, /* skipSubChannel= */ false);
 
         verify(mTunerMock).seek(/* directionDown= */ true, /* skipSubChannel= */ false);
         assertWithMessage("Status for seeking")
@@ -212,13 +315,31 @@
             return RadioManager.STATUS_OK;
         }).when(mTunerMock).seek(anyBoolean(), anyBoolean());
 
-        mRadioTuner.scan(RadioTuner.DIRECTION_UP, /* skipSubChannel*/ true);
+        mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true);
 
         verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onTuneFailed(
                 RadioTuner.TUNER_RESULT_TIMEOUT, FM_SELECTOR);
     }
 
     @Test
+    public void seek_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for seeking when service is in illegal state")
+                .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ false))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void seek_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).seek(anyBoolean(), anyBoolean());
+
+        assertWithMessage("Status for seeking when service is dead")
+                .that(mRadioTuner.seek(RadioTuner.DIRECTION_UP, /* skipSubChannel= */ true))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void tune_withChannelsForTunerAdapter_succeeds() {
         int status = mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0);
 
@@ -228,6 +349,33 @@
     }
 
     @Test
+    public void tune_withInvalidChannel_fails() throws Exception {
+        doThrow(new IllegalArgumentException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is in illegal state")
+                .that(mRadioTuner.tune(/* channel= */ 300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_BAD_VALUE);
+    }
+
+    @Test
+    public void tune_withChannelsWhenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is in illegal state")
+                .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void tune_withChannelsWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+        assertWithMessage("Status for tuning when service is dead")
+                .that(mRadioTuner.tune(/* channel= */ 92300, /* subChannel= */ 0))
+                .isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void tune_withValidSelectorForTunerAdapter_succeeds() throws Exception {
         mRadioTuner.tune(FM_SELECTOR);
 
@@ -250,6 +398,17 @@
     }
 
     @Test
+    public void tune_withSelectorWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).tune(any());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.tune(FM_SELECTOR));
+
+        assertWithMessage("Exception for tuning when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void cancel_forTunerAdapter() throws Exception {
         mRadioTuner.tune(FM_SELECTOR);
 
@@ -259,6 +418,22 @@
     }
 
     @Test
+    public void cancel_whenIllegalState_fails() throws Exception {
+        doThrow(new IllegalStateException()).when(mTunerMock).cancel();
+
+        assertWithMessage("Status for canceling when service is in illegal state")
+                .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_INVALID_OPERATION);
+    }
+
+    @Test
+    public void cancel_forTunerAdapterWhenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).cancel();
+
+        assertWithMessage("Status for canceling when service is dead")
+                .that(mRadioTuner.cancel()).isEqualTo(RadioManager.STATUS_DEAD_OBJECT);
+    }
+
+    @Test
     public void cancelAnnouncement_forTunerAdapter() throws Exception {
         mRadioTuner.cancelAnnouncement();
 
@@ -266,6 +441,17 @@
     }
 
     @Test
+    public void cancelAnnouncement_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).cancelAnnouncement();
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.cancelAnnouncement());
+
+        assertWithMessage("Exception for canceling announcement when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getProgramInfo_beforeProgramInfoSetForTunerAdapter() {
         RadioManager.ProgramInfo[] programInfoArray = new RadioManager.ProgramInfo[1];
 
@@ -295,13 +481,24 @@
         when(mTunerMock.getImage(anyInt())).thenReturn(bitmapExpected);
         int imageId = 1;
 
-        Bitmap image = mRadioTuner.getMetadataImage(/* id= */ imageId);
+        Bitmap image = mRadioTuner.getMetadataImage(imageId);
 
         assertWithMessage("Image obtained from id %s", imageId)
                 .that(image).isEqualTo(bitmapExpected);
     }
 
     @Test
+    public void getMetadataImage_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.getImage(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getMetadataImage(/* id= */ 1));
+
+        assertWithMessage("Exception for getting metadata image when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void startBackgroundScan_forTunerAdapter() throws Exception {
         when(mTunerMock.startBackgroundScan()).thenReturn(false);
 
@@ -312,6 +509,17 @@
     }
 
     @Test
+    public void startBackgroundScan_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.startBackgroundScan()).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.startBackgroundScan());
+
+        assertWithMessage("Exception for background scan when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isAnalogForced_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG)).thenReturn(true);
 
@@ -322,6 +530,19 @@
     }
 
     @Test
+    public void isAnalogForced_whenNotSupported_fails() throws Exception {
+        String errorMessage = "Analog forced switch is not supported";
+        when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_FORCE_ANALOG))
+                .thenThrow(new UnsupportedOperationException(errorMessage));
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mRadioTuner.isAnalogForced());
+
+        assertWithMessage("Exception for checking analog playback switch when not supported")
+                .that(thrown).hasMessageThat().contains(errorMessage);
+    }
+
+    @Test
     public void setAnalogForced_forTunerAdapter() throws Exception {
         boolean analogForced = true;
 
@@ -331,6 +552,19 @@
     }
 
     @Test
+    public void setAnalogForced_whenNotSupported_fails() throws Exception {
+        String errorMessage = "Analog forced switch is not supported";
+        doThrow(new UnsupportedOperationException(errorMessage))
+                .when(mTunerMock).setConfigFlag(eq(RadioManager.CONFIG_FORCE_ANALOG), anyBoolean());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class,
+                () -> mRadioTuner.setAnalogForced(/* isForced= */ false));
+
+        assertWithMessage("Exception for setting analog playback switch when not supported")
+                .that(thrown).hasMessageThat().contains(errorMessage);
+    }
+
+    @Test
     public void isConfigFlagSupported_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING))
                 .thenReturn(true);
@@ -343,6 +577,17 @@
     }
 
     @Test
+    public void isConfigFlagSupported_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.isConfigFlagSupported(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.isConfigFlagSupported(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+        assertWithMessage("Exception for checking config flag support when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isConfigFlagSet_forTunerAdapter() throws Exception {
         when(mTunerMock.isConfigFlagSet(RadioManager.CONFIG_DAB_FM_SOFT_LINKING))
                 .thenReturn(true);
@@ -355,6 +600,17 @@
     }
 
     @Test
+    public void isConfigFlagSet_whenServiceDied_fails() throws Exception {
+        when(mTunerMock.isConfigFlagSet(anyInt())).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.isConfigFlagSet(RadioManager.CONFIG_DAB_DAB_LINKING));
+
+        assertWithMessage("Exception for getting config flag when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void setConfigFlag_forTunerAdapter() throws Exception {
         boolean dabFmLinking = true;
 
@@ -364,8 +620,20 @@
     }
 
     @Test
+    public void setConfigFlag_whenServiceDied_fails() throws Exception {
+        doThrow(new RemoteException()).when(mTunerMock).setConfigFlag(anyInt(), anyBoolean());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.setConfigFlag(RadioManager.CONFIG_DAB_DAB_LINKING,
+                        /* value= */ true));
+
+        assertWithMessage("Exception for setting config flag when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void getParameters_forTunerAdapter() throws Exception {
-        List<String> parameterKeys = Arrays.asList("ParameterKeyMock");
+        List<String> parameterKeys = List.of("ParameterKeyMock");
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
         when(mTunerMock.getParameters(parameterKeys)).thenReturn(parameters);
 
@@ -374,6 +642,18 @@
     }
 
     @Test
+    public void getParameters_whenServiceDied_fails() throws Exception {
+        List<String> parameterKeys = List.of("ParameterKeyMock");
+        when(mTunerMock.getParameters(parameterKeys)).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.getParameters(parameterKeys));
+
+        assertWithMessage("Exception for getting parameters when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void setParameters_forTunerAdapter() throws Exception {
         Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
         when(mTunerMock.setParameters(parameters)).thenReturn(parameters);
@@ -383,6 +663,18 @@
     }
 
     @Test
+    public void setParameters_whenServiceDied_fails() throws Exception {
+        Map<String, String> parameters = Map.of("ParameterKeyMock", "ParameterValueMock");
+        when(mTunerMock.setParameters(parameters)).thenThrow(new RemoteException());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class,
+                () -> mRadioTuner.setParameters(parameters));
+
+        assertWithMessage("Exception for setting parameters when service is dead")
+                .that(thrown).hasMessageThat().contains("Service died");
+    }
+
+    @Test
     public void isAntennaConnected_forTunerAdapter() throws Exception {
         mTunerCallback.onAntennaState(/* connected= */ false);
 
@@ -391,6 +683,15 @@
     }
 
     @Test
+    public void onError_forTunerAdapter() throws Exception {
+        int errorStatus = RadioTuner.ERROR_HARDWARE_FAILURE;
+
+        mTunerCallback.onError(errorStatus);
+
+        verify(mCallbackMock, timeout(CALLBACK_TIMEOUT_MS)).onError(errorStatus);
+    }
+
+    @Test
     public void hasControl_forTunerAdapter() throws Exception {
         when(mTunerMock.isClosed()).thenReturn(true);
 
@@ -398,6 +699,14 @@
     }
 
     @Test
+    public void hasControl_whenServiceDied_returnsFalse() throws Exception {
+        when(mTunerMock.isClosed()).thenThrow(new RemoteException());
+
+        assertWithMessage("Control on tuner when service is dead")
+                .that(mRadioTuner.hasControl()).isFalse();
+    }
+
+    @Test
     public void onConfigurationChanged_forTunerCallbackAdapter() throws Exception {
         mTunerCallback.onConfigurationChanged(TEST_BAND_CONFIG);
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
index a421218..82db716 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/AidlTestUtils.java
@@ -19,6 +19,7 @@
 import android.hardware.broadcastradio.Metadata;
 import android.hardware.broadcastradio.ProgramIdentifier;
 import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
 import android.hardware.broadcastradio.VendorKeyValue;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
@@ -41,17 +42,25 @@
                 /* dabFrequencyTable= */ null, /* vendorInfo= */ null);
     }
 
-    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector,
+            ProgramSelector.Identifier logicallyTunedTo,
+            ProgramSelector.Identifier physicallyTunedTo, int signalQuality) {
         return new RadioManager.ProgramInfo(selector,
-                selector.getPrimaryId(), selector.getPrimaryId(), /* relatedContents= */ null,
+                logicallyTunedTo, physicallyTunedTo, /* relatedContents= */ null,
                 /* infoFlags= */ 0, signalQuality,
                 new RadioMetadata.Builder().build(), new ArrayMap<>());
     }
 
-    static RadioManager.ProgramInfo makeProgramInfo(int programType,
-            ProgramSelector.Identifier identifier, int signalQuality) {
-        ProgramSelector selector = makeProgramSelector(programType, identifier);
-        return makeProgramInfo(selector, signalQuality);
+    static RadioManager.ProgramInfo makeProgramInfo(ProgramSelector selector, int signalQuality) {
+        return makeProgramInfo(selector, selector.getPrimaryId(), selector.getPrimaryId(),
+                signalQuality);
+    }
+
+    static ProgramIdentifier makeHalIdentifier(@IdentifierType int type, long value) {
+        ProgramIdentifier halDabId = new ProgramIdentifier();
+        halDabId.type = type;
+        halDabId.value = value;
+        return halDabId;
     }
 
     static ProgramSelector makeFmSelector(long freq) {
@@ -67,44 +76,48 @@
     }
 
     static android.hardware.broadcastradio.ProgramSelector makeHalFmSelector(int freq) {
-        ProgramIdentifier halId = new ProgramIdentifier();
-        halId.type = IdentifierType.AMFM_FREQUENCY_KHZ;
-        halId.value = freq;
-
-        android.hardware.broadcastradio.ProgramSelector halSelector =
-                new android.hardware.broadcastradio.ProgramSelector();
-        halSelector.primaryId = halId;
-        halSelector.secondaryIds = new ProgramIdentifier[0];
-        return halSelector;
+        ProgramIdentifier halId = makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ, freq);
+        return makeHalSelector(halId, /* secondaryIds= */ new ProgramIdentifier[0]);
     }
 
-    static ProgramInfo programInfoToHalProgramInfo(RadioManager.ProgramInfo info) {
-        // Note that because ConversionUtils does not by design provide functions for all
-        // conversions, this function only copies fields that are set by makeProgramInfo().
-        ProgramInfo hwInfo = new ProgramInfo();
-        hwInfo.selector = ConversionUtils.programSelectorToHalProgramSelector(info.getSelector());
-        hwInfo.logicallyTunedTo =
-                ConversionUtils.identifierToHalProgramIdentifier(info.getLogicallyTunedTo());
-        hwInfo.physicallyTunedTo =
-                ConversionUtils.identifierToHalProgramIdentifier(info.getPhysicallyTunedTo());
-        hwInfo.signalQuality = info.getSignalStrength();
-        hwInfo.relatedContent = new ProgramIdentifier[]{};
-        hwInfo.metadata = new Metadata[]{};
-        return hwInfo;
+    static android.hardware.broadcastradio.ProgramSelector makeHalSelector(
+            ProgramIdentifier primaryId, ProgramIdentifier[] secondaryIds) {
+        android.hardware.broadcastradio.ProgramSelector hwSelector =
+                new android.hardware.broadcastradio.ProgramSelector();
+        hwSelector.primaryId = primaryId;
+        hwSelector.secondaryIds = secondaryIds;
+        return hwSelector;
     }
 
     static ProgramInfo makeHalProgramInfo(
             android.hardware.broadcastradio.ProgramSelector hwSel, int hwSignalQuality) {
+        return makeHalProgramInfo(hwSel, hwSel.primaryId, hwSel.primaryId, hwSignalQuality);
+    }
+
+    static ProgramInfo makeHalProgramInfo(
+            android.hardware.broadcastradio.ProgramSelector hwSel,
+            ProgramIdentifier logicallyTunedTo, ProgramIdentifier physicallyTunedTo,
+            int hwSignalQuality) {
         ProgramInfo hwInfo = new ProgramInfo();
         hwInfo.selector = hwSel;
-        hwInfo.logicallyTunedTo = hwSel.primaryId;
-        hwInfo.physicallyTunedTo = hwSel.primaryId;
+        hwInfo.logicallyTunedTo = logicallyTunedTo;
+        hwInfo.physicallyTunedTo = physicallyTunedTo;
         hwInfo.signalQuality = hwSignalQuality;
         hwInfo.relatedContent = new ProgramIdentifier[]{};
         hwInfo.metadata = new Metadata[]{};
         return hwInfo;
     }
 
+    static ProgramListChunk makeProgramListChunk(boolean purge, boolean complete,
+            ProgramInfo[] modified, ProgramIdentifier[] removed) {
+        ProgramListChunk halChunk = new ProgramListChunk();
+        halChunk.purge = purge;
+        halChunk.complete = complete;
+        halChunk.modified = modified;
+        halChunk.removed = removed;
+        return halChunk;
+    }
+
     static VendorKeyValue makeVendorKeyValue(String vendorKey, String vendorValue) {
         VendorKeyValue vendorKeyValue = new VendorKeyValue();
         vendorKeyValue.key = vendorKey;
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
index f404082..98103f6 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java
@@ -173,6 +173,19 @@
     }
 
     @Test
+    public void openSession_withoutAudio_fails() throws Exception {
+        createBroadcastRadioService();
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID,
+                        /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock,
+                        TARGET_SDK_VERSION));
+
+        assertWithMessage("Exception for opening session without audio")
+                .that(thrown).hasMessageThat().contains("not supported");
+    }
+
+    @Test
     public void binderDied_forDeathRecipient() throws Exception {
         createBroadcastRadioService();
 
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
index a1cebb6..710c150 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -20,9 +20,13 @@
 import android.hardware.broadcastradio.AmFmRegionConfig;
 import android.hardware.broadcastradio.DabTableEntry;
 import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.ProgramIdentifier;
+import android.hardware.broadcastradio.ProgramInfo;
+import android.hardware.broadcastradio.ProgramListChunk;
 import android.hardware.broadcastradio.Properties;
 import android.hardware.broadcastradio.VendorKeyValue;
 import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramList;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
 import android.os.Build;
@@ -33,19 +37,20 @@
 import org.junit.Test;
 
 import java.util.Map;
+import java.util.Set;
 
 public final class ConversionUtilsTest {
 
-    private static final int FM_LOWER_LIMIT = 87500;
-    private static final int FM_UPPER_LIMIT = 108000;
+    private static final int FM_LOWER_LIMIT = 87_500;
+    private static final int FM_UPPER_LIMIT = 108_000;
     private static final int FM_SPACING = 200;
     private static final int AM_LOWER_LIMIT = 540;
-    private static final int AM_UPPER_LIMIT = 1700;
+    private static final int AM_UPPER_LIMIT = 1_700;
     private static final int AM_SPACING = 10;
     private static final String DAB_ENTRY_LABEL_1 = "5A";
-    private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+    private static final int DAB_ENTRY_FREQUENCY_1 = 174_928;
     private static final String DAB_ENTRY_LABEL_2 = "12D";
-    private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+    private static final int DAB_ENTRY_FREQUENCY_2 = 229_072;
     private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
     private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
     private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
@@ -57,6 +62,50 @@
     private static final String TEST_VERSION = "versionMock";
     private static final String TEST_SERIAL = "serialMock";
 
+    private static final int TEST_SIGNAL_QUALITY = 1;
+    private static final long TEST_DAB_DMB_SID_EXT_VALUE = 0xA000000111L;
+    private static final long TEST_DAB_ENSEMBLE_VALUE = 0x1001;
+    private static final long TEST_DAB_FREQUENCY_VALUE = 220_352;
+    private static final long TEST_FM_FREQUENCY_VALUE = 92_100;
+    private static final long TEST_VENDOR_ID_VALUE = 9_901;
+
+    private static final ProgramSelector.Identifier TEST_DAB_SID_EXT_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+    private static final ProgramSelector.Identifier TEST_DAB_ENSEMBLE_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+    private static final ProgramSelector.Identifier TEST_DAB_FREQUENCY_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, TEST_DAB_FREQUENCY_VALUE);
+    private static final ProgramSelector.Identifier TEST_FM_FREQUENCY_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_FM_FREQUENCY_VALUE);
+    private static final ProgramSelector.Identifier TEST_VENDOR_ID =
+            new ProgramSelector.Identifier(
+                    ProgramSelector.IDENTIFIER_TYPE_VENDOR_START, TEST_VENDOR_ID_VALUE);
+
+    private static final ProgramIdentifier TEST_HAL_DAB_SID_EXT_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_SID_EXT, TEST_DAB_DMB_SID_EXT_VALUE);
+    private static final ProgramIdentifier TEST_HAL_DAB_ENSEMBLE_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_ENSEMBLE, TEST_DAB_ENSEMBLE_VALUE);
+    private static final ProgramIdentifier TEST_HAL_DAB_FREQUENCY_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.DAB_FREQUENCY_KHZ,
+                    TEST_DAB_FREQUENCY_VALUE);
+    private static final ProgramIdentifier TEST_HAL_FM_FREQUENCY_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.AMFM_FREQUENCY_KHZ,
+                    TEST_FM_FREQUENCY_VALUE);
+    private static final ProgramIdentifier TEST_HAL_VENDOR_ID =
+            AidlTestUtils.makeHalIdentifier(IdentifierType.VENDOR_START,
+                    TEST_VENDOR_ID_VALUE);
+
+    private static final ProgramSelector TEST_DAB_SELECTOR = new ProgramSelector(
+            ProgramSelector.PROGRAM_TYPE_DAB, TEST_DAB_SID_EXT_ID,
+            new ProgramSelector.Identifier[]{TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID},
+            /* vendorIds= */ null);
+    private static final ProgramSelector TEST_FM_SELECTOR =
+            AidlTestUtils.makeFmSelector(TEST_FM_FREQUENCY_VALUE);
+
     private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
     private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
 
@@ -159,6 +208,246 @@
     }
 
     @Test
+    public void identifierToHalProgramIdentifier_withDabId() {
+        ProgramIdentifier halDabId =
+                ConversionUtils.identifierToHalProgramIdentifier(TEST_DAB_SID_EXT_ID);
+
+        expect.withMessage("Converted HAL DAB identifier").that(halDabId)
+                .isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+    }
+
+    @Test
+    public void identifierFromHalProgramIdentifier_withDabId() {
+        ProgramSelector.Identifier dabId =
+                ConversionUtils.identifierFromHalProgramIdentifier(TEST_HAL_DAB_SID_EXT_ID);
+
+        expect.withMessage("Converted DAB identifier").that(dabId).isEqualTo(TEST_DAB_SID_EXT_ID);
+    }
+
+    @Test
+    public void programSelectorToHalProgramSelector_withValidSelector() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                ConversionUtils.programSelectorToHalProgramSelector(TEST_DAB_SELECTOR);
+
+        expect.withMessage("Primary identifier of converted HAL DAB selector")
+                .that(halDabSelector.primaryId).isEqualTo(TEST_HAL_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary identifiers of converted HAL DAB selector")
+                .that(halDabSelector.secondaryIds).asList()
+                .containsExactly(TEST_HAL_DAB_FREQUENCY_ID, TEST_HAL_DAB_ENSEMBLE_ID);
+    }
+
+    @Test
+    public void programSelectorToHalProgramSelector_withInvalidDabSelector_returnsNull() {
+        ProgramSelector invalidDbSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                TEST_DAB_SID_EXT_ID,
+                new ProgramSelector.Identifier[0],
+                new long[0]);
+
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                ConversionUtils.programSelectorToHalProgramSelector(invalidDbSelector);
+
+        expect.withMessage("Invalid HAL DAB selector without required secondary ids")
+                .that(invalidHalDabSelector).isNull();
+    }
+
+    @Test
+    public void programSelectorFromHalProgramSelector_withValidSelector() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+
+        ProgramSelector dabSelector =
+                ConversionUtils.programSelectorFromHalProgramSelector(halDabSelector);
+
+        expect.withMessage("Primary identifier of converted DAB selector")
+                .that(dabSelector.getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary identifiers of converted DAB selector")
+                .that(dabSelector.getSecondaryIds()).asList()
+                .containsExactly(TEST_DAB_FREQUENCY_ID, TEST_DAB_ENSEMBLE_ID);
+    }
+
+    @Test
+    public void programSelectorFromHalProgramSelector_withInvalidSelector_returnsNull() {
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{});
+
+        ProgramSelector invalidDabSelector =
+                ConversionUtils.programSelectorFromHalProgramSelector(invalidHalDabSelector);
+
+        expect.withMessage("Invalid DAB selector without required secondary ids")
+                .that(invalidDabSelector).isNull();
+    }
+
+    @Test
+    public void programInfoFromHalProgramInfo_withValidProgramInfo() {
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+        RadioManager.ProgramInfo programInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+        expect.withMessage("Primary id of selector of converted program info")
+                .that(programInfo.getSelector().getPrimaryId()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Secondary id of selector of converted program info")
+                .that(programInfo.getSelector().getSecondaryIds()).asList()
+                .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_DAB_FREQUENCY_ID);
+        expect.withMessage("Logically tuned identifier of converted program info")
+                .that(programInfo.getLogicallyTunedTo()).isEqualTo(TEST_DAB_SID_EXT_ID);
+        expect.withMessage("Physically tuned identifier of converted program info")
+                .that(programInfo.getPhysicallyTunedTo()).isEqualTo(TEST_DAB_FREQUENCY_ID);
+        expect.withMessage("Signal quality of converted program info")
+                .that(programInfo.getSignalStrength()).isEqualTo(TEST_SIGNAL_QUALITY);
+    }
+
+    @Test
+    public void programInfoFromHalProgramInfo_withInvalidDabProgramInfo() {
+        android.hardware.broadcastradio.ProgramSelector invalidHalDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID,
+                new ProgramIdentifier[]{TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halProgramInfo = AidlTestUtils.makeHalProgramInfo(invalidHalDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+
+        RadioManager.ProgramInfo programInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+
+        expect.withMessage("Invalid DAB program info with incorrect type of physically tuned to id")
+                .that(programInfo).isNull();
+    }
+
+    @Test
+    public void chunkFromHalProgramListChunk_withValidChunk() {
+        boolean purge = false;
+        boolean complete = true;
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo dabInfo =
+                ConversionUtils.programInfoFromHalProgramInfo(halDabInfo);
+        ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+                new ProgramInfo[]{halDabInfo},
+                new ProgramIdentifier[]{TEST_HAL_VENDOR_ID, TEST_HAL_FM_FREQUENCY_ID});
+
+        ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+        expect.withMessage("Purged state of the converted valid program list chunk")
+                .that(chunk.isPurge()).isEqualTo(purge);
+        expect.withMessage("Completion state of the converted valid program list chunk")
+                .that(chunk.isComplete()).isEqualTo(complete);
+        expect.withMessage("Modified program info in the converted valid program list chunk")
+                .that(chunk.getModified()).containsExactly(dabInfo);
+        expect.withMessage("Removed program ides in the converted valid program list chunk")
+                .that(chunk.getRemoved()).containsExactly(TEST_VENDOR_ID, TEST_FM_FREQUENCY_ID);
+    }
+
+    @Test
+    public void chunkFromHalProgramListChunk_withInvalidModifiedProgramInfo() {
+        boolean purge = true;
+        boolean complete = false;
+        android.hardware.broadcastradio.ProgramSelector halDabSelector =
+                AidlTestUtils.makeHalSelector(TEST_HAL_DAB_SID_EXT_ID, new ProgramIdentifier[]{
+                        TEST_HAL_DAB_ENSEMBLE_ID, TEST_HAL_DAB_FREQUENCY_ID});
+        ProgramInfo halDabInfo = AidlTestUtils.makeHalProgramInfo(halDabSelector,
+                TEST_HAL_DAB_SID_EXT_ID, TEST_HAL_DAB_ENSEMBLE_ID, TEST_SIGNAL_QUALITY);
+        ProgramListChunk halChunk = AidlTestUtils.makeProgramListChunk(purge, complete,
+                new ProgramInfo[]{halDabInfo}, new ProgramIdentifier[]{TEST_HAL_FM_FREQUENCY_ID});
+
+        ProgramList.Chunk chunk = ConversionUtils.chunkFromHalProgramListChunk(halChunk);
+
+        expect.withMessage("Purged state of the converted invalid program list chunk")
+                .that(chunk.isPurge()).isEqualTo(purge);
+        expect.withMessage("Completion state of the converted invalid program list chunk")
+                .that(chunk.isComplete()).isEqualTo(complete);
+        expect.withMessage("Modified program info in the converted invalid program list chunk")
+                .that(chunk.getModified()).isEmpty();
+        expect.withMessage("Removed program ids in the converted invalid program list chunk")
+                .that(chunk.getRemoved()).containsExactly(TEST_FM_FREQUENCY_ID);
+    }
+
+    @Test
+    public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+        expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR)
+                .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR,
+                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+        expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR)
+                .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR,
+                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+    }
+
+    @Test
+    public void programInfoMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+
+        expect.withMessage("Program info %s without required SDK version", dabProgramInfo)
+                .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo,
+                        Build.VERSION_CODES.TIRAMISU)).isFalse();
+    }
+
+    @Test
+    public void programInfoMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() {
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+
+        expect.withMessage("Program info %s with required SDK version", fmProgramInfo)
+                .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo,
+                        Build.VERSION_CODES.TIRAMISU)).isTrue();
+    }
+
+    @Test
+    public void convertChunkToTargetSdkVersion_withLowerSdkVersion() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+        ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+                Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+        ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+                Build.VERSION_CODES.TIRAMISU);
+
+        expect.withMessage(
+                "Purged state of the converted program list chunk with lower SDK version")
+                .that(convertedChunk.isPurge()).isEqualTo(chunk.isPurge());
+        expect.withMessage(
+                "Completion state of the converted program list chunk with lower SDK version")
+                .that(convertedChunk.isComplete()).isEqualTo(chunk.isComplete());
+        expect.withMessage(
+                "Modified program info in the converted program list chunk with lower SDK version")
+                .that(convertedChunk.getModified()).containsExactly(fmProgramInfo);
+        expect.withMessage(
+                "Removed program ids in the converted program list chunk with lower SDK version")
+                .that(convertedChunk.getRemoved())
+                .containsExactly(TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID);
+    }
+
+    @Test
+    public void convertChunkToTargetSdkVersion_withRequiredSdkVersion() {
+        RadioManager.ProgramInfo dabProgramInfo = AidlTestUtils.makeProgramInfo(TEST_DAB_SELECTOR,
+                TEST_DAB_SID_EXT_ID, TEST_DAB_FREQUENCY_ID, TEST_SIGNAL_QUALITY);
+        RadioManager.ProgramInfo fmProgramInfo = AidlTestUtils.makeProgramInfo(TEST_FM_SELECTOR,
+                TEST_SIGNAL_QUALITY);
+        ProgramList.Chunk chunk = new ProgramList.Chunk(/* purge= */ true,
+                /* complete= */ true, Set.of(dabProgramInfo, fmProgramInfo),
+                Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID));
+
+        ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk,
+                Build.VERSION_CODES.CUR_DEVELOPMENT);
+
+        expect.withMessage("Converted program list chunk with required SDK version")
+                .that(convertedChunk).isEqualTo(chunk);
+    }
+
+    @Test
     public void announcementFromHalAnnouncement_typesMatch() {
         expect.withMessage("Announcement type")
                 .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
index c5c6349..d7723ac 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java
@@ -26,7 +26,9 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -43,6 +45,8 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioTuner;
 import android.os.Build;
+import android.os.ParcelableException;
+import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -57,7 +61,6 @@
 import org.mockito.Mock;
 import org.mockito.verification.VerificationWithTimeout;
 
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
@@ -71,10 +74,10 @@
             timeout(/* millis= */ 200);
     private static final int SIGNAL_QUALITY = 1;
     private static final long AM_FM_FREQUENCY_SPACING = 500;
-    private static final long[] AM_FM_FREQUENCY_LIST = {97500, 98100, 99100};
+    private static final long[] AM_FM_FREQUENCY_LIST = {97_500, 98_100, 99_100};
     private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
             new RadioManager.FmBandDescriptor(RadioManager.REGION_ITU_1, RadioManager.BAND_FM,
-                    /* lowerLimit= */ 87500, /* upperLimit= */ 108000, /* spacing= */ 100,
+                    /* lowerLimit= */ 87_500, /* upperLimit= */ 108_000, /* spacing= */ 100,
                     /* stereo= */ false, /* rds= */ false, /* ta= */ false, /* af= */ false,
                     /* ea= */ false);
     private static final RadioManager.BandConfig FM_BAND_CONFIG =
@@ -200,6 +203,17 @@
     }
 
     @Test
+    public void setConfiguration_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onConfigurationChanged(FM_BAND_CONFIG);
+    }
+
+    @Test
     public void getConfiguration() throws Exception {
         openAidlClients(/* numClients= */ 1);
         mTunerSessions[0].setConfiguration(FM_BAND_CONFIG);
@@ -330,10 +344,17 @@
 
     @Test
     public void tune_withUnsupportedSelector_throwsException() throws Exception {
+        ProgramSelector.Identifier dabPrimaryId =
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT,
+                        /* value= */ 0xA000000111L);
+        ProgramSelector.Identifier[] dabSecondaryIds =  new ProgramSelector.Identifier[]{
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                        /* value= */ 1337),
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+                        /* value= */ 225648)};
+        ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector unsupportedSelector = AidlTestUtils.makeProgramSelector(
-                ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
-                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
 
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
                 () -> mTunerSessions[0].tune(unsupportedSelector));
@@ -343,7 +364,22 @@
     }
 
     @Test
-    public void tune_forCurrentUser_doesNotTune() throws Exception {
+    public void tune_withInvalidSelector_throwsIllegalArgumentException() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector.Identifier invalidDabId = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE, /* value= */ 0x1001);
+        ProgramSelector invalidSel = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                invalidDabId, new ProgramSelector.Identifier[0], new long[0]);
+
+        IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class,
+                () -> mTunerSessions[0].tune(invalidSel));
+
+        assertWithMessage("Exception for tuning on DAB selector without DAB_SID_EXT primary id")
+                .that(thrown).hasMessageThat().contains("tune: INVALID_ARGUMENTS");
+    }
+
+    @Test
+    public void tune_forNonCurrentUser_doesNotTune() throws Exception {
         openAidlClients(/* numClients= */ 1);
         doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -357,6 +393,21 @@
     }
 
     @Test
+    public void tune_withHalHasUnknownError_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector sel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        doThrow(new ServiceSpecificException(Result.UNKNOWN_ERROR))
+                .when(mBroadcastRadioMock).tune(any());
+
+        ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+            mTunerSessions[0].tune(sel);
+        });
+
+        assertWithMessage("Exception for tuning when HAL has unknown error")
+                .that(thrown).hasMessageThat().contains("UNKNOWN_ERROR");
+    }
+
+    @Test
     public void step_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[1];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -391,6 +442,35 @@
     }
 
     @Test
+    public void step_forNonCurrentUser_doesNotStep() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[1];
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(any());
+    }
+
+    @Test
+    public void step_withHalInInvalidState_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doThrow(new ServiceSpecificException(Result.INVALID_STATE))
+                .when(mBroadcastRadioMock).step(anyBoolean());
+
+        IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> {
+            mTunerSessions[0].step(/* directionDown= */ true, /* skipSubChannel= */ false);
+        });
+
+        assertWithMessage("Exception for stepping when HAL is in invalid state")
+                .that(thrown).hasMessageThat().contains("INVALID_STATE");
+    }
+
+    @Test
     public void seek_withDirectionUp() throws Exception {
         long initFreq = AM_FM_FREQUENCY_LIST[2];
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
@@ -432,11 +512,44 @@
                 ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
 
         mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
         verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT)
                 .onCurrentProgramInfoChanged(seekUpInfo);
     }
 
     @Test
+    public void seek_forNonCurrentUser_doesNotSeek() throws Exception {
+        long initFreq = AM_FM_FREQUENCY_LIST[2];
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(initFreq);
+        RadioManager.ProgramInfo seekUpInfo = AidlTestUtils.makeProgramInfo(
+                AidlTestUtils.makeFmSelector(getSeekFrequency(initFreq, /* seekDown= */ true)),
+                SIGNAL_QUALITY);
+        openAidlClients(/* numClients= */ 1);
+        mHalCurrentInfo = AidlTestUtils.makeHalProgramInfo(
+                ConversionUtils.programSelectorToHalProgramSelector(initialSel), SIGNAL_QUALITY);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0))
+                .onCurrentProgramInfoChanged(seekUpInfo);
+    }
+
+    @Test
+    public void seek_withHalHasInternalError_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doThrow(new ServiceSpecificException(Result.INTERNAL_ERROR))
+                .when(mBroadcastRadioMock).seek(anyBoolean(), anyBoolean());
+
+        ParcelableException thrown = assertThrows(ParcelableException.class, () -> {
+            mTunerSessions[0].seek(/* directionDown= */ true, /* skipSubChannel= */ false);
+        });
+
+        assertWithMessage("Exception for seeking when HAL has internal error")
+                .that(thrown).hasMessageThat().contains("INTERNAL_ERROR");
+    }
+
+    @Test
     public void cancel() throws Exception {
         openAidlClients(/* numClients= */ 1);
         ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
@@ -448,6 +561,32 @@
     }
 
     @Test
+    public void cancel_forNonCurrentUser_doesNotCancel() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]);
+        mTunerSessions[0].tune(initialSel);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].cancel();
+
+        verify(mBroadcastRadioMock, never()).cancel();
+    }
+
+    @Test
+    public void cancel_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        String exceptionMessage = "HAL service died.";
+        doThrow(new RemoteException(exceptionMessage)).when(mBroadcastRadioMock).cancel();
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].cancel();
+        });
+
+        assertWithMessage("Exception for canceling when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void getImage_withInvalidId_throwsIllegalArgumentException() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int imageId = IBroadcastRadio.INVALID_IMAGE;
@@ -471,6 +610,21 @@
     }
 
     @Test
+    public void getImage_whenHalThrowsException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.getImage(anyInt()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].getImage(/* id= */ 1);
+        });
+
+        assertWithMessage("Exception for getting image when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void startBackgroundScan() throws Exception {
         openAidlClients(/* numClients= */ 1);
 
@@ -480,6 +634,16 @@
     }
 
     @Test
+    public void startBackgroundScan_forNonCurrentUser_doesNotInvokesCallback() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].startBackgroundScan();
+
+        verify(mAidlTunerCallbackMocks[0], CALLBACK_TIMEOUT.times(0)).onBackgroundScanComplete();
+    }
+
+    @Test
     public void stopProgramListUpdates() throws Exception {
         openAidlClients(/* numClients= */ 1);
         ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
@@ -492,6 +656,19 @@
     }
 
     @Test
+    public void stopProgramListUpdates_forNonCurrentUser_doesNotStopUpdates() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        ProgramList.Filter aidlFilter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(),
+                /* includeCategories= */ true, /* excludeModifications= */ false);
+        mTunerSessions[0].startProgramListUpdates(aidlFilter);
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].stopProgramListUpdates();
+
+        verify(mBroadcastRadioMock, never()).stopProgramListUpdates();
+    }
+
+    @Test
     public void isConfigFlagSupported_withUnsupportedFlag_returnsFalse() throws Exception {
         openAidlClients(/* numClients= */ 1);
         int flag = UNSUPPORTED_CONFIG_FLAG;
@@ -547,6 +724,17 @@
     }
 
     @Test
+    public void setConfigFlag_forNonCurrentUser_doesNotSetConfigFlag() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setConfigFlag(flag, /* value= */ true);
+
+        verify(mBroadcastRadioMock, never()).setConfigFlag(flag, /* value= */ true);
+    }
+
+    @Test
     public void isConfigFlagSet_withUnsupportedFlag_throwsRuntimeException()
             throws Exception {
         openAidlClients(/* numClients= */ 1);
@@ -556,7 +744,7 @@
             mTunerSessions[0].isConfigFlagSet(flag);
         });
 
-        assertWithMessage("Exception for check if unsupported flag %s is set", flag)
+        assertWithMessage("Exception for checking if unsupported flag %s is set", flag)
                 .that(thrown).hasMessageThat().contains("isConfigFlagSet: NOT_SUPPORTED");
     }
 
@@ -574,6 +762,20 @@
     }
 
     @Test
+    public void isConfigFlagSet_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        int flag = UNSUPPORTED_CONFIG_FLAG + 1;
+        doThrow(new RemoteException()).when(mBroadcastRadioMock).isConfigFlagSet(anyInt());
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].isConfigFlagSet(flag);
+        });
+
+        assertWithMessage("Exception for checking config flag when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains("Failed to check flag");
+    }
+
+    @Test
     public void setParameters_withMockParameters() throws Exception {
         openAidlClients(/* numClients= */ 1);
         Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
@@ -586,16 +788,58 @@
     }
 
     @Test
+    public void setParameters_forNonCurrentUser_doesNotSetParameters() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+                "mockParam2", "mockValue2");
+        doReturn(false).when(() -> RadioServiceUserController.isCurrentOrSystemUser());
+
+        mTunerSessions[0].setParameters(parametersSet);
+
+        verify(mBroadcastRadioMock, never()).setParameters(any());
+    }
+
+    @Test
+    public void setParameters_whenHalThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        Map<String, String> parametersSet = Map.of("mockParam1", "mockValue1",
+                "mockParam2", "mockValue2");
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.setParameters(any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].setParameters(parametersSet);
+        });
+
+        assertWithMessage("Exception for setting parameters when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
+    }
+
+    @Test
     public void getParameters_withMockKeys() throws Exception {
         openAidlClients(/* numClients= */ 1);
-        List<String> parameterKeys = new ArrayList<>(2);
-        parameterKeys.add("mockKey1");
-        parameterKeys.add("mockKey2");
+        List<String> parameterKeys = List.of("mockKey1", "mockKey2");
 
         mTunerSessions[0].getParameters(parameterKeys);
 
-        verify(mBroadcastRadioMock).getParameters(
-                parameterKeys.toArray(new String[0]));
+        verify(mBroadcastRadioMock).getParameters(parameterKeys.toArray(new String[0]));
+    }
+
+    @Test
+    public void getParameters_whenServiceThrowsRemoteException_fails() throws Exception {
+        openAidlClients(/* numClients= */ 1);
+        List<String> parameterKeys = List.of("mockKey1", "mockKey2");
+        String exceptionMessage = "HAL service died.";
+        when(mBroadcastRadioMock.getParameters(any()))
+                .thenThrow(new RemoteException(exceptionMessage));
+
+        RuntimeException thrown = assertThrows(RuntimeException.class, () -> {
+            mTunerSessions[0].getParameters(parameterKeys);
+        });
+
+        assertWithMessage("Exception for getting parameters when HAL throws remote exception")
+                .that(thrown).hasMessageThat().contains(exceptionMessage);
     }
 
     @Test
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
index db16c03..6e54dcf 100644
--- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/hal2/TunerSessionHidlTest.java
@@ -324,10 +324,17 @@
 
     @Test
     public void tune_withUnsupportedSelector_throwsException() throws Exception {
+        ProgramSelector.Identifier dabPrimaryId =
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT,
+                        /* value= */ 0xA00111);
+        ProgramSelector.Identifier[] dabSecondaryIds =  new ProgramSelector.Identifier[]{
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE,
+                        /* value= */ 1337),
+                new ProgramSelector.Identifier(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY,
+                        /* value= */ 225648)};
+        ProgramSelector unsupportedSelector = new ProgramSelector(ProgramSelector.PROGRAM_TYPE_DAB,
+                dabPrimaryId, dabSecondaryIds, /* vendorIds= */ null);
         openAidlClients(/* numClients= */ 1);
-        ProgramSelector unsupportedSelector = TestUtils.makeProgramSelector(
-                ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, new ProgramSelector.Identifier(
-                        ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY, /* value= */ 300));
 
         UnsupportedOperationException thrown = assertThrows(UnsupportedOperationException.class,
                 () -> mTunerSessions[0].tune(unsupportedSelector));
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml
deleted file mode 100644
index 1f57318..0000000
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_amp_24.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="#d14d2c">
-  <path
-      android:fillColor="@android:color/white"
-      android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11.17,19.5h-0.83l0.82,-5.83 -4.18,0.01 5.85,-9.17h0.83l-0.84,5.84h4.17l-5.82,9.15z"/>
-</vector>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_energy_24.xml
similarity index 70%
rename from core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml
rename to core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_energy_24.xml
index 39f9689..11f8d7b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_custom_24.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/drawable/gm_energy_24.xml
@@ -1,9 +1,9 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="24dp"
-    android:height="24dp"
-    android:viewportWidth="24"
-    android:viewportHeight="24"
-    android:tint="#d14d2c">
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24"
+        android:viewportHeight="24"
+        android:tint="#d14d2c">
   <path
       android:fillColor="@android:color/white"
       android:pathData="M22,9L22,7h-2L20,5c0,-1.1 -0.9,-2 -2,-2L4,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-2h2v-2h-2v-2h2v-2h-2L20,9h2zM18,19L4,19L4,5h14v14zM6,13h5v4L6,17v-4zM12,7h4v3h-4L12,7zM6,7h5v5L6,12L6,7zM12,11h4v6h-4v-6z"/>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
index 2dbe48b..6cc70bd 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
+++ b/core/tests/batterystatstests/BatteryStatsViewer/res/values/colors.xml
@@ -17,5 +17,5 @@
 
 <resources>
     <color name="battery_consumer_bg_power_profile">#ffffff</color>
-    <color name="battery_consumer_bg_measured_energy">#fff5eb</color>
+    <color name="battery_consumer_bg_energy_consumption">#fff5eb</color>
 </resources>
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
index 19bb718..f691a1b 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryConsumerData.java
@@ -33,15 +33,15 @@
 
     enum EntryType {
         UID_TOTAL_POWER,
-        UID_POWER_MODELED,
-        UID_POWER_MODELED_PROCESS_STATE,
-        UID_POWER_MEASURED,
-        UID_POWER_MEASURED_PROCESS_STATE,
+        UID_POWER_PROFILE,
+        UID_POWER_PROFILE_PROCESS_STATE,
+        UID_POWER_ENERGY_CONSUMPTION,
+        UID_POWER_ENERGY_PROCESS_STATE,
         UID_POWER_CUSTOM,
         UID_DURATION,
         DEVICE_TOTAL_POWER,
         DEVICE_POWER_MODELED,
-        DEVICE_POWER_MEASURED,
+        DEVICE_POWER_ENERGY_CONSUMPTION,
         DEVICE_POWER_CUSTOM,
         DEVICE_DURATION,
     }
@@ -111,12 +111,12 @@
                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                             .getConsumedPower());
         } else {
-            addEntry("Consumed (measured)", EntryType.UID_TOTAL_POWER,
+            addEntry("Consumed (PowerStats)", EntryType.UID_TOTAL_POWER,
                     requestedBatteryConsumer.getConsumedPower(),
                     batteryUsageStats.getAggregateBatteryConsumer(
                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
                             .getConsumedPower());
-            addEntry("Consumed (modeled)", EntryType.UID_TOTAL_POWER,
+            addEntry("Consumed (PowerProfile)", EntryType.UID_TOTAL_POWER,
                     requestedModeledBatteryConsumer.getConsumedPower(),
                     modeledBatteryUsageStats.getAggregateBatteryConsumer(
                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_ALL_APPS)
@@ -128,21 +128,21 @@
             final int powerModel = requestedBatteryConsumer.getPowerModel(component);
             if (powerModel == BatteryConsumer.POWER_MODEL_POWER_PROFILE
                     || powerModel == BatteryConsumer.POWER_MODEL_UNDEFINED) {
-                addEntry(metricTitle, EntryType.UID_POWER_MODELED,
+                addEntry(metricTitle, EntryType.UID_POWER_PROFILE,
                         requestedBatteryConsumer.getConsumedPower(component),
                         totalPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE,
+                addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
                         requestedBatteryConsumer, component);
             } else {
-                addEntry(metricTitle + " (measured)", EntryType.UID_POWER_MEASURED,
+                addEntry(metricTitle + " (PowerStats)", EntryType.UID_POWER_ENERGY_CONSUMPTION,
                         requestedBatteryConsumer.getConsumedPower(component),
                         totalPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_MEASURED_PROCESS_STATE,
+                addProcessStateEntries(metricTitle, EntryType.UID_POWER_ENERGY_PROCESS_STATE,
                         requestedBatteryConsumer, component);
-                addEntry(metricTitle + " (modeled)", EntryType.UID_POWER_MODELED,
+                addEntry(metricTitle + " (PowerProfile)", EntryType.UID_POWER_PROFILE,
                         requestedModeledBatteryConsumer.getConsumedPower(component),
                         totalModeledPowerByComponentMah[component]);
-                addProcessStateEntries(metricTitle, EntryType.UID_POWER_MODELED_PROCESS_STATE,
+                addProcessStateEntries(metricTitle, EntryType.UID_POWER_PROFILE_PROCESS_STATE,
                         requestedModeledBatteryConsumer, component);
             }
         }
@@ -150,7 +150,7 @@
         for (int component = 0; component < customComponentCount; component++) {
             final String name = requestedBatteryConsumer.getCustomPowerComponentName(
                     BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
-            addEntry(name + " (custom)", EntryType.UID_POWER_CUSTOM,
+            addEntry(name + " (PowerStats)", EntryType.UID_POWER_CUSTOM,
                     requestedBatteryConsumer.getConsumedPowerForCustomComponent(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
                     totalCustomPowerByComponentMah[component]
@@ -223,10 +223,10 @@
                     deviceBatteryConsumer.getConsumedPower(),
                     appsBatteryConsumer.getConsumedPower());
         } else {
-            addEntry("Consumed (measured)", EntryType.DEVICE_TOTAL_POWER,
+            addEntry("Consumed (PowerStats)", EntryType.DEVICE_TOTAL_POWER,
                     deviceBatteryConsumer.getConsumedPower(),
                     appsBatteryConsumer.getConsumedPower());
-            addEntry("Consumed (modeled)", EntryType.DEVICE_TOTAL_POWER,
+            addEntry("Consumed (PowerProfile)", EntryType.DEVICE_TOTAL_POWER,
                     modeledDeviceBatteryConsumer.getConsumedPower(),
                     modeledAppsBatteryConsumer.getConsumedPower());
         }
@@ -244,10 +244,10 @@
                         deviceBatteryConsumer.getConsumedPower(component),
                         appsBatteryConsumer.getConsumedPower(component));
             } else {
-                addEntry(metricTitle + " (measured)", EntryType.DEVICE_POWER_MEASURED,
+                addEntry(metricTitle + " (PowerStats)", EntryType.DEVICE_POWER_ENERGY_CONSUMPTION,
                         deviceBatteryConsumer.getConsumedPower(component),
                         appsBatteryConsumer.getConsumedPower(component));
-                addEntry(metricTitle + " (modeled)", EntryType.DEVICE_POWER_MODELED,
+                addEntry(metricTitle + " (PowerProfile)", EntryType.DEVICE_POWER_MODELED,
                         modeledDeviceBatteryConsumer.getConsumedPower(component),
                         modeledAppsBatteryConsumer.getConsumedPower(component));
             }
@@ -258,7 +258,7 @@
         for (int component = 0; component < customComponentCount; component++) {
             final String name = deviceBatteryConsumer.getCustomPowerComponentName(
                     BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component);
-            addEntry(name + " (custom)", EntryType.DEVICE_POWER_CUSTOM,
+            addEntry(name + " (PowerStats)", EntryType.DEVICE_POWER_CUSTOM,
                     deviceBatteryConsumer.getConsumedPowerForCustomComponent(
                             BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + component),
                     appsBatteryConsumer.getConsumedPowerForCustomComponent(
diff --git a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
index 0f45c3b..e165c49 100644
--- a/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
+++ b/core/tests/batterystatstests/BatteryStatsViewer/src/com/android/frameworks/core/batterystatsviewer/BatteryStatsViewerActivity.java
@@ -285,38 +285,38 @@
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setProportionText(viewHolder.value2TextView, entry);
                     break;
-                case UID_POWER_MODELED:
+                case UID_POWER_PROFILE:
                     setTitleIconAndBackground(viewHolder, entry.title,
                             R.drawable.gm_calculate_24,
                             R.color.battery_consumer_bg_power_profile);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setProportionText(viewHolder.value2TextView, entry);
                     break;
-                case UID_POWER_MODELED_PROCESS_STATE:
+                case UID_POWER_PROFILE_PROCESS_STATE:
                     setTitleIconAndBackground(viewHolder, "    " + entry.title,
                             R.drawable.gm_calculate_24,
                             R.color.battery_consumer_bg_power_profile);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     viewHolder.value2TextView.setVisibility(View.INVISIBLE);
                     break;
-                case UID_POWER_MEASURED:
+                case UID_POWER_ENERGY_CONSUMPTION:
                     setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_amp_24,
-                            R.color.battery_consumer_bg_measured_energy);
+                            R.drawable.gm_energy_24,
+                            R.color.battery_consumer_bg_energy_consumption);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setProportionText(viewHolder.value2TextView, entry);
                     break;
-                case UID_POWER_MEASURED_PROCESS_STATE:
+                case UID_POWER_ENERGY_PROCESS_STATE:
                     setTitleIconAndBackground(viewHolder, "    " + entry.title,
-                            R.drawable.gm_amp_24,
-                            R.color.battery_consumer_bg_measured_energy);
+                            R.drawable.gm_energy_24,
+                            R.color.battery_consumer_bg_energy_consumption);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     viewHolder.value2TextView.setVisibility(View.INVISIBLE);
                     break;
                 case UID_POWER_CUSTOM:
                     setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_custom_24,
-                            R.color.battery_consumer_bg_measured_energy);
+                            R.drawable.gm_energy_24,
+                            R.color.battery_consumer_bg_energy_consumption);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setProportionText(viewHolder.value2TextView, entry);
                     break;
@@ -339,17 +339,17 @@
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setPowerText(viewHolder.value2TextView, entry.value2);
                     break;
-                case DEVICE_POWER_MEASURED:
+                case DEVICE_POWER_ENERGY_CONSUMPTION:
                     setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_amp_24,
-                            R.color.battery_consumer_bg_measured_energy);
+                            R.drawable.gm_energy_24,
+                            R.color.battery_consumer_bg_energy_consumption);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setPowerText(viewHolder.value2TextView, entry.value2);
                     break;
                 case DEVICE_POWER_CUSTOM:
                     setTitleIconAndBackground(viewHolder, entry.title,
-                            R.drawable.gm_custom_24,
-                            R.color.battery_consumer_bg_measured_energy);
+                            R.drawable.gm_energy_24,
+                            R.color.battery_consumer_bg_energy_consumption);
                     setPowerText(viewHolder.value1TextView, entry.value1);
                     setPowerText(viewHolder.value2TextView, entry.value2);
                     break;
diff --git a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
index f82523c..0676f89 100644
--- a/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
+++ b/core/tests/batterystatstests/BatteryUsageStatsProtoTests/src/com/android/internal/os/BatteryUsageStatsPulledTest.java
@@ -275,11 +275,11 @@
 
         uidBuilder.setConsumedPower(keyFg, 9100, BatteryConsumer.POWER_MODEL_POWER_PROFILE)
                 .setUsageDurationMillis(keyFg, 8100)
-                .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                .setConsumedPower(keyBg, 9200, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
                 .setUsageDurationMillis(keyBg, 8200)
-                .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                .setConsumedPower(keyFgs, 9300, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
                 .setUsageDurationMillis(keyFgs, 8300)
-                .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                .setConsumedPower(keyCached, 9400, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
                 .setUsageDurationMillis(keyFgs, 8400);
 
         builder.getOrCreateUidBatteryConsumerBuilder(UID_1)
@@ -302,7 +302,7 @@
                         BatteryConsumer.POWER_MODEL_POWER_PROFILE) // Empty
                 .setConsumedPower(
                         BatteryConsumer.POWER_COMPONENT_CAMERA, 20150,
-                        BatteryConsumer.POWER_MODEL_MEASURED_ENERGY)
+                        BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION)
                 .setConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID, 20200)
                 .setUsageDurationMillis(
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 4011933..9db8805 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -16,6 +16,7 @@
 
 package android.app.activity;
 
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_INVALID;
 import static android.content.Intent.ACTION_EDIT;
 import static android.content.Intent.ACTION_VIEW;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
@@ -204,7 +205,8 @@
         try {
             // Send process level config change.
             ClientTransaction transaction = newTransaction(activityThread, null);
-            transaction.addCallback(ConfigurationChangeItem.obtain(new Configuration(newConfig)));
+            transaction.addCallback(ConfigurationChangeItem.obtain(
+                    new Configuration(newConfig), DEVICE_ID_INVALID));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -413,12 +415,14 @@
         activity.mTestLatch = new CountDownLatch(1);
 
         ClientTransaction transaction = newTransaction(activityThread, null);
-        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigLandscape));
+        transaction.addCallback(ConfigurationChangeItem.obtain(
+                processConfigLandscape, DEVICE_ID_INVALID));
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread, activity.getActivityToken());
         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
-        transaction.addCallback(ConfigurationChangeItem.obtain(processConfigPortrait));
+        transaction.addCallback(ConfigurationChangeItem.obtain(
+                processConfigPortrait, DEVICE_ID_INVALID));
         transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
         appThread.scheduleTransaction(transaction);
 
@@ -530,7 +534,7 @@
                     ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
 
             activityThread.updatePendingConfiguration(newAppConfig);
-            activityThread.handleConfigurationChanged(newAppConfig);
+            activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
 
             try {
                 assertEquals("Virtual display orientation must not change when process"
@@ -548,7 +552,7 @@
     private static void restoreConfig(ActivityThread thread, Configuration originalConfig) {
         thread.getConfiguration().seq = originalConfig.seq - 1;
         ResourcesManager.getInstance().getConfiguration().seq = originalConfig.seq - 1;
-        thread.handleConfigurationChanged(originalConfig);
+        thread.handleConfigurationChanged(originalConfig, DEVICE_ID_INVALID);
     }
 
     @Test
@@ -626,7 +630,7 @@
             newAppConfig.seq++;
 
             final ActivityThread activityThread = activity.getActivityThread();
-            activityThread.handleConfigurationChanged(newAppConfig);
+            activityThread.handleConfigurationChanged(newAppConfig, DEVICE_ID_INVALID);
 
             // Verify that application config update was applied, but didn't change activity config.
             assertEquals("Activity config must not change if the process config changes",
diff --git a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
index cbf167c..27ee82e 100644
--- a/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupManagerTest.java
@@ -83,6 +83,13 @@
                 () -> mBackupManager.getBackupRestoreEventLogger(agent));
     }
 
+    @Test
+    public void testGetDelayedRestoreLogger_returnsRestoreLogger() {
+        BackupRestoreEventLogger logger = mBackupManager.getDelayedRestoreLogger();
+
+        assertThat(logger.getOperationType()).isEqualTo(OperationType.RESTORE);
+    }
+
     private static BackupAgent getTestAgent() {
         return new BackupAgent() {
             @Override
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 993ecf6..7f2b51d 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -98,15 +98,15 @@
 
     @Test
     public void testRecycleConfigurationChangeItem() {
-        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null);
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
         assertNotSame(item, emptyItem);
         assertFalse(item.equals(emptyItem));
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
         assertSame(item, item2);
         assertFalse(item2.equals(emptyItem));
     }
@@ -147,6 +147,7 @@
         persistableBundle.putInt("k", 4);
         IBinder assistToken = new Binder();
         IBinder shareableActivityToken = new Binder();
+        int deviceId = 3;
 
         Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
                 .setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
@@ -155,7 +156,7 @@
                 .setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
                 .setIsForward(true).setAssistToken(assistToken)
                 .setShareableActivityToken(shareableActivityToken)
-                .setTaskFragmentToken(new Binder()).build();
+                .setTaskFragmentToken(new Binder()).setDeviceId(deviceId).build();
 
         LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
         LaunchActivityItem item = itemSupplier.get();
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 2cd890c..0ed6a29 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -95,6 +95,7 @@
         private ActivityInfo mInfo;
         private Configuration mCurConfig;
         private Configuration mOverrideConfig;
+        private int mDeviceId;
         private String mReferrer;
         private IVoiceInteractor mVoiceInteractor;
         private int mProcState;
@@ -135,6 +136,11 @@
             return this;
         }
 
+        LaunchActivityItemBuilder setDeviceId(int deviceId) {
+            mDeviceId = deviceId;
+            return this;
+        }
+
         LaunchActivityItemBuilder setReferrer(String referrer) {
             mReferrer = referrer;
             return this;
@@ -207,7 +213,7 @@
 
         LaunchActivityItem build() {
             return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
-                    mCurConfig, mOverrideConfig, mReferrer, mVoiceInteractor,
+                    mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
                     null /* activityClientController */, mShareableActivityToken,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index a0ed026..f4bf1cd 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -25,53 +25,26 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityOptions;
-import android.app.ContentProviderHolder;
-import android.app.IApplicationThread;
-import android.app.IInstrumentationWatcher;
-import android.app.IUiAutomationConnection;
-import android.app.ProfilerInfo;
 import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
-import android.content.AutofillOptions;
-import android.content.ComponentName;
-import android.content.ContentCaptureOptions;
-import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ProviderInfoList;
-import android.content.pm.ServiceInfo;
-import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
-import android.os.Debug;
-import android.os.IBinder;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SharedMemory;
 import android.platform.test.annotations.Presubmit;
-import android.view.autofill.AutofillId;
-import android.view.translation.TranslationSpec;
-import android.view.translation.UiTranslationSpec;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.internal.app.IVoiceInteractor;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
 
 /**
  * Test parcelling and unparcelling of transactions and transaction items.
@@ -97,7 +70,7 @@
     @Test
     public void testConfigurationChange() {
         // Write to parcel
-        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config());
+        ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1 /* deviceId */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
diff --git a/core/tests/coretests/src/android/service/controls/OWNERS b/core/tests/coretests/src/android/service/controls/OWNERS
new file mode 100644
index 0000000..bf67034
--- /dev/null
+++ b/core/tests/coretests/src/android/service/controls/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/core/java/android/service/controls/OWNERS
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c917302..5ec93e5 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -71,6 +71,7 @@
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
 import android.view.animation.LinearInterpolator;
+import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -136,7 +137,8 @@
                         private boolean mImeRequestedShow;
 
                         @Override
-                        public int requestShow(boolean fromController) {
+                        public int requestShow(boolean fromController,
+                                ImeTracker.Token statsToken) {
                             if (fromController || mImeRequestedShow) {
                                 mImeRequestedShow = true;
                                 return SHOW_IMMEDIATELY;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 3a3eeee..0486e3c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -43,6 +43,7 @@
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowManager.BadTokenException;
 import android.view.WindowManager.LayoutParams;
+import android.view.inputmethod.ImeTracker;
 import android.widget.TextView;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -221,7 +222,8 @@
                     return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
                             () -> mMockTransaction, controller) {
                         @Override
-                        public int requestShow(boolean fromController) {
+                        public int requestShow(boolean fromController,
+                                ImeTracker.Token statsToken) {
                             return SHOW_IMMEDIATELY;
                         }
                     };
diff --git a/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
new file mode 100644
index 0000000..f93cd18
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/TextAppearanceInfoTest.java
@@ -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 android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextPaint;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ScaleXSpan;
+import android.text.style.StyleSpan;
+import android.text.style.TypefaceSpan;
+import android.view.ViewGroup;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextAppearanceInfoTest {
+    private static final float EPSILON = 0.0000001f;
+    private static final String TEST_TEXT = "Happy birthday!";
+    private static final float TEXT_SIZE = 16.5f;
+    private static final LocaleList TEXT_LOCALES = LocaleList.forLanguageTags("en,ja");
+    private static final String FONT_FAMILY_NAME = "sans-serif";
+    private static final int TEXT_WEIGHT = FontStyle.FONT_WEIGHT_MEDIUM;
+    private static final int TEXT_STYLE = Typeface.ITALIC;
+    private static final boolean ALL_CAPS = true;
+    private static final float SHADOW_DX = 2.0f;
+    private static final float SHADOW_DY = 2.0f;
+    private static final float SHADOW_RADIUS = 2.0f;
+    private static final int SHADOW_COLOR = Color.GRAY;
+    private static final boolean ELEGANT_TEXT_HEIGHT = true;
+    private static final boolean FALLBACK_LINE_SPACING = true;
+    private static final float LETTER_SPACING = 5.0f;
+    private static final String FONT_FEATURE_SETTINGS = "smcp";
+    private static final String FONT_VARIATION_SETTINGS = "'wdth' 1.0";
+    private static final int LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_LOOSE;
+    private static final int LINE_BREAK_WORD_STYLE = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+    private static final float TEXT_SCALEX = 1.5f;
+    private static final int HIGHLIGHT_TEXT_COLOR = Color.YELLOW;
+    private static final int TEXT_COLOR = Color.RED;
+    private static final int HINT_TEXT_COLOR = Color.GREEN;
+    private static final int LINK_TEXT_COLOR = Color.BLUE;
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final EditText mEditText = new EditText(mContext);
+    private final SpannableStringBuilder mSpannableText = new SpannableStringBuilder(TEST_TEXT);
+    private Canvas mCanvas;
+
+    @Before
+    public void setUp() {
+        mEditText.setText(mSpannableText);
+        mEditText.getPaint().setTextSize(TEXT_SIZE);
+        mEditText.setTextLocales(TEXT_LOCALES);
+        Typeface family = Typeface.create(FONT_FAMILY_NAME, Typeface.NORMAL);
+        mEditText.setTypeface(
+                Typeface.create(family, TEXT_WEIGHT, (TEXT_STYLE & Typeface.ITALIC) != 0));
+        mEditText.setAllCaps(ALL_CAPS);
+        mEditText.setShadowLayer(SHADOW_RADIUS, SHADOW_DX, SHADOW_DY, SHADOW_COLOR);
+        mEditText.setElegantTextHeight(ELEGANT_TEXT_HEIGHT);
+        mEditText.setFallbackLineSpacing(FALLBACK_LINE_SPACING);
+        mEditText.setLetterSpacing(LETTER_SPACING);
+        mEditText.setFontFeatureSettings(FONT_FEATURE_SETTINGS);
+        mEditText.setFontVariationSettings(FONT_VARIATION_SETTINGS);
+        mEditText.setLineBreakStyle(LINE_BREAK_STYLE);
+        mEditText.setLineBreakWordStyle(LINE_BREAK_WORD_STYLE);
+        mEditText.setTextScaleX(TEXT_SCALEX);
+        mEditText.setHighlightColor(HIGHLIGHT_TEXT_COLOR);
+        mEditText.setTextColor(TEXT_COLOR);
+        mEditText.setHintTextColor(HINT_TEXT_COLOR);
+        mEditText.setLinkTextColor(LINK_TEXT_COLOR);
+        ViewGroup.LayoutParams params =
+                new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mEditText.setLayoutParams(params);
+        mEditText.measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        Bitmap bitmap =
+                Bitmap.createBitmap(
+                        Math.max(1, mEditText.getMeasuredWidth()),
+                        Math.max(1, mEditText.getMeasuredHeight()),
+                        Bitmap.Config.ARGB_8888);
+        mEditText.layout(0, 0, mEditText.getMeasuredWidth(), mEditText.getMeasuredHeight());
+        mCanvas = new Canvas(bitmap);
+        mEditText.draw(mCanvas);
+    }
+
+    @Test
+    public void testCreateFromTextView_noSpan() {
+        assertTextAppearanceInfoContentsEqual(TextAppearanceInfo.createFromTextView(mEditText));
+    }
+
+    @Test
+    public void testCreateFromTextView_withSpan1() {
+        AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+        mSpannableText.setSpan(sizeSpan, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+        mSpannableText.setSpan(colorSpan, 1, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+        mSpannableText.setSpan(typefaceSpan, 2, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(mSpannableText);
+
+        // |Happy birthday!
+        mEditText.setSelection(0);
+        TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info1.getTextSize(), TEXT_SIZE, EPSILON);
+        assertEquals(info1.getTextColor(), TEXT_COLOR);
+        assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // H|appy birthday!
+        mEditText.setSelection(1);
+        TextAppearanceInfo info2 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info2.getTextSize(), 30f, EPSILON);
+        assertEquals(info2.getTextColor(), TEXT_COLOR);
+        assertEquals(info2.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Ha|ppy birthday!
+        mEditText.setSelection(2);
+        TextAppearanceInfo info3 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info3.getTextSize(), 30f, EPSILON);
+        assertEquals(info3.getTextColor(), Color.CYAN);
+        assertEquals(info3.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Ha[ppy birthday!]
+        mEditText.setSelection(2, mSpannableText.length());
+        TextAppearanceInfo info4 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info4.getTextSize(), 30f, EPSILON);
+        assertEquals(info4.getTextColor(), Color.CYAN);
+        assertEquals(info4.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+
+        // Happy| birthday!
+        mEditText.setSelection(5);
+        TextAppearanceInfo info5 = TextAppearanceInfo.createFromTextView(mEditText);
+        assertEquals(info5.getTextSize(), 30f, EPSILON);
+        assertEquals(info5.getTextColor(), Color.CYAN);
+        assertEquals(info5.getSystemFontFamilyName(), "cursive");
+    }
+
+    @Test
+    public void testCreateFromTextView_withSpan2() {
+        // aab|
+        SpannableStringBuilder spannableText = new SpannableStringBuilder("aab");
+
+        AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(30);
+        spannableText.setSpan(sizeSpan, 0, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+        ForegroundColorSpan colorSpan = new ForegroundColorSpan(Color.CYAN);
+        spannableText.setSpan(colorSpan, 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
+        spannableText.setSpan(styleSpan, 1, 2, Spanned.SPAN_EXCLUSIVE_INCLUSIVE);
+
+        TypefaceSpan typefaceSpan = new TypefaceSpan("cursive");
+        spannableText.setSpan(typefaceSpan, 3, 3, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+
+        ScaleXSpan scaleXSpan = new ScaleXSpan(2.0f);
+        spannableText.setSpan(scaleXSpan, 3, 3, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(spannableText);
+        mEditText.setSelection(3);
+        TextAppearanceInfo info = TextAppearanceInfo.createFromTextView(mEditText);
+
+        // The character before cursor 'b' should only have an AbsoluteSizeSpan.
+        assertEquals(info.getTextSize(), 30f, EPSILON);
+        assertEquals(info.getTextColor(), TEXT_COLOR);
+        assertEquals(info.getTextStyle(), TEXT_STYLE);
+        assertEquals(info.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+        assertEquals(info.getTextScaleX(), TEXT_SCALEX, EPSILON);
+    }
+
+    @Test
+    public void testCreateFromTextView_contradictorySpans() {
+        // Set multiple contradictory spans
+        AbsoluteSizeSpan sizeSpan1 = new AbsoluteSizeSpan(30);
+        CustomForegroundColorSpan colorSpan1 = new CustomForegroundColorSpan(Color.BLUE);
+        AbsoluteSizeSpan sizeSpan2 = new AbsoluteSizeSpan(10);
+        CustomForegroundColorSpan colorSpan2 = new CustomForegroundColorSpan(Color.GREEN);
+
+        mSpannableText.setSpan(sizeSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(colorSpan1, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(sizeSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+        mSpannableText.setSpan(colorSpan2, 0, 5, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        mEditText.setText(mSpannableText);
+        mEditText.draw(mCanvas);
+        mEditText.setSelection(3);
+        // Get a copy of the real TextPaint after setting the last span
+        TextPaint realTextPaint = colorSpan2.lastTextPaint;
+        assertNotNull(realTextPaint);
+        TextAppearanceInfo info1 = TextAppearanceInfo.createFromTextView(mEditText);
+        // Verify the real TextPaint equals the last span of multiple contradictory spans
+        assertEquals(info1.getTextSize(), 10f, EPSILON);
+        assertEquals(info1.getTextSize(), realTextPaint.getTextSize(), EPSILON);
+        assertEquals(info1.getTextColor(), Color.GREEN);
+        assertEquals(info1.getTextColor(), realTextPaint.getColor());
+        assertEquals(info1.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+    }
+
+    private void assertTextAppearanceInfoContentsEqual(TextAppearanceInfo textAppearanceInfo) {
+        assertEquals(textAppearanceInfo.getTextSize(), TEXT_SIZE, EPSILON);
+        assertEquals(textAppearanceInfo.getTextLocales(), TEXT_LOCALES);
+        assertEquals(textAppearanceInfo.getSystemFontFamilyName(), FONT_FAMILY_NAME);
+        assertEquals(textAppearanceInfo.getTextFontWeight(), TEXT_WEIGHT);
+        assertEquals(textAppearanceInfo.getTextStyle(), TEXT_STYLE);
+        assertEquals(textAppearanceInfo.isAllCaps(), ALL_CAPS);
+        assertEquals(textAppearanceInfo.getShadowRadius(), SHADOW_RADIUS, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowDx(), SHADOW_DX, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowDy(), SHADOW_DY, EPSILON);
+        assertEquals(textAppearanceInfo.getShadowColor(), SHADOW_COLOR);
+        assertEquals(textAppearanceInfo.isElegantTextHeight(), ELEGANT_TEXT_HEIGHT);
+        assertEquals(textAppearanceInfo.isFallbackLineSpacing(), FALLBACK_LINE_SPACING);
+        assertEquals(textAppearanceInfo.getLetterSpacing(), LETTER_SPACING, EPSILON);
+        assertEquals(textAppearanceInfo.getFontFeatureSettings(), FONT_FEATURE_SETTINGS);
+        assertEquals(textAppearanceInfo.getFontVariationSettings(), FONT_VARIATION_SETTINGS);
+        assertEquals(textAppearanceInfo.getLineBreakStyle(), LINE_BREAK_STYLE);
+        assertEquals(textAppearanceInfo.getLineBreakWordStyle(), LINE_BREAK_WORD_STYLE);
+        assertEquals(textAppearanceInfo.getTextScaleX(), TEXT_SCALEX, EPSILON);
+        assertEquals(textAppearanceInfo.getTextColor(), TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getHighlightTextColor(), HIGHLIGHT_TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getHintTextColor(), HINT_TEXT_COLOR);
+        assertEquals(textAppearanceInfo.getLinkTextColor(), LINK_TEXT_COLOR);
+    }
+
+    static class CustomForegroundColorSpan extends ForegroundColorSpan {
+        @Nullable public TextPaint lastTextPaint = null;
+
+        CustomForegroundColorSpan(int color) {
+            super(color);
+        }
+
+        CustomForegroundColorSpan(@NonNull Parcel src) {
+            super(src);
+        }
+
+        @Override
+        public void updateDrawState(@NonNull TextPaint tp) {
+            super.updateDrawState(tp);
+            // Copy the real TextPaint
+            TextPaint tpCopy = new TextPaint();
+            tpCopy.set(tp);
+            lastTextPaint = tpCopy;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index a1d0337..38c3aa0 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -16,6 +16,8 @@
 
 package com.android.internal.os;
 
+import com.android.internal.power.EnergyConsumerStatsTest;
+
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -34,7 +36,7 @@
         LongMultiStateCounterTest.class,
         PowerProfileTest.class,
 
-        com.android.internal.power.MeasuredEnergyStatsTest.class
+        EnergyConsumerStatsTest.class
     })
 public class BatteryStatsTests {
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
similarity index 81%
rename from core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
rename to core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
index 88349b3..e09cfd2 100644
--- a/core/tests/coretests/src/com/android/internal/power/MeasuredEnergyStatsTest.java
+++ b/core/tests/coretests/src/com/android/internal/power/EnergyConsumerStatsTest.java
@@ -18,13 +18,13 @@
 
 import static android.os.BatteryStats.POWER_DATA_UNAVAILABLE;
 
-import static com.android.internal.power.MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_CPU;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_SCREEN_OTHER;
-import static com.android.internal.power.MeasuredEnergyStats.POWER_BUCKET_WIFI;
+import static com.android.internal.power.EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_BLUETOOTH;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_CPU;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_SCREEN_DOZE;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_SCREEN_ON;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_SCREEN_OTHER;
+import static com.android.internal.power.EnergyConsumerStats.POWER_BUCKET_WIFI;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -43,13 +43,10 @@
 import java.util.Arrays;
 
 /**
- * Test class for {@link MeasuredEnergyStats}.
- *
- * To run the tests, use
- * atest FrameworksCoreTests:com.android.internal.power.MeasuredEnergyStatsTest
+ * Test class for {@link EnergyConsumerStats}.
  */
 @SmallTest
-public class MeasuredEnergyStatsTest {
+public class EnergyConsumerStatsTest {
 
     @Test
     public void testConstruction() {
@@ -59,11 +56,11 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_WIFI},
                         new String[]{"state0", "state1", "state3"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         for (int bucket = 0; bucket < NUMBER_STANDARD_POWER_BUCKETS; bucket++) {
             if (supportedStandardBuckets[bucket]) {
@@ -95,10 +92,10 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[]{POWER_BUCKET_SCREEN_ON}, new String[]{"s0", "s1"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         stats.setState(0, 1000);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 2000);
@@ -112,7 +109,7 @@
         stats.writeToParcel(parcel);
 
         parcel.setDataPosition(0);
-        MeasuredEnergyStats newStats = new MeasuredEnergyStats(config, parcel);
+        EnergyConsumerStats newStats = new EnergyConsumerStats(config, parcel);
 
         for (int bucket = 0; bucket < NUMBER_STANDARD_POWER_BUCKETS; bucket++) {
             assertEquals(stats.getAccumulatedStandardBucketCharge(bucket),
@@ -139,17 +136,17 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_WIFI},
                         new String[] {"state0", "state1", "state2"});
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.Config.writeToParcel(config, parcel);
+        EnergyConsumerStats.Config.writeToParcel(config, parcel);
 
         parcel.setDataPosition(0);
 
-        final MeasuredEnergyStats.Config newConfig = MeasuredEnergyStats.Config.createFromParcel(
+        final EnergyConsumerStats.Config newConfig = EnergyConsumerStats.Config.createFromParcel(
                 parcel);
 
         assertThat(newConfig).isNotNull();
@@ -172,11 +169,11 @@
     @Test
     public void testCreateAndReadConfigFromParcel_nullConfig() {
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.Config.writeToParcel(null, parcel);
+        EnergyConsumerStats.Config.writeToParcel(null, parcel);
 
         parcel.setDataPosition(0);
 
-        final MeasuredEnergyStats.Config newConfig = MeasuredEnergyStats.Config.createFromParcel(
+        final EnergyConsumerStats.Config newConfig = EnergyConsumerStats.Config.createFromParcel(
                 parcel);
 
         assertThat(newConfig).isNull();
@@ -190,10 +187,10 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
@@ -202,12 +199,12 @@
         stats.updateCustomBucket(1, 60);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
+        EnergyConsumerStats.writeSummaryToParcel(stats, parcel);
 
         parcel.setDataPosition(0);
 
-        MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(config, parcel);
+        EnergyConsumerStats newStats =
+                EnergyConsumerStats.createAndReadSummaryFromParcel(config, parcel);
 
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
             assertEquals(stats.isStandardBucketSupported(i),
@@ -232,17 +229,17 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
         stats.updateCustomBucket(0, 50);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
+        EnergyConsumerStats.writeSummaryToParcel(stats, parcel);
         parcel.setDataPosition(0);
 
         final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
@@ -250,12 +247,12 @@
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = true; // switched false > true
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = false; // switched true > false
 
-        final MeasuredEnergyStats.Config newConfig =
-                new MeasuredEnergyStats.Config(newSupportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config newConfig =
+                new EnergyConsumerStats.Config(newSupportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
 
-        final MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(newConfig, parcel);
+        final EnergyConsumerStats newStats =
+                EnergyConsumerStats.createAndReadSummaryFromParcel(newConfig, parcel);
 
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
             if (!newSupportedStandardBuckets[i]) {
@@ -288,10 +285,10 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5);
@@ -300,11 +297,11 @@
         stats.updateCustomBucket(1, 60);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
+        EnergyConsumerStats.writeSummaryToParcel(stats, parcel);
         parcel.setDataPosition(0);
 
-        MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(null, parcel);
+        EnergyConsumerStats newStats =
+                EnergyConsumerStats.createAndReadSummaryFromParcel(null, parcel);
         assertNull(newStats);
         parcel.recycle();
     }
@@ -317,29 +314,29 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 0);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_OTHER, 40);
 
         final Parcel parcel = Parcel.obtain();
-        MeasuredEnergyStats.writeSummaryToParcel(stats, parcel);
+        EnergyConsumerStats.writeSummaryToParcel(stats, parcel);
 
         final boolean[] newSupportedStandardBuckets = new boolean[NUMBER_STANDARD_POWER_BUCKETS];
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_ON] = true;
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = true; // switched false > true
         newSupportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = false; // switched true > false
-        final MeasuredEnergyStats.Config newConfig =
-                new MeasuredEnergyStats.Config(newSupportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config newConfig =
+                new EnergyConsumerStats.Config(newSupportedStandardBuckets, customBucketNames,
                         new int[0], new String[]{"s"});
 
         parcel.setDataPosition(0);
 
-        final MeasuredEnergyStats newStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(newConfig, parcel);
+        final EnergyConsumerStats newStats =
+                EnergyConsumerStats.createAndReadSummaryFromParcel(newConfig, parcel);
 
         // The only non-0 entry in stats is no longer supported, so now there's no interesting data.
         assertNull(newStats);
@@ -355,11 +352,11 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_DOZE] = false;
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         new int[]{POWER_BUCKET_SCREEN_ON, POWER_BUCKET_SCREEN_OTHER},
                         new String[]{"s0", "s1"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         stats.setState(0, 1000);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 2000);
@@ -391,11 +388,11 @@
 
     @Test
     public void testIsValidCustomBucket() {
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                         new String[]{"A", "B", "C"},
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
         assertFalse(stats.isValidCustomBucket(-1));
         assertTrue(stats.isValidCustomBucket(0));
         assertTrue(stats.isValidCustomBucket(1));
@@ -403,10 +400,10 @@
         assertFalse(stats.isValidCustomBucket(3));
         assertFalse(stats.isValidCustomBucket(4));
 
-        final MeasuredEnergyStats.Config boringConfig =
-                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+        final EnergyConsumerStats.Config boringConfig =
+                new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                         new String[0], new int[0], new String[]{"s"});
-        final MeasuredEnergyStats boringStats = new MeasuredEnergyStats(boringConfig);
+        final EnergyConsumerStats boringStats = new EnergyConsumerStats(boringConfig);
         assertFalse(boringStats.isValidCustomBucket(-1));
         assertFalse(boringStats.isValidCustomBucket(0));
         assertFalse(boringStats.isValidCustomBucket(1));
@@ -414,11 +411,11 @@
 
     @Test
     public void testGetAccumulatedCustomBucketCharges() {
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                         new String[]{"A", "B", "C"},
                         new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
         stats.updateCustomBucket(0, 50);
         stats.updateCustomBucket(1, 60);
         stats.updateCustomBucket(2, 13);
@@ -434,10 +431,10 @@
 
     @Test
     public void testGetAccumulatedCustomBucketCharges_empty() {
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                         new String[0], new int[0], new String[]{"s"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
 
         final long[] output = stats.getAccumulatedCustomBucketCharges();
         assertEquals(0, output.length);
@@ -446,13 +443,13 @@
     @Test
     public void testGetNumberCustomChargeBuckets() {
         assertEquals(0,
-                new MeasuredEnergyStats(
-                        new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                new EnergyConsumerStats(
+                        new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                                 new String[0], new int[0], new String[]{"s"}))
                         .getNumberCustomPowerBuckets());
         assertEquals(3,
-                new MeasuredEnergyStats(
-                        new MeasuredEnergyStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
+                new EnergyConsumerStats(
+                        new EnergyConsumerStats.Config(new boolean[NUMBER_STANDARD_POWER_BUCKETS],
                                 new String[]{"A", "B", "C"}, new int[0], new String[]{"s"}))
                         .getNumberCustomPowerBuckets());
     }
@@ -466,10 +463,10 @@
         supportedStandardBuckets[POWER_BUCKET_SCREEN_OTHER] = true;
 
         final int[] supportedMultiStateBuckets = new int[]{POWER_BUCKET_SCREEN_ON};
-        final MeasuredEnergyStats.Config config =
-                new MeasuredEnergyStats.Config(supportedStandardBuckets, customBucketNames,
+        final EnergyConsumerStats.Config config =
+                new EnergyConsumerStats.Config(supportedStandardBuckets, customBucketNames,
                         supportedMultiStateBuckets, new String[]{"s1", "s2"});
-        final MeasuredEnergyStats stats = new MeasuredEnergyStats(config);
+        final EnergyConsumerStats stats = new EnergyConsumerStats(config);
         stats.setState(1, 0);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 10, 1000);
         stats.updateStandardBucket(POWER_BUCKET_SCREEN_ON, 5, 2000);
@@ -482,7 +479,7 @@
         assertThat(stats.getAccumulatedStandardBucketCharge(POWER_BUCKET_SCREEN_ON, 1))
                 .isEqualTo(15);
 
-        MeasuredEnergyStats.resetIfNotNull(stats);
+        EnergyConsumerStats.resetIfNotNull(stats);
         // All charges should be reset to 0
         for (int i = 0; i < NUMBER_STANDARD_POWER_BUCKETS; i++) {
             if (supportedStandardBuckets[i]) {
@@ -517,13 +514,13 @@
         int exp;
 
         exp = POWER_BUCKET_SCREEN_ON;
-        assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_ON));
-        assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_VR));
-        assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_ON_SUSPEND));
+        assertEquals(exp, EnergyConsumerStats.getDisplayPowerBucket(Display.STATE_ON));
+        assertEquals(exp, EnergyConsumerStats.getDisplayPowerBucket(Display.STATE_VR));
+        assertEquals(exp, EnergyConsumerStats.getDisplayPowerBucket(Display.STATE_ON_SUSPEND));
 
         exp = POWER_BUCKET_SCREEN_DOZE;
-        assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_DOZE));
-        assertEquals(exp, MeasuredEnergyStats.getDisplayPowerBucket(Display.STATE_DOZE_SUSPEND));
+        assertEquals(exp, EnergyConsumerStats.getDisplayPowerBucket(Display.STATE_DOZE));
+        assertEquals(exp, EnergyConsumerStats.getDisplayPowerBucket(Display.STATE_DOZE_SUSPEND));
     }
 
     @Test
@@ -534,7 +531,7 @@
         final int[] supportedMultiStateBuckets = {POWER_BUCKET_CPU, POWER_BUCKET_WIFI};
         final String[] stateNames = {"s"};
 
-        final MeasuredEnergyStats.Config config = new MeasuredEnergyStats.Config(
+        final EnergyConsumerStats.Config config = new EnergyConsumerStats.Config(
                 supportedStandardBuckets,
                 customBucketNames,
                 supportedMultiStateBuckets,
@@ -542,7 +539,7 @@
         assertTrue(
                 "All standard and custom bucket supports match",
                 config.isCompatible(
-                        new MeasuredEnergyStats.Config(
+                        new EnergyConsumerStats.Config(
                                 supportedStandardBuckets,
                                 customBucketNames,
                                 supportedMultiStateBuckets,
@@ -554,7 +551,7 @@
         assertFalse(
                 "Standard bucket support mismatch",
                 config.isCompatible(
-                        new MeasuredEnergyStats.Config(
+                        new EnergyConsumerStats.Config(
                                 differentSupportedStandardBuckets,
                                 customBucketNames,
                                 supportedMultiStateBuckets,
@@ -562,7 +559,7 @@
         assertFalse(
                 "Custom bucket support mismatch",
                 config.isCompatible(
-                        new MeasuredEnergyStats.Config(
+                        new EnergyConsumerStats.Config(
                                 supportedStandardBuckets,
                                 new String[]{"C", "B"},
                                 supportedMultiStateBuckets,
@@ -570,7 +567,7 @@
         assertFalse(
                 "Multi-state bucket mismatch",
                 config.isCompatible(
-                        new MeasuredEnergyStats.Config(
+                        new EnergyConsumerStats.Config(
                                 supportedStandardBuckets,
                                 new String[]{"A"},
                                 new int[] {POWER_BUCKET_CPU, POWER_BUCKET_BLUETOOTH},
@@ -578,7 +575,7 @@
         assertFalse(
                 "Multi-state bucket state list mismatch",
                 config.isCompatible(
-                        new MeasuredEnergyStats.Config(
+                        new EnergyConsumerStats.Config(
                                 supportedStandardBuckets,
                                 new String[]{"A"},
                                 supportedMultiStateBuckets,
diff --git a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
index d8dc1ea..64df348 100755
--- a/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
+++ b/core/tests/hdmitests/src/android/hardware/hdmi/HdmiPortInfoTest.java
@@ -37,26 +37,38 @@
         boolean isCec = true;
         boolean isMhl = false;
         boolean isArcSupported = false;
+        boolean isEarcSupported = false;
 
         new EqualsTester()
                 .addEqualityGroup(
-                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported),
-                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported))
+                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
+                                isEarcSupported),
+                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
                         new HdmiPortInfo(
-                                portId + 1, portType, address, isCec, isMhl, isArcSupported))
+                                portId + 1, portType, address, isCec, isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
                         new HdmiPortInfo(
-                                portId, portType + 1, address, isCec, isMhl, isArcSupported))
+                                portId, portType + 1, address, isCec, isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
                         new HdmiPortInfo(
-                                portId, portType, address + 1, isCec, isMhl, isArcSupported))
+                                portId, portType, address + 1, isCec, isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
-                        new HdmiPortInfo(portId, portType, address, !isCec, isMhl, isArcSupported))
+                        new HdmiPortInfo(portId, portType, address, !isCec, isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
-                        new HdmiPortInfo(portId, portType, address, isCec, !isMhl, isArcSupported))
+                        new HdmiPortInfo(portId, portType, address, isCec, !isMhl, isArcSupported,
+                                isEarcSupported))
                 .addEqualityGroup(
-                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, !isArcSupported))
+                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, !isArcSupported,
+                                isEarcSupported))
+                .addEqualityGroup(
+                        new HdmiPortInfo(portId, portType, address, isCec, isMhl, isArcSupported,
+                                !isEarcSupported))
                 .testEquals();
     }
 }
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 88b2de7..3e75c7d 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -347,7 +347,7 @@
             doNothing().when(packageInfo).updateApplicationInfo(any(), any());
 
             return new ActivityClientRecord(mock(IBinder.class), Intent.makeMainActivity(component),
-                    0 /* ident */, info, new Configuration(), null /* referrer */,
+                    0 /* ident */, info, new Configuration(), 0 /*deviceId */, null /* referrer */,
                     null /* voiceInteractor */, null /* state */, null /* persistentState */,
                     null /* pendingResults */, null /* pendingNewIntents */,
                     null /* activityOptions */, true /* isForward */, null /* profilerInfo */,
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 278b958..bc3af1d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -313,6 +313,12 @@
       "group": "WM_DEBUG_IME",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-1812743677": {
+      "message": "Display id=%d is ignoring all orientation requests, camera is active and the top activity is eligible for force rotation, return %s,portrait activity: %b, is natural orientation portrait: %b.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-1810446914": {
       "message": "Trying to update display configuration for system\/invalid process.",
       "level": "WARN",
@@ -493,6 +499,12 @@
       "group": "WM_DEBUG_ORIENTATION",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
+    "-1631991057": {
+      "message": "Display id=%d is notified that Camera %s is closed but activity is still refreshing. Rescheduling an update.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-1630752478": {
       "message": "removeLockedTask: removed %s",
       "level": "DEBUG",
@@ -637,6 +649,12 @@
       "group": "WM_DEBUG_WINDOW_INSETS",
       "at": "com\/android\/server\/wm\/InsetsSourceProvider.java"
     },
+    "-1480918485": {
+      "message": "Refreshed activity: %s",
+      "level": "INFO",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/ActivityRecord.java"
+    },
     "-1480772131": {
       "message": "No app or window is requesting an orientation, return %d for display id=%d",
       "level": "VERBOSE",
@@ -1375,6 +1393,12 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-799396645": {
+      "message": "Display id=%d is notified that Camera %s is closed, updating rotation.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-799003045": {
       "message": "Set animatingExit: reason=remove\/replaceWindow win=%s",
       "level": "VERBOSE",
@@ -1585,6 +1609,12 @@
       "group": "WM_DEBUG_FOCUS_LIGHT",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-637815408": {
+      "message": "Invalid surface rotation angle in config_deviceTabletopRotations: %d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
     "-636553602": {
       "message": "commitVisibility: %s: visible=%b visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
       "level": "VERBOSE",
@@ -1597,6 +1627,12 @@
       "group": "WM_DEBUG_SCREEN_ON",
       "at": "com\/android\/server\/wm\/DisplayContent.java"
     },
+    "-627759820": {
+      "message": "Display id=%d is notified that Camera %s is open for package %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-622997754": {
       "message": "postWindowRemoveCleanupLocked: %s",
       "level": "VERBOSE",
@@ -1627,12 +1663,6 @@
       "group": "WM_ERROR",
       "at": "com\/android\/server\/wm\/WindowManagerService.java"
     },
-    "-576580969": {
-      "message": "viewServerWindowCommand: bootFinished() failed.",
-      "level": "WARN",
-      "group": "WM_ERROR",
-      "at": "com\/android\/server\/wm\/WindowManagerService.java"
-    },
     "-576070986": {
       "message": "Performing post-rotate rotation after seamless rotation",
       "level": "INFO",
@@ -1975,6 +2005,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-254406860": {
+      "message": "Unable to tell MediaProjectionManagerService about visibility change on the active projection: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-251259736": {
       "message": "No longer freezing: %s",
       "level": "VERBOSE",
@@ -2029,6 +2065,12 @@
       "group": "WM_DEBUG_ANIM",
       "at": "com\/android\/server\/wm\/SurfaceAnimator.java"
     },
+    "-206549078": {
+      "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: config_perDisplayFocusEnabled",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "-203358733": {
       "message": "commitFinishDrawingLocked: mDrawState=READY_TO_SHOW %s",
       "level": "INFO",
@@ -2149,6 +2191,12 @@
       "group": "WM_SHOW_TRANSACTIONS",
       "at": "com\/android\/server\/wm\/Session.java"
     },
+    "-81260230": {
+      "message": "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "-81121442": {
       "message": "ImeContainer just became organized but it doesn't have a parent or the parent doesn't have a surface control. mSurfaceControl=%s imeParentSurfaceControl=%s",
       "level": "ERROR",
@@ -2257,6 +2305,12 @@
       "group": "WM_DEBUG_RESIZE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "34682671": {
+      "message": "Not moving display (displayId=%d) to top. Top focused displayId=%d. Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
+      "level": "INFO",
+      "group": "WM_DEBUG_FOCUS_LIGHT",
+      "at": "com\/android\/server\/wm\/WindowManagerService.java"
+    },
     "35398067": {
       "message": "goodToGo(): onAnimationStart, transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
       "level": "DEBUG",
@@ -3235,6 +3289,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/TaskFragment.java"
     },
+    "939638078": {
+      "message": "config_deviceTabletopRotations is not defined. Half-fold letterboxing will work inconsistently.",
+      "level": "WARN",
+      "group": "WM_DEBUG_ORIENTATION",
+      "at": "com\/android\/server\/wm\/DisplayRotation.java"
+    },
     "948208142": {
       "message": "Setting Activity.mLauncherTaskBehind to true. Activity=%s",
       "level": "DEBUG",
@@ -4153,12 +4213,6 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
-    "1903353011": {
-      "message": "notifyAppStopped: %s",
-      "level": "VERBOSE",
-      "group": "WM_DEBUG_ADD_REMOVE",
-      "at": "com\/android\/server\/wm\/ActivityRecord.java"
-    },
     "1912291550": {
       "message": "Sleep still waiting to pause %s",
       "level": "VERBOSE",
@@ -4219,6 +4273,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "1967643923": {
+      "message": "Refershing activity for camera compatibility treatment, activityRecord=%s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/DisplayRotationCompatPolicy.java"
+    },
     "1967975839": {
       "message": "Changing app %s visible=%b performLayout=%b",
       "level": "VERBOSE",
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index e62ac46..2f396c0 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -668,12 +668,21 @@
     }
 
     /**
+     * Draws a mesh object to the screen.
+     *
+     * @param mesh {@link Mesh} object that will be drawn to the screen
+     * @param blendMode {@link BlendMode} used to blend mesh primitives with the Paint color/shader
+     * @param paint {@link Paint} used to provide a color/shader/blend mode.
+     *
      * @hide
      */
-    public void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+    public void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
         if (!isHardwareAccelerated() && onHwFeatureInSwMode()) {
             throw new RuntimeException("software rendering doesn't support meshes");
         }
+        if (blendMode == null) {
+            blendMode = BlendMode.MODULATE;
+        }
         nDrawMesh(this.mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
                 blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
     }
diff --git a/graphics/java/android/graphics/BaseRecordingCanvas.java b/graphics/java/android/graphics/BaseRecordingCanvas.java
index eeff694..2ec4524 100644
--- a/graphics/java/android/graphics/BaseRecordingCanvas.java
+++ b/graphics/java/android/graphics/BaseRecordingCanvas.java
@@ -607,7 +607,10 @@
     }
 
     @Override
-    public final void drawMesh(Mesh mesh, BlendMode blendMode, Paint paint) {
+    public final void drawMesh(@NonNull Mesh mesh, BlendMode blendMode, @NonNull Paint paint) {
+        if (blendMode == null) {
+            blendMode = BlendMode.MODULATE;
+        }
         nDrawMesh(mNativeCanvasWrapper, mesh.getNativeWrapperInstance(),
                 blendMode.getXfermode().porterDuffMode, paint.getNativeInstance());
     }
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index ef1e7bf..701e20c 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -161,11 +161,17 @@
          * be thrown by the decode methods when setting a non-RGB color space
          * such as {@link ColorSpace.Named#CIE_LAB Lab}.</p>
          *
-         * <p class="note">The specified color space's transfer function must be
+         * <p class="note">
+         * Prior to {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * the specified color space's transfer function must be
          * an {@link ColorSpace.Rgb.TransferParameters ICC parametric curve}. An
          * <code>IllegalArgumentException</code> will be thrown by the decode methods
          * if calling {@link ColorSpace.Rgb#getTransferParameters()} on the
-         * specified color space returns null.</p>
+         * specified color space returns null.
+         *
+         * Starting from {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+         * non ICC parametric curve transfer function is allowed.
+         * E.g., {@link ColorSpace.Named#BT2020_HLG BT2020_HLG}.</p>
          *
          * <p>After decode, the bitmap's color space is stored in
          * {@link #outColorSpace}.</p>
@@ -458,7 +464,11 @@
                     throw new IllegalArgumentException("The destination color space must use the " +
                             "RGB color model");
                 }
-                if (((ColorSpace.Rgb) opts.inPreferredColorSpace).getTransferParameters() == null) {
+                if (!opts.inPreferredColorSpace.equals(ColorSpace.get(ColorSpace.Named.BT2020_HLG))
+                        && !opts.inPreferredColorSpace.equals(
+                            ColorSpace.get(ColorSpace.Named.BT2020_PQ))
+                        && ((ColorSpace.Rgb) opts.inPreferredColorSpace)
+                            .getTransferParameters() == null) {
                     throw new IllegalArgumentException("The destination color space must use an " +
                             "ICC parametric transfer function");
                 }
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 31df474..2427dec 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@
 
     private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
     private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+    private static final float[] BT2020_PRIMARIES =
+            { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
     /**
      * A gray color space does not have meaningful primaries, so we use this arbitrary set.
      */
@@ -208,6 +210,12 @@
 
     private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
             new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
+    private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
+            new Rgb.TransferParameters(2.0f, 2.0f, 1 / 0.17883277f,
+                0.28466892f, 0.5599107f, 0.0f, -3.0f, true);
+    private static final Rgb.TransferParameters BT2020_PQ_TRANSFER_PARAMETERS =
+            new Rgb.TransferParameters(107 / 128.0f, 1.0f, 32 / 2523.0f,
+                2413 / 128.0f, -2392 / 128.0f, 8192 / 1305.0f, -2.0f, true);
 
     // See static initialization block next to #get(Named)
     private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
@@ -703,7 +711,29 @@
          *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
          * </table>
          */
-        CIE_LAB
+        CIE_LAB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+         * Hybrid Log Gamma encoding.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Hybrid Log Gamma encoding</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         */
+        BT2020_HLG,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2100 standardized as
+         * Perceptual Quantizer encoding.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Perceptual Quantizer encoding</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         */
+        BT2020_PQ
         // Update the initialization block next to #get(Named) when adding new values
     }
 
@@ -1534,7 +1564,7 @@
         sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
         sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
                 "Rec. ITU-R BT.2020-1",
-                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+                BT2020_PRIMARIES,
                 ILLUMINANT_D65,
                 null,
                 new Rgb.TransferParameters(1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145, 1 / 0.45),
@@ -1616,6 +1646,70 @@
                 "Generic L*a*b*",
                 Named.CIE_LAB.ordinal()
         );
+        sNamedColorSpaces[Named.BT2020_HLG.ordinal()] = new ColorSpace.Rgb(
+                "Hybrid Log Gamma encoding",
+                BT2020_PRIMARIES,
+                ILLUMINANT_D65,
+                null,
+                x -> transferHLGOETF(x),
+                x -> transferHLGEOTF(x),
+                0.0f, 1.0f,
+                BT2020_HLG_TRANSFER_PARAMETERS,
+                Named.BT2020_HLG.ordinal()
+        );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_HLG, Named.BT2020_HLG.ordinal());
+        sNamedColorSpaces[Named.BT2020_PQ.ordinal()] = new ColorSpace.Rgb(
+                "Perceptual Quantizer encoding",
+                BT2020_PRIMARIES,
+                ILLUMINANT_D65,
+                null,
+                x -> transferST2048OETF(x),
+                x -> transferST2048EOTF(x),
+                0.0f, 1.0f,
+                BT2020_PQ_TRANSFER_PARAMETERS,
+                Named.BT2020_PQ.ordinal()
+        );
+        sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020_PQ, Named.BT2020_PQ.ordinal());
+    }
+
+    private static double transferHLGOETF(double x) {
+        double a = 0.17883277;
+        double b = 0.28466892;
+        double c = 0.55991073;
+        double r = 0.5;
+        return x > 1.0 ? a * Math.log(x - b) + c : r * Math.sqrt(x);
+    }
+
+    private static double transferHLGEOTF(double x) {
+        double a = 0.17883277;
+        double b = 0.28466892;
+        double c = 0.55991073;
+        double r = 0.5;
+        return x <= 0.5 ? (x * x) / (r * r) : Math.exp((x - c) / a + b);
+    }
+
+    private static double transferST2048OETF(double x) {
+        double m1 = (2610.0 / 4096.0) / 4.0;
+        double m2 = (2523.0 / 4096.0) * 128.0;
+        double c1 = (3424.0 / 4096.0);
+        double c2 = (2413.0 / 4096.0) * 32.0;
+        double c3 = (2392.0 / 4096.0) * 32.0;
+
+        double tmp = Math.pow(x, m1);
+        tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
+        return Math.pow(tmp, m2);
+    }
+
+    private static double transferST2048EOTF(double x) {
+        double m1 = (2610.0 / 4096.0) / 4.0;
+        double m2 = (2523.0 / 4096.0) * 128.0;
+        double c1 = (3424.0 / 4096.0);
+        double c2 = (2413.0 / 4096.0) * 32.0;
+        double c3 = (2392.0 / 4096.0) * 32.0;
+
+        double tmp = Math.pow(Math.min(Math.max(x, 0.0), 1.0), 1.0 / m2);
+        tmp = Math.max(tmp - c1, 0.0) / (c2 - c3 * tmp);
+        return Math.pow(tmp, 1.0 / m1);
     }
 
     // Reciprocal piecewise gamma response
@@ -2197,6 +2291,58 @@
             /** Variable \(g\) in the equation of the EOTF described above. */
             public final double g;
 
+            private TransferParameters(double a, double b, double c, double d, double e,
+                    double f, double g, boolean nonCurveTransferParameters) {
+                // nonCurveTransferParameters correspondes to a "special" transfer function
+                if (!nonCurveTransferParameters) {
+                    if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c)
+                            || Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f)
+                            || Double.isNaN(g)) {
+                        throw new IllegalArgumentException("Parameters cannot be NaN");
+                    }
+
+                    // Next representable float after 1.0
+                    // We use doubles here but the representation inside our native code
+                    // is often floats
+                    if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
+                        throw new IllegalArgumentException(
+                            "Parameter d must be in the range [0..1], " + "was " + d);
+                    }
+
+                    if (d == 0.0 && (a == 0.0 || g == 0.0)) {
+                        throw new IllegalArgumentException(
+                            "Parameter a or g is zero, the transfer function is constant");
+                    }
+
+                    if (d >= 1.0 && c == 0.0) {
+                        throw new IllegalArgumentException(
+                            "Parameter c is zero, the transfer function is constant");
+                    }
+
+                    if ((a == 0.0 || g == 0.0) && c == 0.0) {
+                        throw new IllegalArgumentException("Parameter a or g is zero,"
+                            + " and c is zero, the transfer function is constant");
+                    }
+
+                    if (c < 0.0) {
+                        throw new IllegalArgumentException(
+                            "The transfer function must be increasing");
+                    }
+
+                    if (a < 0.0 || g < 0.0) {
+                        throw new IllegalArgumentException(
+                            "The transfer function must be positive or increasing");
+                    }
+                }
+                this.a = a;
+                this.b = b;
+                this.c = c;
+                this.d = d;
+                this.e = e;
+                this.f = f;
+                this.g = g;
+            }
+
             /**
              * <p>Defines the parameters for the ICC parametric curve type 3, as
              * defined in ICC.1:2004-10, section 10.15.</p>
@@ -2219,7 +2365,7 @@
              * @throws IllegalArgumentException If the parameters form an invalid transfer function
              */
             public TransferParameters(double a, double b, double c, double d, double g) {
-                this(a, b, c, d, 0.0, 0.0, g);
+                this(a, b, c, d, 0.0, 0.0, g, false);
             }
 
             /**
@@ -2238,51 +2384,7 @@
              */
             public TransferParameters(double a, double b, double c, double d, double e,
                     double f, double g) {
-
-                if (Double.isNaN(a) || Double.isNaN(b) || Double.isNaN(c) ||
-                        Double.isNaN(d) || Double.isNaN(e) || Double.isNaN(f) ||
-                        Double.isNaN(g)) {
-                    throw new IllegalArgumentException("Parameters cannot be NaN");
-                }
-
-                // Next representable float after 1.0
-                // We use doubles here but the representation inside our native code is often floats
-                if (!(d >= 0.0 && d <= 1.0f + Math.ulp(1.0f))) {
-                    throw new IllegalArgumentException("Parameter d must be in the range [0..1], " +
-                            "was " + d);
-                }
-
-                if (d == 0.0 && (a == 0.0 || g == 0.0)) {
-                    throw new IllegalArgumentException(
-                            "Parameter a or g is zero, the transfer function is constant");
-                }
-
-                if (d >= 1.0 && c == 0.0) {
-                    throw new IllegalArgumentException(
-                            "Parameter c is zero, the transfer function is constant");
-                }
-
-                if ((a == 0.0 || g == 0.0) && c == 0.0) {
-                    throw new IllegalArgumentException("Parameter a or g is zero," +
-                            " and c is zero, the transfer function is constant");
-                }
-
-                if (c < 0.0) {
-                    throw new IllegalArgumentException("The transfer function must be increasing");
-                }
-
-                if (a < 0.0 || g < 0.0) {
-                    throw new IllegalArgumentException("The transfer function must be " +
-                            "positive or increasing");
-                }
-
-                this.a = a;
-                this.b = b;
-                this.c = c;
-                this.d = d;
-                this.e = e;
-                this.f = f;
-                this.g = g;
+                this(a, b, c, d, e, f, g, false);
             }
 
             @SuppressWarnings("SimplifiableIfStatement")
@@ -2357,6 +2459,36 @@
         private static native long nativeCreate(float a, float b, float c, float d,
                 float e, float f, float g, float[] xyz);
 
+        private static DoubleUnaryOperator generateOETF(TransferParameters function) {
+            boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+                    || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+            if (isNonCurveTransferParameters) {
+                return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGOETF(x)
+                    : x -> transferST2048OETF(x);
+            } else {
+                return function.e == 0.0 && function.f == 0.0
+                    ? x -> rcpResponse(x, function.a, function.b,
+                    function.c, function.d, function.g)
+                    : x -> rcpResponse(x, function.a, function.b, function.c,
+                        function.d, function.e, function.f, function.g);
+            }
+        }
+
+        private static DoubleUnaryOperator generateEOTF(TransferParameters function) {
+            boolean isNonCurveTransferParameters = function.equals(BT2020_HLG_TRANSFER_PARAMETERS)
+                    || function.equals(BT2020_PQ_TRANSFER_PARAMETERS);
+            if (isNonCurveTransferParameters) {
+                return function.f == 0.0 && function.g < 0.0 ? x -> transferHLGEOTF(x)
+                    : x -> transferST2048EOTF(x);
+            } else {
+                return function.e == 0.0 && function.f == 0.0
+                    ? x -> response(x, function.a, function.b,
+                    function.c, function.d, function.g)
+                    : x -> response(x, function.a, function.b, function.c,
+                        function.d, function.e, function.f, function.g);
+            }
+        }
+
         /**
          * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
          * The transform matrix must convert from the RGB space to the profile connection
@@ -2553,16 +2685,8 @@
                 @NonNull TransferParameters function,
                 @IntRange(from = MIN_ID, to = MAX_ID) int id) {
             this(name, primaries, whitePoint, transform,
-                    function.e == 0.0 && function.f == 0.0 ?
-                            x -> rcpResponse(x, function.a, function.b,
-                                    function.c, function.d, function.g) :
-                            x -> rcpResponse(x, function.a, function.b, function.c,
-                                    function.d, function.e, function.f, function.g),
-                    function.e == 0.0 && function.f == 0.0 ?
-                            x -> response(x, function.a, function.b,
-                                    function.c, function.d, function.g) :
-                            x -> response(x, function.a, function.b, function.c,
-                                    function.d, function.e, function.f, function.g),
+                    generateOETF(function),
+                    generateEOTF(function),
                     0.0f, 1.0f, function, id);
         }
 
@@ -3063,7 +3187,12 @@
          */
         @Nullable
         public TransferParameters getTransferParameters() {
-            return mTransferParameters;
+            if (mTransferParameters != null
+                    && !mTransferParameters.equals(BT2020_PQ_TRANSFER_PARAMETERS)
+                    && !mTransferParameters.equals(BT2020_HLG_TRANSFER_PARAMETERS)) {
+                return mTransferParameters;
+            }
+            return null;
         }
 
         @Override
diff --git a/graphics/java/android/graphics/Mesh.java b/graphics/java/android/graphics/Mesh.java
index f32e0ee..e186386 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -16,6 +16,9 @@
 
 package android.graphics;
 
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
 import libcore.util.NativeAllocationRegistry;
 
 import java.nio.Buffer;
@@ -25,11 +28,12 @@
  * Class representing a mesh object.
  *
  * This class generates Mesh objects via the
- * {@link #make(MeshSpecification, Mode, Buffer, int, Rect)} and
- * {@link #makeIndexed(MeshSpecification, Mode, Buffer, int, ShortBuffer, Rect)} methods,
+ * {@link #make(MeshSpecification, int, Buffer, int, Rect)} and
+ * {@link #makeIndexed(MeshSpecification, int, Buffer, int, ShortBuffer, Rect)} methods,
  * where a {@link MeshSpecification} is required along with various attributes for
  * detailing the mesh object, including a mode, vertex buffer, optional index buffer, and bounds
- * for the mesh.
+ * for the mesh. Once generated, a mesh object can be drawn through
+ * {@link Canvas#drawMesh(Mesh, BlendMode, Paint)}
  *
  * @hide
  */
@@ -38,9 +42,20 @@
     private boolean mIsIndexed;
 
     /**
-     * Enum to determine how the mesh is represented.
+     * Determines how the mesh is represented and will be drawn.
      */
-    public enum Mode {Triangles, TriangleStrip}
+    @IntDef({TRIANGLES, TRIANGLE_STRIP})
+    private @interface Mode {}
+
+    /**
+     * The mesh will be drawn with triangles without utilizing shared vertices.
+     */
+    public static final int TRIANGLES = 0;
+
+    /**
+     * The mesh will be drawn with triangles utilizing shared vertices.
+     */
+    public static final int TRIANGLE_STRIP = 1;
 
     private static class MeshHolder {
         public static final NativeAllocationRegistry MESH_SPECIFICATION_REGISTRY =
@@ -52,15 +67,24 @@
      * Generates a {@link Mesh} object.
      *
      * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
-     * @param mode         {@link Mode} enum
-     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
-     * @param vertexCount  the number of vertices represented in the vertexBuffer.
+     * @param mode         Determines what mode to draw the mesh in. Must be one of
+     *                     {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+     *                     for all attributes provided within the meshSpec for every vertex. That
+     *                     is, a vertex buffer should be (attributes size * number of vertices) in
+     *                     length to be valid. Note that currently implementation will have a CPU
+     *                     backed buffer generated.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer and mesh.
      * @param bounds       bounds of the mesh object.
      * @return a new Mesh object.
      */
-    public static Mesh make(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
-            int vertexCount, Rect bounds) {
-        long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+    @NonNull
+    public static Mesh make(@NonNull MeshSpecification meshSpec, @Mode int mode,
+            @NonNull Buffer vertexBuffer, int vertexCount, @NonNull Rect bounds) {
+        if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+            throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+        }
+        long nativeMesh = nativeMake(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
                 vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), bounds.left,
                 bounds.top, bounds.right, bounds.bottom);
         if (nativeMesh == 0) {
@@ -70,19 +94,32 @@
     }
 
     /**
-     * Generates an indexed {@link Mesh} object.
+     * Generates a {@link Mesh} object.
      *
      * @param meshSpec     {@link MeshSpecification} used when generating the mesh.
-     * @param mode         {@link Mode} enum
-     * @param vertexBuffer vertex buffer representing through {@link Buffer}.
-     * @param vertexCount  the number of vertices represented in the vertexBuffer.
-     * @param indexBuffer  index buffer representing through {@link ShortBuffer}.
+     * @param mode         Determines what mode to draw the mesh in. Must be one of
+     *                     {@link Mesh#TRIANGLES} or {@link Mesh#TRIANGLE_STRIP}
+     * @param vertexBuffer vertex buffer representing through {@link Buffer}. This provides the data
+     *                     for all attributes provided within the meshSpec for every vertex. That
+     *                     is, a vertex buffer should be (attributes size * number of vertices) in
+     *                     length to be valid. Note that currently implementation will have a CPU
+     *                     backed buffer generated.
+     * @param vertexCount  the number of vertices represented in the vertexBuffer and mesh.
+     * @param indexBuffer  index buffer representing through {@link ShortBuffer}. Indices are
+     *                     required to be 16 bits, so ShortBuffer is necessary. Note that
+     *                     currently implementation will have a CPU
+     *                     backed buffer generated.
      * @param bounds       bounds of the mesh object.
      * @return a new Mesh object.
      */
-    public static Mesh makeIndexed(MeshSpecification meshSpec, Mode mode, Buffer vertexBuffer,
-            int vertexCount, ShortBuffer indexBuffer, Rect bounds) {
-        long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode.ordinal(), vertexBuffer,
+    @NonNull
+    public static Mesh makeIndexed(@NonNull MeshSpecification meshSpec, @Mode int mode,
+            @NonNull Buffer vertexBuffer, int vertexCount, @NonNull ShortBuffer indexBuffer,
+            @NonNull Rect bounds) {
+        if (mode != TRIANGLES && mode != TRIANGLE_STRIP) {
+            throw new IllegalArgumentException("Invalid value passed in for mode parameter");
+        }
+        long nativeMesh = nativeMakeIndexed(meshSpec.mNativeMeshSpec, mode, vertexBuffer,
                 vertexBuffer.isDirect(), vertexCount, vertexBuffer.position(), indexBuffer,
                 indexBuffer.isDirect(), indexBuffer.capacity(), indexBuffer.position(), bounds.left,
                 bounds.top, bounds.right, bounds.bottom);
@@ -93,36 +130,45 @@
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+     * does not have a uniform with that name or if the uniform is declared with a type other than
+     * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+     * thrown.
      *
      * @param uniformName name matching the color uniform declared in the shader program.
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and be available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, int color) {
+    public void setColorUniform(@NonNull String uniformName, int color) {
         setUniform(uniformName, Color.valueOf(color).getComponents(), true);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+     * does not have a uniform with that name or if the uniform is declared with a type other than
+     * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+     * thrown.
      *
      * @param uniformName name matching the color uniform declared in the shader program.
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and be available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, long color) {
+    public void setColorUniform(@NonNull String uniformName, long color) {
         Color exSRGB = Color.valueOf(color).convert(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
         setUniform(uniformName, exSRGB.getComponents(), true);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform color value corresponding to the shader assigned to the mesh. If the shader
+     * does not have a uniform with that name or if the uniform is declared with a type other than
+     * vec3 or vec4 and corresponding layout(color) annotation then an IllegalArgumentExcepton is
+     * thrown.
      *
      * @param uniformName name matching the color uniform declared in the shader program.
      * @param color       the provided sRGB color will be converted into the shader program's output
      *                    colorspace and will be made available as a vec4 uniform in the program.
      */
-    public void setColorUniform(String uniformName, Color color) {
+    public void setColorUniform(@NonNull String uniformName, @NonNull Color color) {
         if (color == null) {
             throw new NullPointerException("The color parameter must not be null");
         }
@@ -132,28 +178,34 @@
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than a
+     * float or float[1] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param value       float value corresponding to the float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float value) {
+    public void setFloatUniform(@NonNull String uniformName, float value) {
         setFloatUniform(uniformName, value, 0.0f, 0.0f, 0.0f, 1);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than a
+     * vec2 or float[2] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param value1      first float value corresponding to the float uniform with the given name.
      * @param value2      second float value corresponding to the float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float value1, float value2) {
+    public void setFloatUniform(@NonNull String uniformName, float value1, float value2) {
         setFloatUniform(uniformName, value1, value2, 0.0f, 0.0f, 2);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than a
+     * vec3 or float[3] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param value1      first float value corresponding to the float uniform with the given name.
@@ -161,12 +213,15 @@
      * @param value3      third float value corresponding to the float unifiform with the given
      *                    name.
      */
-    public void setFloatUniform(String uniformName, float value1, float value2, float value3) {
+    public void setFloatUniform(
+            @NonNull String uniformName, float value1, float value2, float value3) {
         setFloatUniform(uniformName, value1, value2, value3, 0.0f, 3);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than a
+     * vec4 or float[4] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param value1      first float value corresponding to the float uniform with the given name.
@@ -175,17 +230,20 @@
      * @param value4      fourth float value corresponding to the float uniform with the given name.
      */
     public void setFloatUniform(
-            String uniformName, float value1, float value2, float value3, float value4) {
+            @NonNull String uniformName, float value1, float value2, float value3, float value4) {
         setFloatUniform(uniformName, value1, value2, value3, value4, 4);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than a
+     * float (for N=1), vecN, or float[N], where N is the length of the values param, then an
+     * IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the float uniform declared in the shader program.
      * @param values      float value corresponding to the vec4 float uniform with the given name.
      */
-    public void setFloatUniform(String uniformName, float[] values) {
+    public void setFloatUniform(@NonNull String uniformName, @NonNull float[] values) {
         setUniform(uniformName, values, false);
     }
 
@@ -210,40 +268,48 @@
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than int
+     * or int[1] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param value       value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value) {
+    public void setIntUniform(@NonNull String uniformName, int value) {
         setIntUniform(uniformName, value, 0, 0, 0, 1);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than ivec2
+     * or int[2] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2) {
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2) {
         setIntUniform(uniformName, value1, value2, 0, 0, 2);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than ivec3
+     * or int[3] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
      * @param value2      second value corresponding to the int uniform with the given name.
      * @param value3      third value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2, int value3) {
+    public void setIntUniform(@NonNull String uniformName, int value1, int value2, int value3) {
         setIntUniform(uniformName, value1, value2, value3, 0, 3);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than ivec4
+     * or int[4] then an IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param value1      first value corresponding to the int uniform with the given name.
@@ -251,17 +317,21 @@
      * @param value3      third value corresponding to the int uniform with the given name.
      * @param value4      fourth value corresponding to the int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int value1, int value2, int value3, int value4) {
+    public void setIntUniform(
+            @NonNull String uniformName, int value1, int value2, int value3, int value4) {
         setIntUniform(uniformName, value1, value2, value3, value4, 4);
     }
 
     /**
-     * Sets the uniform color value corresponding to the shader assigned to the mesh.
+     * Sets the uniform value corresponding to the shader assigned to the mesh. If the shader does
+     * not have a uniform with that name or if the uniform is declared with a type other than an
+     * int (for N=1), ivecN, or int[N], where N is the length of the values param, then an
+     * IllegalArgumentException is thrown.
      *
      * @param uniformName name matching the int uniform delcared in the shader program.
      * @param values      int values corresponding to the vec4 int uniform with the given name.
      */
-    public void setIntUniform(String uniformName, int[] values) {
+    public void setIntUniform(@NonNull String uniformName, @NonNull int[] values) {
         if (uniformName == null) {
             throw new NullPointerException("The uniformName parameter must not be null");
         }
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 45c13af..6ef3596 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -17,9 +17,12 @@
 package android.graphics;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.util.List;
+
 /**
  * Class responsible for holding specifications for {@link Mesh} creations. This class
  * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
@@ -42,34 +45,73 @@
     long mNativeMeshSpec;
 
     /**
-     * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
-     * to determine alpha type
+     * Constants for {@link #make(List, int, List, String, String)}
+     * to determine alpha type. Describes how to interpret the alpha component of a pixel.
      */
     @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
-    public @interface AlphaType {
-    }
+    private @interface AlphaType {}
 
+    /**
+     * uninitialized.
+     */
     public static final int UNKNOWN = 0;
+
+    /**
+     * Pixel is opaque.
+     */
     public static final int OPAQUE = 1;
+
+    /**
+     * Pixel components are premultiplied by alpha.
+     */
     public static final int PREMUL = 2;
+
+    /**
+     * Pixel components are independent of alpha.
+     */
     public static final int UNPREMULT = 3;
 
     /**
      * Constants for {@link Attribute} and {@link Varying} for determining the data type.
      */
     @IntDef({FLOAT, FLOAT2, FLOAT3, FLOAT4, UBYTE4})
-    public @interface Type {
-    }
+    private @interface Type {}
 
+    /**
+     * Represents one float. Its equivalent shader type is float.
+     */
     public static final int FLOAT = 0;
+
+    /**
+     * Represents two floats. Its equivalent shader type is float2.
+     */
     public static final int FLOAT2 = 1;
+
+    /**
+     * Represents three floats. Its equivalent shader type is float3.
+     */
     public static final int FLOAT3 = 2;
+
+    /**
+     * Represents four floats. Its equivalent shader type is float4.
+     */
     public static final int FLOAT4 = 3;
+
+    /**
+     * Represents four bytes. Its equivalent shader type is half4.
+     */
     public static final int UBYTE4 = 4;
 
     /**
      * Data class to represent a single attribute in a shader. Note that type parameter must be
      * one of {@link #FLOAT}, {@link #FLOAT2}, {@link #FLOAT3}, {@link #FLOAT4}, or {@link #UBYTE4}.
+     *
+     * Note that offset is the offset in number of bytes. For example, if we had two attributes
+     *
+     * Float3 att1
+     * Float att2
+     *
+     * att1 would have an offset of 0, while att2 would have an offset of 12 bytes.
      */
     public static class Attribute {
         @Type
@@ -77,7 +119,7 @@
         private int mOffset;
         private String mName;
 
-        public Attribute(@Type int type, int offset, String name) {
+        public Attribute(@Type int type, int offset, @NonNull String name) {
             mType = type;
             mOffset = offset;
             mName = name;
@@ -93,7 +135,7 @@
         private int mType;
         private String mName;
 
-        public Varying(@Type int type, String name) {
+        public Varying(@Type int type, @NonNull String name) {
             mType = type;
             mName = name;
         }
@@ -106,20 +148,28 @@
     }
 
     /**
-     * Creates a {@link MeshSpecification} object.
+     * Creates a {@link MeshSpecification} object for use within {@link Mesh}.
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
      *                       8.
-     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param vertexStride   length of vertex stride in bytes. This should be the size of a single
+     *                       vertex' attributes. Max of 1024 is accepted.
      * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
-     * @param vertexShader   vertex shader to be supplied to the mesh.
-     * @param fragmentShader fragment shader to be suppied to the mesh.
+     *                       Note that `position` is provided by default, does not need to be
+     *                       provided in the list, and does not count towards
+     *                       the 6 varyings allowed.
+     * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
+     *                       varying is set within the shader to get proper results.
+     * @param fragmentShader fragment shader to be supplied to the mesh.
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader) {
-        long nativeMeshSpec =
-                nativeMake(attributes, vertexStride, varyings, vertexShader, fragmentShader);
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader) {
+        long nativeMeshSpec = nativeMake(attributes.toArray(new Attribute[attributes.size()]),
+                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+                fragmentShader);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
         }
@@ -131,17 +181,24 @@
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
      *                       8.
-     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
-     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of
-     *                       6.
-     * @param vertexShader   vertex shader to be supplied to the mesh.
+     * @param vertexStride   length of vertex stride in bytes. This should be the size of a single
+     *                       vertex' attributes. Max of 1024 is accepted.
+     * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
+     *                       Note that `position` is provided by default, does not need to be
+     *                       provided in the list, and does not count towards
+     *                       the 6 varyings allowed.
+     * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
+     *                       varying is set within the shader to get proper results.
      * @param fragmentShader fragment shader to be supplied to the mesh.
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace) {
-        long nativeMeshSpec = nativeMakeWithCS(attributes, vertexStride, varyings, vertexShader,
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace) {
+        long nativeMeshSpec = nativeMakeWithCS(attributes.toArray(new Attribute[attributes.size()]),
+                vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
                 fragmentShader, colorSpace.getNativeInstance());
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
@@ -154,20 +211,33 @@
      *
      * @param attributes     list of attributes represented by {@link Attribute}. Can hold a max of
      *                       8.
-     * @param vertexStride   length of vertex stride. Max of 1024 is accepted.
+     * @param vertexStride   length of vertex stride in bytes. This should be the size of a single
+     *                       vertex' attributes. Max of 1024 is accepted.
      * @param varyings       List of varyings represented by {@link Varying}. Can hold a max of 6.
-     * @param vertexShader   vertex shader code to be supplied to the mesh.
-     * @param fragmentShader fragment shader code to be suppied to the mesh.
+     *                       Note that `position` is provided by default, does not need to be
+     *                       provided in the list, and does not count towards
+     *                       the 6 varyings allowed.
+     * @param vertexShader   vertex shader to be supplied to the mesh. Ensure that the position
+     *                       varying is set within the shader to get proper results.
+     * @param fragmentShader fragment shader to be supplied to the mesh.
      * @param colorSpace     {@link ColorSpace} to tell what color space to work in.
      * @param alphaType      Describes how to interpret the alpha component for a pixel. Must be
-     *                       one of {@link AlphaType} values.
+     *                       one of
+     *                       {@link MeshSpecification#UNKNOWN},
+     *                       {@link MeshSpecification#OPAQUE},
+     *                       {@link MeshSpecification#PREMUL}, or
+     *                       {@link MeshSpecification#UNPREMULT}
      * @return {@link MeshSpecification} object for use when creating {@link Mesh}
      */
-    public static MeshSpecification make(Attribute[] attributes, int vertexStride,
-            Varying[] varyings, String vertexShader, String fragmentShader, ColorSpace colorSpace,
+    @NonNull
+    public static MeshSpecification make(@NonNull List<Attribute> attributes, int vertexStride,
+            @NonNull List<Varying> varyings, @NonNull String vertexShader,
+            @NonNull String fragmentShader, @NonNull ColorSpace colorSpace,
             @AlphaType int alphaType) {
-        long nativeMeshSpec = nativeMakeWithAlpha(attributes, vertexStride, varyings, vertexShader,
-                fragmentShader, colorSpace.getNativeInstance(), alphaType);
+        long nativeMeshSpec =
+                nativeMakeWithAlpha(attributes.toArray(new Attribute[attributes.size()]),
+                        vertexStride, varyings.toArray(new Varying[varyings.size()]), vertexShader,
+                        fragmentShader, colorSpace.getNativeInstance(), alphaType);
         if (nativeMeshSpec == 0) {
             throw new IllegalArgumentException("MeshSpecification construction failed");
         }
diff --git a/graphics/java/android/graphics/fonts/SystemFonts.java b/graphics/java/android/graphics/fonts/SystemFonts.java
index ec8b2d6..8fe28ae 100644
--- a/graphics/java/android/graphics/fonts/SystemFonts.java
+++ b/graphics/java/android/graphics/fonts/SystemFonts.java
@@ -22,8 +22,8 @@
 import android.graphics.Typeface;
 import android.text.FontConfig;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
+import android.util.SparseIntArray;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -117,17 +117,18 @@
             }
         }
 
+
         final FontFamily defaultFamily = defaultFonts.isEmpty() ? null : createFontFamily(
                 defaultFonts, languageTags, variant, false, cache);
-
         // Insert family into fallback map.
         for (int i = 0; i < fallbackMap.size(); i++) {
             final String name = fallbackMap.keyAt(i);
             final NativeFamilyListSet familyListSet = fallbackMap.valueAt(i);
-            if (familyListSet.seenXmlFamilies.contains(xmlFamily)) {
+            int identityHash = System.identityHashCode(xmlFamily);
+            if (familyListSet.seenXmlFamilies.get(identityHash, -1) != -1) {
                 continue;
             } else {
-                familyListSet.seenXmlFamilies.add(xmlFamily);
+                familyListSet.seenXmlFamilies.append(identityHash, 1);
             }
             final ArrayList<FontConfig.Font> fallback = specificFallbackFonts.get(name);
             if (fallback == null) {
@@ -213,7 +214,7 @@
                 return;
             }
             familyListSet.familyList.add(family);
-            familyListSet.seenXmlFamilies.add(xmlFamily);
+            familyListSet.seenXmlFamilies.append(System.identityHashCode(xmlFamily), 1);
         }
         fallbackListMap.put(familyName, familyListSet);
     }
@@ -276,7 +277,7 @@
 
     private static final class NativeFamilyListSet {
         public List<FontFamily> familyList = new ArrayList<>();
-        public Set<FontConfig.FontFamily> seenXmlFamilies = new ArraySet<>();
+        public SparseIntArray seenXmlFamilies = new SparseIntArray();
     }
 
     /** @hide */
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index 23db233..774f6c6 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -115,4 +115,10 @@
     <!-- Components support to launch multiple instances into split-screen -->
     <string-array name="config_appsSupportMultiInstancesSplit">
     </string-array>
+
+    <!-- Whether the extended restart dialog is enabled -->
+    <bool name="config_letterboxIsRestartDialogEnabled">false</bool>
+
+    <!-- Whether the additional education about reachability is enabled -->
+    <bool name="config_letterboxIsReachabilityEducationEnabled">false</bool>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
index af13bf5..94aeb2b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
@@ -346,7 +346,7 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration newMergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int syncSeqId,
-                int resizeMode) {}
+                boolean dragResizing) {}
 
         @Override
         public void insetsControlChanged(InsetsState insetsState,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
new file mode 100644
index 0000000..4f33a71
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIConfiguration.java
@@ -0,0 +1,119 @@
+/*
+ * 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.wm.shell.compatui;
+
+import android.content.Context;
+import android.provider.DeviceConfig;
+
+import androidx.annotation.NonNull;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.WMSingleton;
+
+import javax.inject.Inject;
+
+/**
+ * Configuration flags for the CompatUX implementation
+ */
+@WMSingleton
+public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {
+
+    static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
+
+    static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
+            "enable_letterbox_reachability_education";
+
+    // Whether the extended restart dialog is enabled
+    private boolean mIsRestartDialogEnabled;
+
+    // Whether the additional education about reachability is enabled
+    private boolean mIsReachabilityEducationEnabled;
+
+    // Whether the extended restart dialog is enabled
+    private boolean mIsRestartDialogOverrideEnabled;
+
+    // Whether the additional education about reachability is enabled
+    private boolean mIsReachabilityEducationOverrideEnabled;
+
+    // Whether the extended restart dialog is allowed from backend
+    private boolean mIsLetterboxRestartDialogAllowed;
+
+    // Whether the additional education about reachability is allowed from backend
+    private boolean mIsLetterboxReachabilityEducationAllowed;
+
+    @Inject
+    public CompatUIConfiguration(Context context, @ShellMainThread ShellExecutor mainExecutor) {
+        mIsRestartDialogEnabled = context.getResources().getBoolean(
+                R.bool.config_letterboxIsRestartDialogEnabled);
+        mIsReachabilityEducationEnabled = context.getResources().getBoolean(
+                R.bool.config_letterboxIsReachabilityEducationEnabled);
+        mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG, false);
+        mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION,
+                false);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
+                this);
+    }
+
+    /**
+     * @return {@value true} if the restart dialog is enabled.
+     */
+    boolean isRestartDialogEnabled() {
+        return mIsRestartDialogOverrideEnabled || (mIsRestartDialogEnabled
+                && mIsLetterboxRestartDialogAllowed);
+    }
+
+    /**
+     * Enables/Disables the restart education dialog
+     */
+    void setIsRestartDialogOverrideEnabled(boolean enabled) {
+        mIsRestartDialogOverrideEnabled = enabled;
+    }
+
+    /**
+     * @return {@value true} if the reachability education is enabled.
+     */
+    boolean isReachabilityEducationEnabled() {
+        return mIsReachabilityEducationOverrideEnabled || (mIsReachabilityEducationEnabled
+                && mIsLetterboxReachabilityEducationAllowed);
+    }
+
+    /**
+     * Enables/Disables the reachability education
+     */
+    void setIsReachabilityEducationOverrideEnabled(boolean enabled) {
+        mIsReachabilityEducationOverrideEnabled = enabled;
+    }
+
+    @Override
+    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+        // TODO(b/263349751): Update flag and default value to true
+        if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_RESTART_DIALOG)) {
+            mIsLetterboxRestartDialogAllowed = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_WINDOW_MANAGER, KEY_ENABLE_LETTERBOX_RESTART_DIALOG,
+                    false);
+        }
+        if (properties.getKeyset().contains(KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION)) {
+            mIsLetterboxReachabilityEducationAllowed = DeviceConfig.getBoolean(
+                    DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                    KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
new file mode 100644
index 0000000..4fb18e2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIShellCommandHandler.java
@@ -0,0 +1,103 @@
+/*
+ * 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.wm.shell.compatui;
+
+import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+
+import java.io.PrintWriter;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+
+/**
+ * Handles the shell commands for the CompatUX.
+ *
+ * <p> Use with {@code adb shell dumpsys activity service SystemUIService WMShell compatui
+ * &lt;command&gt;}.
+ */
+@WMSingleton
+public final class CompatUIShellCommandHandler implements
+        ShellCommandHandler.ShellCommandActionHandler {
+
+    private final CompatUIConfiguration mCompatUIConfiguration;
+    private final ShellCommandHandler mShellCommandHandler;
+
+    @Inject
+    public CompatUIShellCommandHandler(ShellCommandHandler shellCommandHandler,
+            CompatUIConfiguration compatUIConfiguration) {
+        mShellCommandHandler = shellCommandHandler;
+        mCompatUIConfiguration = compatUIConfiguration;
+    }
+
+    void onInit() {
+        mShellCommandHandler.addCommandCallback("compatui", this, this);
+    }
+
+    @Override
+    public boolean onShellCommand(String[] args, PrintWriter pw) {
+        if (args.length != 2) {
+            pw.println("Invalid command: " + args[0]);
+            return false;
+        }
+        switch (args[0]) {
+            case "restartDialogEnabled":
+                return invokeOrError(args[1], pw,
+                        mCompatUIConfiguration::setIsRestartDialogOverrideEnabled);
+            case "reachabilityEducationEnabled":
+                return invokeOrError(args[1], pw,
+                        mCompatUIConfiguration::setIsReachabilityEducationOverrideEnabled);
+            default:
+                pw.println("Invalid command: " + args[0]);
+                return false;
+        }
+    }
+
+    @Override
+    public void printShellCommandHelp(PrintWriter pw, String prefix) {
+        pw.println(prefix + "restartDialogEnabled [0|false|1|true]");
+        pw.println(prefix + "  Enable/Disable the restart education dialog for Size Compat Mode");
+        pw.println(prefix + "reachabilityEducationEnabled [0|false|1|true]");
+        pw.println(prefix
+                + "  Enable/Disable the restart education dialog for letterbox reachability");
+        pw.println(prefix + "  Disable the restart education dialog for letterbox reachability");
+    }
+
+    private static boolean invokeOrError(String input, PrintWriter pw,
+            Consumer<Boolean> setter) {
+        Boolean asBoolean = strToBoolean(input);
+        if (asBoolean == null) {
+            pw.println("Error: expected true, 1, false, 0.");
+            return false;
+        }
+        setter.accept(asBoolean);
+        return true;
+    }
+
+    // Converts a String to boolean if possible or it returns null otherwise
+    private static Boolean strToBoolean(String str) {
+        switch(str) {
+            case "1":
+            case "true":
+                return true;
+            case "0":
+            case "false":
+                return false;
+        }
+        return null;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
similarity index 85%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
index 3061eab..7475fea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogAnimationController.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.compatui.letterboxedu;
+package com.android.wm.shell.compatui;
 
 import static com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
@@ -38,10 +38,15 @@
 import com.android.internal.policy.TransitionAnimation;
 
 /**
- * Controls the enter/exit animations of the letterbox education.
+ * Controls the enter/exit a dialog.
+ *
+ * @param <T> The {@link DialogContainerSupplier} to use
  */
-class LetterboxEduAnimationController {
-    private static final String TAG = "LetterboxEduAnimation";
+public class DialogAnimationController<T extends DialogContainerSupplier> {
+
+    // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
+    // 204 is simply 255 * 0.8.
+    static final int BACKGROUND_DIM_ALPHA = 204;
 
     // If shell transitions are enabled, startEnterAnimation will be called after all transitions
     // have finished, and therefore the start delay should be shorter.
@@ -49,6 +54,7 @@
 
     private final TransitionAnimation mTransitionAnimation;
     private final String mPackageName;
+    private final String mTag;
     @AnyRes
     private final int mAnimStyleResId;
 
@@ -57,23 +63,24 @@
     @Nullable
     private Animator mBackgroundDimAnimator;
 
-    LetterboxEduAnimationController(Context context) {
-        mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, TAG);
+    public DialogAnimationController(Context context, String tag) {
+        mTransitionAnimation = new TransitionAnimation(context, /* debug= */ false, tag);
         mAnimStyleResId = (new ContextThemeWrapper(context,
                 android.R.style.ThemeOverlay_Material_Dialog).getTheme()).obtainStyledAttributes(
                 com.android.internal.R.styleable.Window).getResourceId(
                 com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
         mPackageName = context.getPackageName();
+        mTag = tag;
     }
 
     /**
      * Starts both background dim fade-in animation and the dialog enter animation.
      */
-    void startEnterAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+    public void startEnterAnimation(@NonNull T layout, Runnable endCallback) {
         // Cancel any previous animation if it's still running.
         cancelAnimation();
 
-        final View dialogContainer = layout.getDialogContainer();
+        final View dialogContainer = layout.getDialogContainerView();
         mDialogAnimation = loadAnimation(WindowAnimation_windowEnterAnimation);
         if (mDialogAnimation == null) {
             endCallback.run();
@@ -86,8 +93,8 @@
                     endCallback.run();
                 }));
 
-        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(),
-                /* endAlpha= */ LetterboxEduDialogLayout.BACKGROUND_DIM_ALPHA,
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+                /* endAlpha= */ BACKGROUND_DIM_ALPHA,
                 mDialogAnimation.getDuration());
         mBackgroundDimAnimator.addListener(getDimAnimatorListener());
 
@@ -101,11 +108,11 @@
     /**
      * Starts both the background dim fade-out animation and the dialog exit animation.
      */
-    void startExitAnimation(@NonNull LetterboxEduDialogLayout layout, Runnable endCallback) {
+    public void startExitAnimation(@NonNull T layout, Runnable endCallback) {
         // Cancel any previous animation if it's still running.
         cancelAnimation();
 
-        final View dialogContainer = layout.getDialogContainer();
+        final View dialogContainer = layout.getDialogContainerView();
         mDialogAnimation = loadAnimation(WindowAnimation_windowExitAnimation);
         if (mDialogAnimation == null) {
             endCallback.run();
@@ -119,8 +126,8 @@
                     endCallback.run();
                 }));
 
-        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDim(), /* endAlpha= */ 0,
-                mDialogAnimation.getDuration());
+        mBackgroundDimAnimator = getAlphaAnimator(layout.getBackgroundDimDrawable(),
+                /* endAlpha= */ 0, mDialogAnimation.getDuration());
         mBackgroundDimAnimator.addListener(getDimAnimatorListener());
 
         dialogContainer.startAnimation(mDialogAnimation);
@@ -130,7 +137,7 @@
     /**
      * Cancels all animations and resets the state of the controller.
      */
-    void cancelAnimation() {
+    public void cancelAnimation() {
         if (mDialogAnimation != null) {
             mDialogAnimation.cancel();
             mDialogAnimation = null;
@@ -145,7 +152,7 @@
         Animation animation = mTransitionAnimation.loadAnimationAttr(mPackageName, mAnimStyleResId,
                 animAttr, /* translucent= */ false);
         if (animation == null) {
-            Log.e(TAG, "Failed to load animation " + animAttr);
+            Log.e(mTag, "Failed to load animation " + animAttr);
         }
         return animation;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
new file mode 100644
index 0000000..7eea446
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/DialogContainerSupplier.java
@@ -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.
+ */
+
+package com.android.wm.shell.compatui;
+
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+/**
+ * A component which can provide a {@link View} to use as a container for a Dialog
+ */
+public interface DialogContainerSupplier {
+
+    /**
+     * @return The {@link View} to use as a container for a Dialog
+     */
+    View getDialogContainerView();
+
+    /**
+     * @return The {@link Drawable} to use as background of the dialog.
+     */
+    Drawable getBackgroundDimDrawable();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
index 2e0b09e..9232f36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayout.java
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.compatui.DialogContainerSupplier;
 
 /**
  * Container for Letterbox Education Dialog and background dim.
@@ -33,11 +34,7 @@
  * <p>This layout should fill the entire task and the background around the dialog acts as the
  * background dim which dismisses the dialog when clicked.
  */
-class LetterboxEduDialogLayout extends ConstraintLayout {
-
-    // The alpha of a background is a number between 0 (fully transparent) to 255 (fully opaque).
-    // 204 is simply 255 * 0.8.
-    static final int BACKGROUND_DIM_ALPHA = 204;
+class LetterboxEduDialogLayout extends ConstraintLayout implements DialogContainerSupplier {
 
     private View mDialogContainer;
     private TextView mDialogTitle;
@@ -60,18 +57,20 @@
         super(context, attrs, defStyleAttr, defStyleRes);
     }
 
-    View getDialogContainer() {
+    @Override
+    public View getDialogContainerView() {
         return mDialogContainer;
     }
 
+    @Override
+    public Drawable getBackgroundDimDrawable() {
+        return mBackgroundDim;
+    }
+
     TextView getDialogTitle() {
         return mDialogTitle;
     }
 
-    Drawable getBackgroundDim() {
-        return mBackgroundDim;
-    }
-
     /**
      * Register a callback for the dismiss button and background dim.
      *
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
index 867d0ef..c14c009 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManager.java
@@ -37,6 +37,7 @@
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.compatui.CompatUIWindowManagerAbstract;
+import com.android.wm.shell.compatui.DialogAnimationController;
 import com.android.wm.shell.transition.Transitions;
 
 /**
@@ -63,7 +64,7 @@
      */
     private final SharedPreferences mSharedPreferences;
 
-    private final LetterboxEduAnimationController mAnimationController;
+    private final DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
 
     private final Transitions mTransitions;
 
@@ -96,14 +97,17 @@
             DisplayLayout displayLayout, Transitions transitions,
             Runnable onDismissCallback, DockStateReader dockStateReader) {
         this(context, taskInfo, syncQueue, taskListener, displayLayout, transitions,
-                onDismissCallback, new LetterboxEduAnimationController(context), dockStateReader);
+                onDismissCallback,
+                new DialogAnimationController<>(context, /* tag */ "LetterboxEduWindowManager"),
+                dockStateReader);
     }
 
     @VisibleForTesting
     LetterboxEduWindowManager(Context context, TaskInfo taskInfo,
             SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
             DisplayLayout displayLayout, Transitions transitions, Runnable onDismissCallback,
-            LetterboxEduAnimationController animationController, DockStateReader dockStateReader) {
+            DialogAnimationController<LetterboxEduDialogLayout> animationController,
+            DockStateReader dockStateReader) {
         super(context, taskInfo, syncQueue, taskListener, displayLayout);
         mTransitions = transitions;
         mOnDismissCallback = onDismissCallback;
@@ -160,7 +164,7 @@
         if (mLayout == null) {
             return;
         }
-        final View dialogContainer = mLayout.getDialogContainer();
+        final View dialogContainer = mLayout.getDialogContainerView();
         MarginLayoutParams marginParams = (MarginLayoutParams) dialogContainer.getLayoutParams();
 
         final Rect taskBounds = getTaskBounds();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b075b14..3341470 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -17,12 +17,20 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager
-import android.app.WindowConfiguration
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
+import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
+import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
-import android.view.WindowManager
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_CHANGE
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
 import com.android.internal.protolog.common.ProtoLog
@@ -51,7 +59,7 @@
     private val transitions: Transitions,
     private val desktopModeTaskRepository: DesktopModeTaskRepository,
     @ShellMainThread private val mainExecutor: ShellExecutor
-) : RemoteCallable<DesktopTasksController> {
+) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
 
     private val desktopMode: DesktopModeImpl
 
@@ -69,6 +77,7 @@
             { createExternalInterface() },
             this
         )
+        transitions.addHandler(this)
     }
 
     /** Show all tasks, that are part of the desktop, on top of launcher */
@@ -81,7 +90,7 @@
         // Execute transaction if there are pending operations
         if (!wct.isEmpty) {
             if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                transitions.startTransition(WindowManager.TRANSIT_TO_FRONT, wct, null /* handler */)
+                transitions.startTransition(TRANSIT_TO_FRONT, wct, null /* handler */)
             } else {
                 shellTaskOrganizer.applyTransaction(wct)
             }
@@ -101,11 +110,11 @@
         // Bring other apps to front first
         bringDesktopAppsToFront(wct)
 
-        wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FREEFORM)
+        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FREEFORM)
         wct.reorder(task.getToken(), true /* onTop */)
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -121,10 +130,10 @@
         ProtoLog.v(WM_SHELL_DESKTOP_MODE, "moveToFullscreen: %d", task.taskId)
 
         val wct = WindowContainerTransaction()
-        wct.setWindowingMode(task.getToken(), WindowConfiguration.WINDOWING_MODE_FULLSCREEN)
+        wct.setWindowingMode(task.getToken(), WINDOWING_MODE_FULLSCREEN)
         wct.setBounds(task.getToken(), null)
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-            transitions.startTransition(WindowManager.TRANSIT_CHANGE, wct, null /* handler */)
+            transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */)
         } else {
             shellTaskOrganizer.applyTransaction(wct)
         }
@@ -181,6 +190,80 @@
         return mainExecutor
     }
 
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: Transitions.TransitionFinishCallback
+    ): Boolean {
+        // This handler should never be the sole handler, so should not animate anything.
+        return false
+    }
+
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo
+    ): WindowContainerTransaction? {
+        // Check if we should skip handling this transition
+        val task: ActivityManager.RunningTaskInfo? = request.triggerTask
+        val shouldHandleRequest =
+            when {
+                // Only handle open or to front transitions
+                request.type != TRANSIT_OPEN && request.type != TRANSIT_TO_FRONT -> false
+                // Only handle when it is a task transition
+                task == null -> false
+                // Only handle standard type tasks
+                task.activityType != ACTIVITY_TYPE_STANDARD -> false
+                // Only handle fullscreen or freeform tasks
+                task.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                    task.windowingMode != WINDOWING_MODE_FREEFORM -> false
+                // Otherwise process it
+                else -> true
+            }
+
+        if (!shouldHandleRequest) {
+            return null
+        }
+
+        val activeTasks = desktopModeTaskRepository.getActiveTasks()
+
+        // Check if we should switch a fullscreen task to freeform
+        if (task?.windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            // If there are any visible desktop tasks, switch the task to freeform
+            if (activeTasks.any { desktopModeTaskRepository.isVisibleTask(it) }) {
+                ProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController#handleRequest: switch fullscreen task to freeform," +
+                        " taskId=%d",
+                    task.taskId
+                )
+                return WindowContainerTransaction().apply {
+                    setWindowingMode(task.token, WINDOWING_MODE_FREEFORM)
+                }
+            }
+        }
+
+        // CHeck if we should switch a freeform task to fullscreen
+        if (task?.windowingMode == WINDOWING_MODE_FREEFORM) {
+            // If no visible desktop tasks, switch this task to freeform as the transition came
+            // outside of this controller
+            if (activeTasks.none { desktopModeTaskRepository.isVisibleTask(it) }) {
+                ProtoLog.d(
+                    WM_SHELL_DESKTOP_MODE,
+                    "DesktopTasksController#handleRequest: switch freeform task to fullscreen," +
+                        " taskId=%d",
+                    task.taskId
+                )
+                return WindowContainerTransaction().apply {
+                    setWindowingMode(task.token, WINDOWING_MODE_FULLSCREEN)
+                    setBounds(task.token, null)
+                }
+            }
+        }
+        return null
+    }
+
     /** Creates a new instance of the external interface to pass to another process. */
     private fun createExternalInterface(): ExternalInterfaceBinder {
         return IDesktopModeImpl(this)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 9d6711f..65da757b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -239,7 +239,7 @@
         public void resized(ClientWindowFrames frames, boolean reportDraw,
                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
-                int resizeMode) {
+                boolean dragResizing) {
             final TaskSnapshotWindow snapshot = mOuter.get();
             if (snapshot == null) {
                 return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index 6b59e31..d7cb490 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -16,8 +16,6 @@
 
 package com.android.wm.shell.unfold;
 
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
 import android.annotation.NonNull;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -56,6 +54,12 @@
     private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
     private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
 
+    /**
+     * Indicates whether we're in stage change process. This should be set to {@code true} in
+     * {@link #onStateChangeStarted()} and {@code false} in {@link #onStateChangeFinished()}.
+     */
+    private boolean mIsInStageChange;
+
     public UnfoldAnimationController(
             @NonNull ShellInit shellInit,
             @NonNull TransactionPool transactionPool,
@@ -123,7 +127,7 @@
                 animator.onTaskChanged(taskInfo);
             } else {
                 // Became inapplicable
-                resetTask(animator, taskInfo);
+                maybeResetTask(animator, taskInfo);
                 animator.onTaskVanished(taskInfo);
                 mAnimatorsByTaskId.remove(taskInfo.taskId);
             }
@@ -154,7 +158,7 @@
         final boolean isCurrentlyApplicable = animator != null;
 
         if (isCurrentlyApplicable) {
-            resetTask(animator, taskInfo);
+            maybeResetTask(animator, taskInfo);
             animator.onTaskVanished(taskInfo);
             mAnimatorsByTaskId.remove(taskInfo.taskId);
         }
@@ -166,6 +170,7 @@
             return;
         }
 
+        mIsInStageChange = true;
         SurfaceControl.Transaction transaction = null;
         for (int i = 0; i < mAnimators.size(); i++) {
             final UnfoldTaskAnimator animator = mAnimators.get(i);
@@ -219,11 +224,12 @@
         transaction.apply();
 
         mTransactionPool.release(transaction);
+        mIsInStageChange = false;
     }
 
-    private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
-        if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
-            // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
+    private void maybeResetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
+        if (!mIsInStageChange) {
+            // No need to resetTask if there is no ongoing state change.
             return;
         }
         final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
index 8465678..122c18d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/BaseTest.kt
@@ -33,7 +33,6 @@
 import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.taskBarWindowIsAlwaysVisible
 import com.android.server.wm.traces.common.ComponentNameMatcher
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.Test
 
@@ -49,15 +48,6 @@
     protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation(),
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
-    init {
-        flicker.scenario.setIsTablet(
-            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
-                .currentState
-                .wmState
-                .isTablet
-        )
-    }
-
     /** Specification of the test transition to execute */
     abstract val transition: FlickerBuilder.() -> Unit
 
@@ -68,7 +58,7 @@
     @FlickerBuilderProvider
     fun buildFlicker(): FlickerBuilder {
         return FlickerBuilder(instrumentation).apply {
-            setup { flicker.scenario.setIsTablet(wmHelper.currentState.wmState.isTablet) }
+            setup { flicker.scenario.setIsTablet(tapl.isTablet) }
             transition()
         }
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index 2b90243..a4c8d6f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -120,7 +120,7 @@
      */
     @Before
     fun setup() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(tapl.isTablet)
     }
 
     /**
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index 157aa98..7d5dd89 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -103,7 +103,7 @@
      */
     @Before
     fun setup() {
-        Assume.assumeFalse(flicker.scenario.isTablet)
+        Assume.assumeFalse(tapl.isTablet)
     }
 
     @Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index 0160f18..73671db 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -85,7 +85,18 @@
     @Test
     fun splitScreenDividerKeepVisible() = flicker.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
-    @Presubmit @Test fun primaryAppLayerKeepVisible() = flicker.layerKeepVisible(primaryApp)
+    @Presubmit
+    @Test
+    fun primaryAppLayerKeepVisible() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
+        flicker.layerKeepVisible(primaryApp)
+    }
+
+    @FlakyTest(bugId = 263213649)
+    @Test fun primaryAppLayerKeepVisible_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        flicker.layerKeepVisible(primaryApp)
+    }
 
     @Presubmit
     @Test
@@ -99,17 +110,7 @@
         }
     }
 
-    @Presubmit
-    @Test fun primaryAppWindowKeepVisible() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
-        flicker.appWindowKeepVisible(primaryApp)
-    }
-
-    @FlakyTest(bugId = 263213649)
-    @Test fun primaryAppWindowKeepVisible_ShellTransit() {
-        Assume.assumeTrue(isShellTransitionsEnabled)
-        flicker.appWindowKeepVisible(primaryApp)
-    }
+    @Presubmit @Test fun primaryAppWindowKeepVisible() = flicker.appWindowKeepVisible(primaryApp)
 
     @Presubmit
     @Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index abf9426..af63f7c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -53,7 +53,7 @@
 
     @Before
     fun before() {
-        Assume.assumeTrue(flicker.scenario.isTablet)
+        Assume.assumeTrue(tapl.isTablet)
     }
 
     override val transition: FlickerBuilder.() -> Unit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
index 1dee88c..a58620d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduDialogLayoutTest.java
@@ -68,11 +68,11 @@
 
     @Test
     public void testOnFinishInflate() {
-        assertEquals(mLayout.getDialogContainer(),
+        assertEquals(mLayout.getDialogContainerView(),
                 mLayout.findViewById(R.id.letterbox_education_dialog_container));
         assertEquals(mLayout.getDialogTitle(),
                 mLayout.findViewById(R.id.letterbox_education_dialog_title));
-        assertEquals(mLayout.getBackgroundDim(), mLayout.getBackground());
+        assertEquals(mLayout.getBackgroundDimDrawable(), mLayout.getBackground());
         assertEquals(mLayout.getBackground().getAlpha(), 0);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
index 16517c0..14190f1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/letterboxedu/LetterboxEduWindowManagerTest.java
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.compatui.DialogAnimationController;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.After;
@@ -98,7 +99,7 @@
     @Captor
     private ArgumentCaptor<Runnable> mRunOnIdleCaptor;
 
-    @Mock private LetterboxEduAnimationController mAnimationController;
+    @Mock private DialogAnimationController<LetterboxEduDialogLayout> mAnimationController;
     @Mock private SyncTransactionQueue mSyncTransactionQueue;
     @Mock private ShellTaskOrganizer.TaskListener mTaskListener;
     @Mock private SurfaceControlViewHost mViewHost;
@@ -366,7 +367,7 @@
         assertThat(params.width).isEqualTo(expectedWidth);
         assertThat(params.height).isEqualTo(expectedHeight);
         MarginLayoutParams dialogParams =
-                (MarginLayoutParams) layout.getDialogContainer().getLayoutParams();
+                (MarginLayoutParams) layout.getDialogContainerView().getLayoutParams();
         int verticalMargin = (int) mContext.getResources().getDimension(
                 R.dimen.letterbox_education_dialog_margin);
         assertThat(dialogParams.topMargin).isEqualTo(verticalMargin + expectedExtraTopMargin);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index de2473b..9a92879 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -17,10 +17,18 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
+import android.os.Binder
 import android.testing.AndroidTestingRunner
+import android.view.WindowManager
+import android.view.WindowManager.TRANSIT_OPEN
+import android.view.WindowManager.TRANSIT_TO_FRONT
+import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
 import androidx.test.filters.SmallTest
@@ -29,6 +37,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.TestShellExecutor
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
@@ -37,9 +46,11 @@
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.After
+import org.junit.Assume.assumeTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -79,6 +90,7 @@
         desktopModeTaskRepository = DesktopModeTaskRepository()
 
         whenever(shellTaskOrganizer.getRunningTasks(anyInt())).thenAnswer { runningTasks }
+        whenever(transitions.startTransition(anyInt(), any(), isNull())).thenAnswer { Binder() }
 
         controller = createController()
 
@@ -221,6 +233,114 @@
         assertThat(controller.getTaskWindowingMode(999)).isEqualTo(WINDOWING_MODE_UNDEFINED)
     }
 
+    @Test
+    fun handleRequest_fullscreenTask_freeformVisible_returnSwitchToFreeformWCT() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val freeformTask = setUpFreeformTask()
+        markTaskVisible(freeformTask)
+        val fullscreenTask = createFullscreenTask()
+
+        val result = controller.handleRequest(Binder(), createTransition(fullscreenTask))
+        assertThat(result?.changes?.get(fullscreenTask.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FREEFORM)
+    }
+
+    @Test
+    fun handleRequest_fullscreenTask_freeformNotVisible_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val freeformTask = setUpFreeformTask()
+        markTaskHidden(freeformTask)
+        val fullscreenTask = createFullscreenTask()
+        assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+    }
+
+    @Test
+    fun handleRequest_fullscreenTask_noOtherTasks_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val fullscreenTask = createFullscreenTask()
+        assertThat(controller.handleRequest(Binder(), createTransition(fullscreenTask))).isNull()
+    }
+
+    @Test
+    fun handleRequest_freeformTask_freeformVisible_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val freeformTask1 = setUpFreeformTask()
+        markTaskVisible(freeformTask1)
+
+        val freeformTask2 = createFreeformTask()
+        assertThat(controller.handleRequest(Binder(), createTransition(freeformTask2))).isNull()
+    }
+
+    @Test
+    fun handleRequest_freeformTask_freeformNotVisible_returnSwitchToFullscreenWCT() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val freeformTask1 = setUpFreeformTask()
+        markTaskHidden(freeformTask1)
+
+        val freeformTask2 = createFreeformTask()
+        val result =
+            controller.handleRequest(
+                Binder(),
+                createTransition(freeformTask2, type = TRANSIT_TO_FRONT)
+            )
+        assertThat(result?.changes?.get(freeformTask2.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    @Test
+    fun handleRequest_freeformTask_noOtherTasks_returnSwitchToFullscreenWCT() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val task = createFreeformTask()
+        val result = controller.handleRequest(Binder(), createTransition(task))
+        assertThat(result?.changes?.get(task.token.asBinder())?.windowingMode)
+            .isEqualTo(WINDOWING_MODE_FULLSCREEN)
+    }
+
+    @Test
+    fun handleRequest_notOpenOrToFrontTransition_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val task =
+            TestRunningTaskInfoBuilder()
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+                .build()
+        val transition = createTransition(task = task, type = WindowManager.TRANSIT_CLOSE)
+        val result = controller.handleRequest(Binder(), transition)
+        assertThat(result).isNull()
+    }
+
+    @Test
+    fun handleRequest_noTriggerTask_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        assertThat(controller.handleRequest(Binder(), createTransition(task = null))).isNull()
+    }
+
+    @Test
+    fun handleRequest_triggerTaskNotStandard_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+        val task = TestRunningTaskInfoBuilder().setActivityType(ACTIVITY_TYPE_HOME).build()
+        assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+    }
+
+    @Test
+    fun handleRequest_triggerTaskNotFullscreenOrFreeform_returnNull() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS)
+
+        val task =
+            TestRunningTaskInfoBuilder()
+                .setActivityType(ACTIVITY_TYPE_STANDARD)
+                .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
+                .build()
+        assertThat(controller.handleRequest(Binder(), createTransition(task))).isNull()
+    }
+
     private fun setUpFreeformTask(): RunningTaskInfo {
         val task = createFreeformTask()
         whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
@@ -254,7 +374,7 @@
 
     private fun getLatestWct(): WindowContainerTransaction {
         val arg = ArgumentCaptor.forClass(WindowContainerTransaction::class.java)
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+        if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions).startTransition(anyInt(), arg.capture(), isNull())
         } else {
             verify(shellTaskOrganizer).applyTransaction(arg.capture())
@@ -263,12 +383,19 @@
     }
 
     private fun verifyWCTNotExecuted() {
-        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+        if (ENABLE_SHELL_TRANSITIONS) {
             verify(transitions, never()).startTransition(anyInt(), any(), isNull())
         } else {
             verify(shellTaskOrganizer, never()).applyTransaction(any())
         }
     }
+
+    private fun createTransition(
+        task: RunningTaskInfo?,
+        @WindowManager.TransitionType type: Int = TRANSIT_OPEN
+    ): TransitionRequestInfo {
+        return TransitionRequestInfo(type, task, null /* remoteTransition */)
+    }
 }
 
 private fun WindowContainerTransaction.assertReorderAt(index: Int, task: RunningTaskInfo) {
diff --git a/libs/hwui/AndroidTest.xml b/libs/hwui/AndroidTest.xml
index 911315f..75f61f5 100644
--- a/libs/hwui/AndroidTest.xml
+++ b/libs/hwui/AndroidTest.xml
@@ -21,6 +21,7 @@
         <option name="push" value="hwuimacro->/data/local/tmp/benchmarktest/hwuimacro" />
     </target_preparer>
     <option name="test-suite-tag" value="apct" />
+    <option name="not-shardable" value="true" />
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
         <option name="module-name" value="hwui_unit_tests" />
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 6a3bc8f..c835849 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -576,14 +576,22 @@
     LOG_ALWAYS_FATAL_IF(!decodeColorSpace->toXYZD50(&xyzMatrix));
 
     skcms_TransferFunction transferParams;
-    // We can only handle numerical transfer functions at the moment
-    LOG_ALWAYS_FATAL_IF(!decodeColorSpace->isNumericalTransferFn(&transferParams));
+    decodeColorSpace->transferFn(&transferParams);
+    auto res = skcms_TransferFunction_getType(&transferParams);
+    LOG_ALWAYS_FATAL_IF(res == skcms_TFType_HLGinvish || res == skcms_TFType_Invalid);
 
-    jobject params = env->NewObject(gTransferParameters_class,
-            gTransferParameters_constructorMethodID,
-            transferParams.a, transferParams.b, transferParams.c,
-            transferParams.d, transferParams.e, transferParams.f,
-            transferParams.g);
+    jobject params;
+    if (res == skcms_TFType_PQish || res == skcms_TFType_HLGish) {
+        params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+                                transferParams.a, transferParams.b, transferParams.c,
+                                transferParams.d, transferParams.e, transferParams.f,
+                                transferParams.g, true);
+    } else {
+        params = env->NewObject(gTransferParameters_class, gTransferParameters_constructorMethodID,
+                                transferParams.a, transferParams.b, transferParams.c,
+                                transferParams.d, transferParams.e, transferParams.f,
+                                transferParams.g, false);
+    }
 
     jfloatArray xyzArray = env->NewFloatArray(9);
     jfloat xyz[9] = {
@@ -808,8 +816,8 @@
 
     gTransferParameters_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
             "android/graphics/ColorSpace$Rgb$TransferParameters"));
-    gTransferParameters_constructorMethodID = GetMethodIDOrDie(env, gTransferParameters_class,
-            "<init>", "(DDDDDDD)V");
+    gTransferParameters_constructorMethodID =
+            GetMethodIDOrDie(env, gTransferParameters_class, "<init>", "(DDDDDDDZ)V");
 
     gFontMetrics_class = FindClassOrDie(env, "android/graphics/Paint$FontMetrics");
     gFontMetrics_class = MakeGlobalRefOrDie(env, gFontMetrics_class);
diff --git a/libs/hwui/jni/Mesh.cpp b/libs/hwui/jni/Mesh.cpp
index 7b9a93f..3aac48d 100644
--- a/libs/hwui/jni/Mesh.cpp
+++ b/libs/hwui/jni/Mesh.cpp
@@ -44,10 +44,16 @@
     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)
-                        .mesh;
-    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+    auto meshResult = SkMesh::Make(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+    if (!meshResult.error.isEmpty()) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+    }
+
+    auto meshPtr = std::make_unique<MeshWrapper>(
+            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
 
@@ -61,11 +67,17 @@
     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)
-                        .mesh;
-    auto meshPtr = std::make_unique<MeshWrapper>(MeshWrapper{mesh, MeshUniformBuilder(skMeshSpec)});
+
+    auto meshResult = SkMesh::MakeIndexed(
+            skMeshSpec, SkMesh::Mode(mode), skVertexBuffer, vertexCount, vertexOffset,
+            skIndexBuffer, indexCount, indexOffset,
+            SkData::MakeWithCopy(skMeshSpec->uniforms().data(), skMeshSpec->uniformSize()), skRect);
+
+    if (!meshResult.error.isEmpty()) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", meshResult.error.c_str());
+    }
+    auto meshPtr = std::make_unique<MeshWrapper>(
+            MeshWrapper{meshResult.mesh, MeshUniformBuilder(skMeshSpec)});
     return reinterpret_cast<jlong>(meshPtr.release());
 }
 
@@ -139,22 +151,22 @@
     }
 }
 
-static void updateFloatUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateFloatUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                                 jfloat value1, jfloat value2, jfloat value3, jfloat value4,
                                 jint count) {
-    auto* builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto* wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     const float values[4] = {value1, value2, value3, value4};
-    nativeUpdateFloatUniforms(env, builder, name.c_str(), values, count, false);
+    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), values, count, false);
 }
 
-static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring jUniformName,
+static void updateFloatArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring jUniformName,
                                      jfloatArray jvalues, jboolean isColor) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, jUniformName);
     AutoJavaFloatArray autoValues(env, jvalues, 0, kRO_JNIAccess);
-    nativeUpdateFloatUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length(),
-                              isColor);
+    nativeUpdateFloatUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+                              autoValues.length(), isColor);
 }
 
 static void nativeUpdateIntUniforms(JNIEnv* env, MeshUniformBuilder* builder,
@@ -171,20 +183,21 @@
     }
 }
 
-static void updateIntUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                               jint value1, jint value2, jint value3, jint value4, jint count) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     const int values[4] = {value1, value2, value3, value4};
-    nativeUpdateIntUniforms(env, builder, name.c_str(), values, count);
+    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), values, count);
 }
 
-static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong uniBuilder, jstring uniformName,
+static void updateIntArrayUniforms(JNIEnv* env, jobject, jlong meshWrapper, jstring uniformName,
                                    jintArray values) {
-    auto builder = reinterpret_cast<MeshUniformBuilder*>(uniBuilder);
+    auto wrapper = reinterpret_cast<MeshWrapper*>(meshWrapper);
     ScopedUtfChars name(env, uniformName);
     AutoJavaIntArray autoValues(env, values, 0);
-    nativeUpdateIntUniforms(env, builder, name.c_str(), autoValues.ptr(), autoValues.length());
+    nativeUpdateIntUniforms(env, &wrapper->builder, name.c_str(), autoValues.ptr(),
+                            autoValues.length());
 }
 
 static void MeshWrapper_destroy(MeshWrapper* wrapper) {
diff --git a/libs/hwui/jni/Mesh.h b/libs/hwui/jni/Mesh.h
index aa014a5..7a73f2d 100644
--- a/libs/hwui/jni/Mesh.h
+++ b/libs/hwui/jni/Mesh.h
@@ -239,6 +239,7 @@
 
     explicit MeshUniformBuilder(sk_sp<SkMeshSpecification> meshSpec) {
         fMeshSpec = sk_sp(meshSpec);
+        fUniforms = (SkData::MakeZeroInitialized(meshSpec->uniformSize()));
     }
 
     sk_sp<SkData> fUniforms;
diff --git a/libs/hwui/jni/MeshSpecification.cpp b/libs/hwui/jni/MeshSpecification.cpp
index 619a3ed..ae9792d 100644
--- a/libs/hwui/jni/MeshSpecification.cpp
+++ b/libs/hwui/jni/MeshSpecification.cpp
@@ -78,7 +78,6 @@
     auto meshSpecResult = SkMeshSpecification::Make(attributes, vertexStride, varyings,
                                                     SkString(skVertexShader.c_str()),
                                                     SkString(skFragmentShader.c_str()));
-
     if (meshSpecResult.specification.get() == nullptr) {
         jniThrowException(env, "java/lang/IllegalArgumentException", meshSpecResult.error.c_str());
     }
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 64839d0..78ae5cf 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -475,6 +475,7 @@
 void CanvasContext::notifyFramePending() {
     ATRACE_CALL();
     mRenderThread.pushBackFrameCallback(this);
+    sendLoadResetHint();
 }
 
 void CanvasContext::draw() {
diff --git a/location/java/android/location/GnssMeasurementsEvent.java b/location/java/android/location/GnssMeasurementsEvent.java
index a8b0dc2..8b96974 100644
--- a/location/java/android/location/GnssMeasurementsEvent.java
+++ b/location/java/android/location/GnssMeasurementsEvent.java
@@ -37,11 +37,14 @@
  * Events are delivered to registered instances of {@link Callback}.
  */
 public final class GnssMeasurementsEvent implements Parcelable {
+    private final int mFlag;
     private final GnssClock mClock;
     private final List<GnssMeasurement> mMeasurements;
     private final List<GnssAutomaticGainControl> mGnssAgcs;
     private final boolean mIsFullTracking;
 
+    private static final int HAS_FULL_TRACKING = 1;
+
     /**
      * Used for receiving GNSS satellite measurements from the GNSS engine.
      * Each measurement contains raw and computed data identifying a satellite.
@@ -123,10 +126,12 @@
     /**
      * Create a {@link GnssMeasurementsEvent} instance with a full list of parameters.
      */
-    private GnssMeasurementsEvent(@NonNull GnssClock clock,
+    private GnssMeasurementsEvent(int flag,
+            @NonNull GnssClock clock,
             @NonNull List<GnssMeasurement> measurements,
             @NonNull List<GnssAutomaticGainControl> agcs,
             boolean isFullTracking) {
+        mFlag = flag;
         mMeasurements = measurements;
         mGnssAgcs = agcs;
         mClock = clock;
@@ -168,22 +173,32 @@
      *
      * False indicates that the GNSS chipset may optimize power via duty cycling, constellations and
      * frequency limits, etc.
+     *
+     * <p>The value is only available if {@link #hasFullTracking()} is {@code true}.
      */
-    public boolean getIsFullTracking() {
+    public boolean isFullTracking() {
         return mIsFullTracking;
     }
 
+    /**
+     * Return {@code true} if {@link #isFullTracking()} is available, {@code false} otherwise.
+     */
+    public boolean hasFullTracking() {
+        return (mFlag & HAS_FULL_TRACKING) == HAS_FULL_TRACKING;
+    }
+
     public static final @android.annotation.NonNull Creator<GnssMeasurementsEvent> CREATOR =
             new Creator<GnssMeasurementsEvent>() {
         @Override
         public GnssMeasurementsEvent createFromParcel(Parcel in) {
+            int flag = in.readInt();
             GnssClock clock = in.readParcelable(getClass().getClassLoader(),
                     android.location.GnssClock.class);
             List<GnssMeasurement> measurements = in.createTypedArrayList(GnssMeasurement.CREATOR);
             List<GnssAutomaticGainControl> agcs = in.createTypedArrayList(
                     GnssAutomaticGainControl.CREATOR);
             boolean isFullTracking = in.readBoolean();
-            return new GnssMeasurementsEvent(clock, measurements, agcs, isFullTracking);
+            return new GnssMeasurementsEvent(flag, clock, measurements, agcs, isFullTracking);
         }
 
         @Override
@@ -199,6 +214,7 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mFlag);
         parcel.writeParcelable(mClock, flags);
         parcel.writeTypedList(mMeasurements);
         parcel.writeTypedList(mGnssAgcs);
@@ -211,13 +227,16 @@
         builder.append(mClock);
         builder.append(' ').append(mMeasurements.toString());
         builder.append(' ').append(mGnssAgcs.toString());
-        builder.append(" isFullTracking=").append(mIsFullTracking);
+        if (hasFullTracking()) {
+            builder.append(" isFullTracking=").append(mIsFullTracking);
+        }
         builder.append("]");
         return builder.toString();
     }
 
     /** Builder for {@link GnssMeasurementsEvent} */
     public static final class Builder {
+        private int mFlag;
         private GnssClock mClock;
         private List<GnssMeasurement> mMeasurements;
         private List<GnssAutomaticGainControl> mGnssAgcs;
@@ -237,10 +256,11 @@
          * {@link GnssMeasurementsEvent}.
          */
         public Builder(@NonNull GnssMeasurementsEvent event) {
+            mFlag = event.mFlag;
             mClock = event.getClock();
             mMeasurements = (List<GnssMeasurement>) event.getMeasurements();
             mGnssAgcs = (List<GnssAutomaticGainControl>) event.getGnssAutomaticGainControls();
-            mIsFullTracking = event.getIsFullTracking();
+            mIsFullTracking = event.isFullTracking();
         }
 
         /**
@@ -313,15 +333,26 @@
          * and frequency limits, etc.
          */
         @NonNull
-        public Builder setIsFullTracking(boolean isFullTracking) {
+        public Builder setFullTracking(boolean isFullTracking) {
+            mFlag |= HAS_FULL_TRACKING;
             mIsFullTracking = isFullTracking;
             return this;
         }
 
+        /**
+         * Clears the full tracking mode indicator.
+         */
+        @NonNull
+        public Builder clearFullTracking() {
+            mFlag &= ~HAS_FULL_TRACKING;
+            return this;
+        }
+
         /** Builds a {@link GnssMeasurementsEvent} instance as specified by this builder. */
         @NonNull
         public GnssMeasurementsEvent build() {
-            return new GnssMeasurementsEvent(mClock, mMeasurements, mGnssAgcs, mIsFullTracking);
+            return new GnssMeasurementsEvent(mFlag, mClock, mMeasurements, mGnssAgcs,
+                    mIsFullTracking);
         }
     }
 }
diff --git a/media/java/android/media/AudioDescriptor.java b/media/java/android/media/AudioDescriptor.java
index df648be..85a653c 100644
--- a/media/java/android/media/AudioDescriptor.java
+++ b/media/java/android/media/AudioDescriptor.java
@@ -41,11 +41,21 @@
      * The Extended Display Identification Data (EDID) standard for a short audio descriptor.
      */
     public static final int STANDARD_EDID = 1;
+    /**
+     * The standard for a Speaker Allocation Data Block (SADB).
+     */
+    public static final int STANDARD_SADB = 2;
+    /**
+     * The standard for a Vendor-Specific Audio Data Block (VSADB).
+     */
+    public static final int STANDARD_VSADB = 3;
 
     /** @hide */
     @IntDef({
             STANDARD_NONE,
             STANDARD_EDID,
+            STANDARD_SADB,
+            STANDARD_VSADB,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AudioDescriptorStandard {}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3ed2c4b..fdd6233 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -16,6 +16,10 @@
 
 package android.media;
 
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -34,6 +38,7 @@
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -100,6 +105,7 @@
 
     private Context mOriginalContext;
     private Context mApplicationContext;
+    private @Nullable VirtualDeviceManager mVirtualDeviceManager; // Lazy initialized.
     private long mVolumeKeyUpTime;
     private static final String TAG = "AudioManager";
     private static final boolean DEBUG = false;
@@ -858,6 +864,14 @@
         return sService;
     }
 
+    private VirtualDeviceManager getVirtualDeviceManager() {
+        if (mVirtualDeviceManager != null) {
+            return mVirtualDeviceManager;
+        }
+        mVirtualDeviceManager = getContext().getSystemService(VirtualDeviceManager.class);
+        return mVirtualDeviceManager;
+    }
+
     /**
      * Sends a simulated key event for a media button.
      * To simulate a key press, you must first send a KeyEvent built with a
@@ -3635,11 +3649,15 @@
      * whether sounds are heard or not.
      * @hide
      */
-    public void  playSoundEffect(@SystemSoundEffect int effectType, int userId) {
+    public void playSoundEffect(@SystemSoundEffect int effectType, int userId) {
         if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
             return;
         }
 
+        if (delegateSoundEffectToVdm(effectType)) {
+            return;
+        }
+
         final IAudioService service = getService();
         try {
             service.playSoundEffect(effectType, userId);
@@ -3657,11 +3675,15 @@
      * NOTE: This version is for applications that have their own
      * settings panel for enabling and controlling volume.
      */
-    public void  playSoundEffect(@SystemSoundEffect int effectType, float volume) {
+    public void playSoundEffect(@SystemSoundEffect int effectType, float volume) {
         if (effectType < 0 || effectType >= NUM_SOUND_EFFECTS) {
             return;
         }
 
+        if (delegateSoundEffectToVdm(effectType)) {
+            return;
+        }
+
         final IAudioService service = getService();
         try {
             service.playSoundEffectVolume(effectType, volume);
@@ -3671,6 +3693,28 @@
     }
 
     /**
+     * Checks whether this {@link AudioManager} instance is asociated with {@link VirtualDevice}
+     * configured with custom device policy for audio. If there is such device, request to play
+     * sound effect is forwarded to {@link VirtualDeviceManager}.
+     *
+     * @param effectType - The type of sound effect.
+     * @return true if the request was forwarded to {@link VirtualDeviceManager} instance,
+     * false otherwise.
+     */
+    private boolean delegateSoundEffectToVdm(@SystemSoundEffect int effectType) {
+        int deviceId = getContext().getDeviceId();
+        if (deviceId != DEVICE_ID_DEFAULT) {
+            VirtualDeviceManager vdm = getVirtualDeviceManager();
+            if (vdm != null && vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+                    != DEVICE_POLICY_DEFAULT) {
+                vdm.playSoundEffect(deviceId, effectType);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      *  Load Sound effects.
      *  This method must be called when sound effects are enabled.
      */
@@ -6237,6 +6281,49 @@
     }
 
     /**
+     * @hide
+     * Lower media volume to RS1
+     */
+    public void lowerVolumeToRs1() {
+        try {
+            getService().lowerVolumeToRs1(mApplicationContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Sound dose warning at every 100% of dose during integration window
+     */
+    public static final int CSD_WARNING_DOSE_REACHED_1X = 1;
+    /**
+     * @hide
+     * Sound dose warning when 500% of dose is reached during integration window
+     */
+    public static final int CSD_WARNING_DOSE_REPEATED_5X = 2;
+    /**
+     * @hide
+     * Sound dose warning after a momentary exposure event
+     */
+    public static final int CSD_WARNING_MOMENTARY_EXPOSURE = 3;
+    /**
+     * @hide
+     * Sound dose warning at every 100% of dose during integration window
+     */
+    public static final int CSD_WARNING_ACCUMULATION_START = 4;
+
+    /** @hide */
+    @IntDef(flag = false, value = {
+            CSD_WARNING_DOSE_REACHED_1X,
+            CSD_WARNING_DOSE_REPEATED_5X,
+            CSD_WARNING_MOMENTARY_EXPOSURE,
+            CSD_WARNING_ACCUMULATION_START }
+    )
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CsdWarning {}
+
+    /**
      * Only useful for volume controllers.
      * @hide
      */
@@ -8730,6 +8817,55 @@
         }
     }
 
+    /**
+     * Requests if the implementation supports controlling the latency modes
+     * over the Bluetooth A2DP or LE Audio links.
+     *
+     * @return true if supported, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean supportsBluetoothVariableLatency() {
+        try {
+            return getService().supportsBluetoothVariableLatency();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enables or disables the variable Bluetooth latency control mechanism in the
+     * audio framework and the audio HAL. This does not apply to the latency mode control
+     * on the spatializer output as this is a built-in feature.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setBluetoothVariableLatencyEnabled(boolean enabled) {
+        try {
+            getService().setBluetoothVariableLatencyEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Indicates if the variable Bluetooth latency control mechanism is enabled or disabled.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isBluetoothVariableLatencyEnabled() {
+        try {
+            return getService().isBluetoothVariableLatencyEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     //====================================================================
     // Mute await connection
 
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 59a0f7b..f64e5cc 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -32,6 +32,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -178,6 +180,11 @@
      * Used to update the mute state of a player through its port id
      */
     public static final int PLAYER_UPDATE_MUTED = 7;
+    /**
+     * @hide
+     * Used to update the spatialization status and format of a player through its port id
+     */
+    public static final int PLAYER_UPDATE_FORMAT = 8;
 
     /** @hide */
     @IntDef({
@@ -190,6 +197,7 @@
         PLAYER_UPDATE_DEVICE_ID,
         PLAYER_UPDATE_PORT_ID,
         PLAYER_UPDATE_MUTED,
+        PLAYER_UPDATE_FORMAT,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerState {}
@@ -206,6 +214,7 @@
             case PLAYER_UPDATE_DEVICE_ID: return "PLAYER_UPDATE_DEVICE_ID";
             case PLAYER_UPDATE_PORT_ID: return "PLAYER_UPDATE_PORT_ID";
             case PLAYER_UPDATE_MUTED: return "PLAYER_UPDATE_MUTED";
+            case PLAYER_UPDATE_FORMAT: return "PLAYER_UPDATE_FORMAT";
             default:
                 return "invalid state " + state;
         }
@@ -213,6 +222,27 @@
 
     /**
      * @hide
+     * Used to update the spatialization status of a player through its port ID. Must be kept in
+     * sync with frameworks/native/include/audiomanager/AudioManager.h
+     */
+    public static final String EXTRA_PLAYER_EVENT_SPATIALIZED =
+            "android.media.extra.PLAYER_EVENT_SPATIALIZED";
+    /**
+     * @hide
+     * Used to update the sample rate of a player through its port ID. Must be kept in sync with
+     * frameworks/native/include/audiomanager/AudioManager.h
+     */
+    public static final String EXTRA_PLAYER_EVENT_SAMPLE_RATE =
+            "android.media.extra.PLAYER_EVENT_SAMPLE_RATE";
+    /**
+     * @hide
+     * Used to update the channel mask of a player through its port ID. Must be kept in sync with
+     * frameworks/native/include/audiomanager/AudioManager.h
+     */
+    public static final String EXTRA_PLAYER_EVENT_CHANNEL_MASK =
+            "android.media.extra.PLAYER_EVENT_CHANNEL_MASK";
+    /**
+     * @hide
      * Used to update the mute state of a player through its port ID. Must be kept in sync with
      * frameworks/native/include/audiomanager/AudioManager.h
      */
@@ -284,10 +314,16 @@
     private int mPlayerState;
     private AudioAttributes mPlayerAttr; // never null
 
+    // lock for updateable properties
+    private final Object mUpdateablePropLock = new Object();
+
+    @GuardedBy("mUpdateablePropLock")
     private int mDeviceId;
-
+    @GuardedBy("mUpdateablePropLock")
     private int mSessionId;
-
+    @GuardedBy("mUpdateablePropLock")
+    private @NonNull FormatInfo mFormatInfo;
+    @GuardedBy("mUpdateablePropLock")
     @PlayerMuteEvent private int mMutedState;
 
     /**
@@ -320,6 +356,7 @@
             mIPlayerShell = null;
         }
         mSessionId = pic.mSessionId;
+        mFormatInfo = FormatInfo.DEFAULT;
     }
 
     /**
@@ -333,13 +370,23 @@
         }
     }
 
+    // sets the fields that are updateable and require synchronization
+    private void setUpdateableFields(int deviceId, int sessionId, int mutedState, FormatInfo format)
+    {
+        synchronized (mUpdateablePropLock) {
+            mDeviceId = deviceId;
+            mSessionId = sessionId;
+            mMutedState = mutedState;
+            mFormatInfo = format;
+        }
+    }
     // Note that this method is called server side, so no "privileged" information is ever sent
     // to a client that is not supposed to have access to it.
     /**
      * @hide
      * Creates a copy of the playback configuration that is stripped of any data enabling
      * identification of which application it is associated with ("anonymized").
-     * @param toSanitize
+     * @param in the instance to copy from
      */
     public static AudioPlaybackConfiguration anonymizedCopy(AudioPlaybackConfiguration in) {
         final AudioPlaybackConfiguration anonymCopy = new AudioPlaybackConfiguration(in.mPlayerIId);
@@ -357,14 +404,16 @@
             builder.setUsage(in.mPlayerAttr.getUsage());
         }
         anonymCopy.mPlayerAttr = builder.build();
-        anonymCopy.mDeviceId = in.mDeviceId;
         // anonymized data
-        anonymCopy.mMutedState = 0;
         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
         anonymCopy.mIPlayerShell = null;
-        anonymCopy.mSessionId = AudioSystem.AUDIO_SESSION_ALLOCATE;
+        anonymCopy.setUpdateableFields(
+                /*deviceId*/ PLAYER_DEVICEID_INVALID,
+                /*sessionId*/ AudioSystem.AUDIO_SESSION_ALLOCATE,
+                /*mutedState*/ 0,
+                FormatInfo.DEFAULT);
         return anonymCopy;
     }
 
@@ -401,10 +450,14 @@
      * @return the audio playback device or null if the device is not available at the time of query
      */
     public @Nullable AudioDeviceInfo getAudioDeviceInfo() {
-        if (mDeviceId == PLAYER_DEVICEID_INVALID) {
+        final int deviceId;
+        synchronized (mUpdateablePropLock) {
+            deviceId = mDeviceId;
+        }
+        if (deviceId == PLAYER_DEVICEID_INVALID) {
             return null;
         }
-        return AudioManager.getDeviceForPortId(mDeviceId, AudioManager.GET_DEVICES_OUTPUTS);
+        return AudioManager.getDeviceForPortId(deviceId, AudioManager.GET_DEVICES_OUTPUTS);
     }
 
     /**
@@ -415,7 +468,9 @@
      */
     @SystemApi
     public @IntRange(from = 0) int getSessionId() {
-        return mSessionId;
+        synchronized (mUpdateablePropLock) {
+            return mSessionId;
+        }
     }
 
     /**
@@ -428,7 +483,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     public boolean isMuted() {
-        return mMutedState != 0;
+        synchronized (mUpdateablePropLock) {
+            return mMutedState != 0;
+        }
     }
 
     /**
@@ -440,7 +497,9 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
     @PlayerMuteEvent public int getMutedBy() {
-        return mMutedState;
+        synchronized (mUpdateablePropLock) {
+            return mMutedState;
+        }
     }
 
     /**
@@ -500,6 +559,43 @@
 
     /**
      * @hide
+     * Return whether this player's output is spatialized
+     * @return true if spatialized, false if not or playback hasn't started
+     */
+    @SystemApi
+    public boolean isSpatialized() {
+        synchronized (mUpdateablePropLock) {
+            return mFormatInfo.mIsSpatialized;
+        }
+    }
+
+    /**
+     * @hide
+     * Return the sample rate in Hz of the content being played.
+     * @return the sample rate in Hertz, or 0 if playback hasn't started
+     */
+    @SystemApi
+    public @IntRange(from = 0) int getSampleRate() {
+        synchronized (mUpdateablePropLock) {
+            return mFormatInfo.mSampleRate;
+        }
+    }
+
+    /**
+     * @hide
+     * Return the player's channel mask
+     * @return the channel mask, or 0 if playback hasn't started. See {@link AudioFormat} and
+     *     the definitions for the <code>CHANNEL_OUT_*</code> values used for the mask's bitfield
+     */
+    @SystemApi
+    public int getChannelMask() {
+        synchronized (mUpdateablePropLock) {
+            return (AudioFormat.convertNativeChannelMaskToOutMask(mFormatInfo.mNativeChannelMask));
+        }
+    }
+
+    /**
+     * @hide
      * @return the IPlayer interface for the associated player
      */
     IPlayer getIPlayer() {
@@ -527,9 +623,11 @@
      * @param sessionId the audio session ID
      */
     public boolean handleSessionIdEvent(int sessionId) {
-        final boolean changed = sessionId != mSessionId;
-        mSessionId = sessionId;
-        return changed;
+        synchronized (mUpdateablePropLock) {
+            final boolean changed = sessionId != mSessionId;
+            mSessionId = sessionId;
+            return changed;
+        }
     }
 
     /**
@@ -539,9 +637,25 @@
      * @return true if the state changed, false otherwise
      */
     public boolean handleMutedEvent(@PlayerMuteEvent int mutedState) {
-        final boolean changed = mMutedState != mutedState;
-        mMutedState = mutedState;
-        return changed;
+        synchronized (mUpdateablePropLock) {
+            final boolean changed = mMutedState != mutedState;
+            mMutedState = mutedState;
+            return changed;
+        }
+    }
+
+    /**
+     * @hide
+     * Handle a change of playback format
+     * @param fi the latest format information
+     * @return true if the format changed, false otherwise
+     */
+    public boolean handleFormatEvent(@NonNull FormatInfo fi) {
+        synchronized (mUpdateablePropLock) {
+            final boolean changed = !mFormatInfo.equals(fi);
+            mFormatInfo = fi;
+            return changed;
+        }
     }
 
     /**
@@ -558,7 +672,7 @@
      */
     public boolean handleStateEvent(int event, int deviceId) {
         boolean changed = false;
-        synchronized (this) {
+        synchronized (mUpdateablePropLock) {
 
             // Do not update if it is only device id update
             if (event != PLAYER_UPDATE_DEVICE_ID) {
@@ -647,8 +761,10 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid, mClientPid,
-                mSessionId);
+        synchronized (mUpdateablePropLock) {
+            return Objects.hash(mPlayerIId, mDeviceId, mMutedState, mPlayerType, mClientUid,
+                    mClientPid, mSessionId);
+        }
     }
 
     @Override
@@ -658,20 +774,23 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeInt(mPlayerIId);
-        dest.writeInt(mDeviceId);
-        dest.writeInt(mMutedState);
-        dest.writeInt(mPlayerType);
-        dest.writeInt(mClientUid);
-        dest.writeInt(mClientPid);
-        dest.writeInt(mPlayerState);
-        mPlayerAttr.writeToParcel(dest, 0);
-        final IPlayerShell ips;
-        synchronized (this) {
-            ips = mIPlayerShell;
+        synchronized (mUpdateablePropLock) {
+            dest.writeInt(mPlayerIId);
+            dest.writeInt(mDeviceId);
+            dest.writeInt(mMutedState);
+            dest.writeInt(mPlayerType);
+            dest.writeInt(mClientUid);
+            dest.writeInt(mClientPid);
+            dest.writeInt(mPlayerState);
+            mPlayerAttr.writeToParcel(dest, 0);
+            final IPlayerShell ips;
+            synchronized (this) {
+                ips = mIPlayerShell;
+            }
+            dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
+            dest.writeInt(mSessionId);
+            mFormatInfo.writeToParcel(dest, 0);
         }
-        dest.writeStrongInterface(ips == null ? null : ips.getIPlayer());
-        dest.writeInt(mSessionId);
     }
 
     private AudioPlaybackConfiguration(Parcel in) {
@@ -686,6 +805,7 @@
         final IPlayer p = IPlayer.Stub.asInterface(in.readStrongBinder());
         mIPlayerShell = (p == null) ? null : new IPlayerShell(null, p);
         mSessionId = in.readInt();
+        mFormatInfo = FormatInfo.CREATOR.createFromParcel(in);
     }
 
     @Override
@@ -707,35 +827,39 @@
     @Override
     public String toString() {
         StringBuilder apcToString = new StringBuilder();
-        apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append(
-                " deviceId:").append(mDeviceId).append(" type:").append(
-                toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append(mClientUid).append(
-                "/").append(mClientPid).append(" state:").append(
-                toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
-                " sessionId:").append(mSessionId).append(" mutedState:");
-        if (mMutedState == 0) {
-            apcToString.append("none ");
-        } else {
-            if ((mMutedState & MUTED_BY_MASTER) != 0) {
-                apcToString.append("master ");
+        synchronized (mUpdateablePropLock) {
+            apcToString.append("AudioPlaybackConfiguration piid:").append(mPlayerIId).append(
+                    " deviceId:").append(mDeviceId).append(" type:").append(
+                    toLogFriendlyPlayerType(mPlayerType)).append(" u/pid:").append(
+                    mClientUid).append(
+                    "/").append(mClientPid).append(" state:").append(
+                    toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(
+                    mPlayerAttr).append(
+                    " sessionId:").append(mSessionId).append(" mutedState:");
+            if (mMutedState == 0) {
+                apcToString.append("none ");
+            } else {
+                if ((mMutedState & MUTED_BY_MASTER) != 0) {
+                    apcToString.append("master ");
+                }
+                if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
+                    apcToString.append("streamVolume ");
+                }
+                if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
+                    apcToString.append("streamMute ");
+                }
+                if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+                    apcToString.append("appOps ");
+                }
+                if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
+                    apcToString.append("clientVolume ");
+                }
+                if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
+                    apcToString.append("volumeShaper ");
+                }
             }
-            if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
-                apcToString.append("streamVolume ");
-            }
-            if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
-                apcToString.append("streamMute ");
-            }
-            if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
-                apcToString.append("appOps ");
-            }
-            if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
-                apcToString.append("clientVolume ");
-            }
-            if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
-                apcToString.append("volumeShaper ");
-            }
+            apcToString.append(" ").append(mFormatInfo);
         }
-
         return apcToString.toString();
     }
 
@@ -788,6 +912,79 @@
     }
 
     //=====================================================================
+
+    /**
+     * @hide
+     * Class to store sample rate, channel mask, and spatialization status
+     */
+    public static final class FormatInfo implements Parcelable {
+        static final FormatInfo DEFAULT = new FormatInfo(
+                /*spatialized*/ false, /*channel mask*/ 0, /*sample rate*/ 0);
+        final boolean mIsSpatialized;
+        final int mNativeChannelMask;
+        final int mSampleRate;
+
+        public FormatInfo(boolean isSpatialized, int nativeChannelMask, int sampleRate) {
+            mIsSpatialized = isSpatialized;
+            mNativeChannelMask = nativeChannelMask;
+            mSampleRate = sampleRate;
+        }
+
+        @Override
+        public String toString() {
+            return "FormatInfo{"
+                    + "isSpatialized=" + mIsSpatialized
+                    + ", channelMask=0x" + Integer.toHexString(mNativeChannelMask)
+                    + ", sampleRate=" + mSampleRate
+                    + '}';
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof FormatInfo)) return false;
+            FormatInfo that = (FormatInfo) o;
+            return mIsSpatialized == that.mIsSpatialized
+                    && mNativeChannelMask == that.mNativeChannelMask
+                    && mSampleRate == that.mSampleRate;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mIsSpatialized, mNativeChannelMask, mSampleRate);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+            dest.writeBoolean(mIsSpatialized);
+            dest.writeInt(mNativeChannelMask);
+            dest.writeInt(mSampleRate);
+        }
+
+        private FormatInfo(Parcel in) {
+            this(
+                    in.readBoolean(), // isSpatialized
+                    in.readInt(),     // channelMask
+                    in.readInt()      // sampleRate
+            );
+        }
+
+        public static final @NonNull Parcelable.Creator<FormatInfo> CREATOR =
+                new Parcelable.Creator<FormatInfo>() {
+            public FormatInfo createFromParcel(Parcel p) {
+                return new FormatInfo(p);
+            }
+            public FormatInfo[] newArray(int size) {
+                return new FormatInfo[size];
+            }
+        };
+    }
+    //=====================================================================
     // Utilities
 
     /** @hide */
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 3e0d657..a743586 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2476,4 +2476,30 @@
      */
     public static native int clearPreferredMixerAttributes(
             @NonNull AudioAttributes attributes, int portId, int uid);
+
+
+    /**
+     * Requests if the implementation supports controlling the latency modes
+     * over the Bluetooth A2DP or LE Audio links.
+     *
+     * @return true if supported, false otherwise
+     *
+     * @hide
+     */
+    public static native boolean supportsBluetoothVariableLatency();
+
+    /**
+     * Enables or disables the variable Bluetooth latency control mechanism in the
+     * audio framework and the audio HAL. This does not apply to the latency mode control
+     * on the spatializer output as this is a built-in feature.
+     *
+     * @hide
+     */
+    public static native int setBluetoothVariableLatencyEnabled(boolean enabled);
+
+    /**
+     * Indicates if the variable Bluetooth latency control mechanism is enabled or disabled.
+     * @hide
+     */
+    public static native boolean isBluetoothVariableLatencyEnabled();
 }
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 5502db2..0f63cc4 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -261,6 +261,8 @@
 
     void disableSafeMediaVolume(String callingPackage);
 
+    void lowerVolumeToRs1(String callingPackage);
+
     int setHdmiSystemAudioSupported(boolean on);
 
     boolean isHdmiSystemAudioSupported();
@@ -585,4 +587,16 @@
             IPreferredMixerAttributesDispatcher dispatcher);
     oneway void unregisterPreferredMixerAttributesDispatcher(
             IPreferredMixerAttributesDispatcher dispatcher);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    boolean supportsBluetoothVariableLatency();
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    void setBluetoothVariableLatencyEnabled(boolean enabled);
+
+    @EnforcePermission("MODIFY_AUDIO_ROUTING")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)")
+    boolean isBluetoothVariableLatencyEnabled();
 }
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index 7f37265..9cb2eba 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -39,4 +39,19 @@
      *     {@link VolumePolicy#A11Y_MODE_INDEPENDENT_A11Y_VOLUME}
      */
     void setA11yMode(int mode);
+
+    /**
+     * Display a sound-dose related warning.
+     * This method will never be called if the CSD (Computed Sound Dose) feature is
+     * not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
+     * the feature.
+     * @param warning the type of warning to display, values are one of
+     *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
+     *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
+     *        {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
+     *        {@link android.media.AudioManager#CSD_WARNING_ACCUMULATION_START}.
+     * @param displayDurationMs the time expressed in milliseconds after which the dialog will be
+     *        automatically dismissed, or -1 if there is no automatic timeout.
+     */
+    void displayCsdWarning(int warning, int displayDurationMs);
 }
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index 6b42058..6a5b290 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -88,6 +88,9 @@
      * Returns true if the application would like media route listing to use the system's ordering
      * strategy, or false if the application would like route listing to respect the ordering
      * obtained from {@link #getItems()}.
+     *
+     * <p>The system's ordering strategy is implementation-dependent, but may take into account each
+     * route's recency or frequency of use in order to rank them.
      */
     public boolean getUseSystemOrdering() {
         return mUseSystemOrdering;
diff --git a/media/java/android/media/audio/common/AidlConversion.java b/media/java/android/media/audio/common/AidlConversion.java
index 490809c..65fd51b 100644
--- a/media/java/android/media/audio/common/AidlConversion.java
+++ b/media/java/android/media/audio/common/AidlConversion.java
@@ -585,6 +585,10 @@
         switch (standard) {
             case AudioDescriptor.STANDARD_EDID:
                 return AudioStandard.EDID;
+            case AudioDescriptor.STANDARD_SADB:
+                return AudioStandard.SADB;
+            case AudioDescriptor.STANDARD_VSADB:
+                return AudioStandard.VSADB;
             case AudioDescriptor.STANDARD_NONE:
             default:
                 return AudioStandard.NONE;
@@ -599,6 +603,10 @@
         switch (standard) {
             case AudioStandard.EDID:
                 return AudioDescriptor.STANDARD_EDID;
+            case AudioStandard.SADB:
+                return AudioDescriptor.STANDARD_SADB;
+            case AudioStandard.VSADB:
+                return AudioDescriptor.STANDARD_VSADB;
             case AudioStandard.NONE:
             default:
                 return AudioDescriptor.STANDARD_NONE;
diff --git a/media/java/android/media/projection/IMediaProjectionCallback.aidl b/media/java/android/media/projection/IMediaProjectionCallback.aidl
index 2c8de2e..147d74c 100644
--- a/media/java/android/media/projection/IMediaProjectionCallback.aidl
+++ b/media/java/android/media/projection/IMediaProjectionCallback.aidl
@@ -20,4 +20,5 @@
 oneway interface IMediaProjectionCallback {
     void onStop();
     void onCapturedContentResize(int width, int height);
+    void onCapturedContentVisibilityChanged(boolean isVisible);
 }
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index a63d02b..99d1f8d 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -48,7 +48,11 @@
     void notifyActiveProjectionCapturedContentResized(int width, int height);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
-            + ".permission.MANAGE_MEDIA_PROJECTION)")
+                + ".permission.MANAGE_MEDIA_PROJECTION)")
+    void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
+
+    @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
+                + ".permission.MANAGE_MEDIA_PROJECTION)")
     void addCallback(IMediaProjectionWatcherCallback callback);
 
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 3dfff1f..985ac3c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -66,13 +66,20 @@
         }
     }
 
-    /** Register a listener to receive notifications about when the {@link
-     * MediaProjection} changes state.
+    /**
+     * Register a listener to receive notifications about when the {@link MediaProjection} or
+     * captured content changes state.
+     * <p>
+     * The callback should be registered before invoking
+     * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+     * Handler)}
+     * to ensure that any notifications on the callback are not missed.
+     * </p>
      *
      * @param callback The callback to call.
-     * @param handler The handler on which the callback should be invoked, or
-     * null if the callback should be invoked on the calling thread's looper.
-     *
+     * @param handler  The handler on which the callback should be invoked, or
+     *                 null if the callback should be invoked on the calling thread's looper.
+     * @throws IllegalArgumentException If the given callback is null.
      * @see #unregisterCallback
      */
     public void registerCallback(Callback callback, Handler handler) {
@@ -85,10 +92,11 @@
         mCallbacks.put(callback, new CallbackRecord(callback, handler));
     }
 
-    /** Unregister a MediaProjection listener.
+    /**
+     * Unregister a {@link MediaProjection} listener.
      *
      * @param callback The callback to unregister.
-     *
+     * @throws IllegalArgumentException If the given callback is null.
      * @see #registerCallback
      */
     public void unregisterCallback(Callback callback) {
@@ -283,6 +291,34 @@
          * }</pre>
          */
         public void onCapturedContentResize(int width, int height) { }
+
+        /**
+         * Indicates the visibility of the captured region has changed. Called immediately after
+         * capture begins with the initial visibility state, and when visibility changes. Provides
+         * the app with accurate state for presenting its own UI. The application can take advantage
+         * of this by showing or hiding the captured content, based on if the captured region is
+         * currently visible to the user.
+         * <p>
+         * For example, if the user elected to capture a single app (from the activity shown from
+         * {@link MediaProjectionManager#createScreenCaptureIntent()}), the callback may be
+         * triggered for the following reasons:
+         * <ul>
+         *     <li>
+         *         The captured region may become visible ({@code isVisible} with value
+         *         {@code true}), because the captured app is at least partially visible. This may
+         *         happen if the captured app was previously covered by another app. The other app
+         *         moves to show at least some portion of the captured app.
+         *     </li>
+         *     <li>
+         *         The captured region may become invisible ({@code isVisible} with value
+         *         {@code false}) if it is entirely hidden. This may happen if the captured app is
+         *         entirely covered by another app, or the user navigates away from the captured
+         *         app.
+         *     </li>
+         * </ul>
+         * </p>
+         */
+        public void onCapturedContentVisibilityChanged(boolean isVisible) { }
     }
 
     private final class MediaProjectionCallback extends IMediaProjectionCallback.Stub {
@@ -299,6 +335,13 @@
                 cbr.onCapturedContentResize(width, height);
             }
         }
+
+        @Override
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            for (CallbackRecord cbr : mCallbacks.values()) {
+                cbr.onCapturedContentVisibilityChanged(isVisible);
+            }
+        }
     }
 
     private final static class CallbackRecord {
@@ -322,5 +365,9 @@
         public void onCapturedContentResize(int width, int height) {
             mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
         }
+
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
+        }
     }
 }
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index a4215e68..d60dfd9 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -74,8 +74,16 @@
      * Returns an {@link Intent} that <b>must</b> be passed to
      * {@link Activity#startActivityForResult(Intent, int)} (or similar) in order to start screen
      * capture. The activity will prompt the user whether to allow screen capture.  The result of
-     * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)})
+     * this activity (received by overriding {@link Activity#onActivityResult(int, int, Intent)}
      * should be passed to {@link #getMediaProjection(int, Intent)}.
+     * <p>
+     * Identical to calling {@link #createScreenCaptureIntent(MediaProjectionConfig)} with
+     * a {@link MediaProjectionConfig#createConfigForUserChoice()}.
+     * </p>
+     * <p>
+     * Should be used instead of {@link #createScreenCaptureIntent(MediaProjectionConfig)} when the
+     * calling app does not want to customize the activity shown to the user.
+     * </p>
      */
     @NonNull
     public Intent createScreenCaptureIntent() {
@@ -99,20 +107,25 @@
      *
      * <p>
      * If {@link MediaProjectionConfig} was created from:
-     * <li>
-     *     <ul>
+     * <ul>
+     *     <li>
      *         {@link MediaProjectionConfig#createConfigForDisplay(int)}, then creates an
      *         {@link Intent} for capturing this particular display. The activity limits the user's
      *         choice to just the display specified.
-     *     </ul>
-     *     <ul>
+     *     </li>
+     *     <li>
      *         {@link MediaProjectionConfig#createConfigForUserChoice()}, then creates an
      *         {@link Intent} for deferring which region to capture to the user. This gives the
      *         user the same behaviour as calling {@link #createScreenCaptureIntent()}. The
      *         activity gives the user the choice between
      *         {@link android.view.Display#DEFAULT_DISPLAY}, or a different region.
-     *     </ul>
-     * </li>
+     *     </li>
+     * </ul>
+     * </p>
+     * <p>
+     * Should be used instead of {@link #createScreenCaptureIntent()} when the calling app wants to
+     * customize the activity shown to the user.
+     * </p>
      *
      * @param config Customization for the {@link MediaProjection} that this {@link Intent} requests
      *               the user's consent for.
diff --git a/media/java/android/media/tv/AdBuffer.aidl b/media/java/android/media/tv/AdBuffer.aidl
new file mode 100644
index 0000000..b1e2d3e
--- /dev/null
+++ b/media/java/android/media/tv/AdBuffer.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.tv;
+
+parcelable AdBuffer;
diff --git a/media/java/android/media/tv/AdBuffer.java b/media/java/android/media/tv/AdBuffer.java
new file mode 100644
index 0000000..ed44508
--- /dev/null
+++ b/media/java/android/media/tv/AdBuffer.java
@@ -0,0 +1,163 @@
+/*
+ * 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.tv;
+
+import android.annotation.NonNull;
+import android.media.MediaCodec.BufferFlag;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SharedMemory;
+
+/**
+ * Buffer for advertisement data.
+ * @hide
+ */
+public class AdBuffer implements Parcelable {
+    private final int mId;
+    @NonNull
+    private final String mMimeType;
+    @NonNull
+    private final SharedMemory mBuffer;
+    private final int mOffset;
+    private final int mLength;
+    private final long mPresentationTimeUs;
+    private final int mFlags;
+
+    public AdBuffer(
+            int id,
+            @NonNull String mimeType,
+            @NonNull SharedMemory buffer,
+            int offset,
+            int length,
+            long presentationTimeUs,
+            @BufferFlag int flags) {
+        this.mId = id;
+        this.mMimeType = mimeType;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mimeType);
+        this.mBuffer = buffer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, buffer);
+        this.mOffset = offset;
+        this.mLength = length;
+        this.mPresentationTimeUs = presentationTimeUs;
+        this.mFlags = flags;
+    }
+
+    /**
+     * Gets corresponding AD request ID.
+     */
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Gets the mime type of the data.
+     */
+    @NonNull
+    public String getMimeType() {
+        return mMimeType;
+    }
+
+    /**
+     * Gets the shared memory which stores the data.
+     */
+    @NonNull
+    public SharedMemory getSharedMemory() {
+        return mBuffer;
+    }
+
+    /**
+     * Gets the offset of the buffer.
+     */
+    public int getOffset() {
+        return mOffset;
+    }
+
+    /**
+     * Gets the data length.
+     */
+    public int getLength() {
+        return mLength;
+    }
+
+    /**
+     * Gets the presentation time in microseconds.
+     */
+    public long getPresentationTimeUs() {
+        return mPresentationTimeUs;
+    }
+
+    /**
+     * Gets the flags.
+     */
+    @BufferFlag
+    public int getFlags() {
+        return mFlags;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
+        dest.writeInt(mId);
+        dest.writeString(mMimeType);
+        dest.writeTypedObject(mBuffer, flags);
+        dest.writeInt(mOffset);
+        dest.writeInt(mLength);
+        dest.writeLong(mPresentationTimeUs);
+        dest.writeInt(mFlags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    private AdBuffer(@NonNull Parcel in) {
+        int id = in.readInt();
+        String mimeType = in.readString();
+        SharedMemory buffer = (SharedMemory) in.readTypedObject(SharedMemory.CREATOR);
+        int offset = in.readInt();
+        int length = in.readInt();
+        long presentationTimeUs = in.readLong();
+        int flags = in.readInt();
+
+        this.mId = id;
+        this.mMimeType = mimeType;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mMimeType);
+        this.mBuffer = buffer;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mBuffer);
+        this.mOffset = offset;
+        this.mLength = length;
+        this.mPresentationTimeUs = presentationTimeUs;
+        this.mFlags = flags;
+    }
+
+    public static final @NonNull Parcelable.Creator<AdBuffer> CREATOR =
+            new Parcelable.Creator<AdBuffer>() {
+                @Override
+                public AdBuffer[] newArray(int size) {
+                    return new AdBuffer[size];
+                }
+
+                @Override
+                public AdBuffer createFromParcel(Parcel in) {
+                    return new AdBuffer(in);
+            }
+    };
+}
diff --git a/media/java/android/media/tv/AdRequest.java b/media/java/android/media/tv/AdRequest.java
index f2fb93d..60dfc5e 100644
--- a/media/java/android/media/tv/AdRequest.java
+++ b/media/java/android/media/tv/AdRequest.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
@@ -69,10 +70,25 @@
     private final long mEchoInterval;
     private final String mMediaFileType;
     private final Bundle mMetadata;
+    private final Uri mUri;
 
     public AdRequest(int id, @RequestType int requestType,
             @Nullable ParcelFileDescriptor fileDescriptor, long startTime, long stopTime,
             long echoInterval, @Nullable String mediaFileType, @NonNull Bundle metadata) {
+        this(id, requestType, fileDescriptor, null, startTime, stopTime, echoInterval,
+                mediaFileType, metadata);
+    }
+
+    /** @hide */
+    public AdRequest(int id, @RequestType int requestType, @Nullable Uri uri, long startTime,
+            long stopTime, long echoInterval, @NonNull Bundle metadata) {
+        this(id, requestType, null, uri, startTime, stopTime, echoInterval, null, metadata);
+    }
+
+    private AdRequest(int id, @RequestType int requestType,
+            @Nullable ParcelFileDescriptor fileDescriptor, @Nullable Uri uri, long startTime,
+            long stopTime, long echoInterval, @Nullable String mediaFileType,
+            @NonNull Bundle metadata) {
         mId = id;
         mRequestType = requestType;
         mFileDescriptor = fileDescriptor;
@@ -81,15 +97,23 @@
         mEchoInterval = echoInterval;
         mMediaFileType = mediaFileType;
         mMetadata = metadata;
+        mUri = uri;
     }
 
     private AdRequest(Parcel source) {
         mId = source.readInt();
         mRequestType = source.readInt();
-        if (source.readInt() != 0) {
+        int readInt = source.readInt();
+        if (readInt == 1) {
             mFileDescriptor = ParcelFileDescriptor.CREATOR.createFromParcel(source);
+            mUri = null;
+        } else if (readInt == 2) {
+            String stringUri = source.readString();
+            mUri = stringUri == null ? null : Uri.parse(stringUri);
+            mFileDescriptor = null;
         } else {
             mFileDescriptor = null;
+            mUri = null;
         }
         mStartTime = source.readLong();
         mStopTime = source.readLong();
@@ -117,7 +141,7 @@
      * Gets the file descriptor of the AD media.
      *
      * @return The file descriptor of the AD media. Can be {@code null} for
-     *         {@link #REQUEST_TYPE_STOP}
+     *         {@link #REQUEST_TYPE_STOP} or a URI is used.
      */
     @Nullable
     public ParcelFileDescriptor getFileDescriptor() {
@@ -125,6 +149,18 @@
     }
 
     /**
+     * Gets the URI of the AD media.
+     *
+     * @return The URI of the AD media. Can be {@code null} for {@link #REQUEST_TYPE_STOP} or a file
+     *         descriptor is used.
+     * @hide
+     */
+    @Nullable
+    public Uri getUri() {
+        return mUri;
+    }
+
+    /**
      * Gets the start time of the AD media in milliseconds.
      * <p>0 means start immediately
      */
@@ -189,6 +225,10 @@
         if (mFileDescriptor != null) {
             dest.writeInt(1);
             mFileDescriptor.writeToParcel(dest, flags);
+        } else if (mUri != null) {
+            dest.writeInt(2);
+            String stringUri = mUri.toString();
+            dest.writeString(stringUri);
         } else {
             dest.writeInt(0);
         }
diff --git a/media/java/android/media/tv/AdResponse.java b/media/java/android/media/tv/AdResponse.java
index 08c66ab..a15e8c1 100644
--- a/media/java/android/media/tv/AdResponse.java
+++ b/media/java/android/media/tv/AdResponse.java
@@ -34,7 +34,8 @@
             RESPONSE_TYPE_PLAYING,
             RESPONSE_TYPE_FINISHED,
             RESPONSE_TYPE_STOPPED,
-            RESPONSE_TYPE_ERROR
+            RESPONSE_TYPE_ERROR,
+            RESPONSE_TYPE_BUFFERING
     })
     public @interface ResponseType {}
 
@@ -42,6 +43,8 @@
     public static final int RESPONSE_TYPE_FINISHED = 2;
     public static final int RESPONSE_TYPE_STOPPED = 3;
     public static final int RESPONSE_TYPE_ERROR = 4;
+    /** @hide */
+    public static final int RESPONSE_TYPE_BUFFERING = 5;
 
     public static final @NonNull Parcelable.Creator<AdResponse> CREATOR =
             new Parcelable.Creator<AdResponse>() {
diff --git a/media/java/android/media/tv/ITvInputClient.aidl b/media/java/android/media/tv/ITvInputClient.aidl
index 49148ce..ed2fd20 100644
--- a/media/java/android/media/tv/ITvInputClient.aidl
+++ b/media/java/android/media/tv/ITvInputClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.content.ComponentName;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
@@ -59,4 +60,5 @@
 
     // For ad response
     void onAdResponse(in AdResponse response, int seq);
+    void onAdBufferConsumed(in AdBuffer buffer, int seq);
 }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 2a33ee6..f7c1e3c 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -20,6 +20,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.DvbDeviceInfo;
@@ -110,6 +111,7 @@
 
     // For ad request
     void requestAd(in IBinder sessionToken, in AdRequest request, int userId);
+    void notifyAdBuffer(in IBinder sessionToken, in AdBuffer buffer, int userId);
 
     // For TV input hardware binding
     List<TvInputHardwareInfo> getHardwareList();
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 9820034..326b98d 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -18,6 +18,7 @@
 
 import android.graphics.Rect;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.TvTrackInfo;
@@ -71,4 +72,5 @@
 
     // For ad request
     void requestAd(in AdRequest request);
+    void notifyAdBuffer(in AdBuffer buffer);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionCallback.aidl b/media/java/android/media/tv/ITvInputSessionCallback.aidl
index 9dfdb78..b2a8d1c 100644
--- a/media/java/android/media/tv/ITvInputSessionCallback.aidl
+++ b/media/java/android/media/tv/ITvInputSessionCallback.aidl
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
 import android.media.tv.BroadcastInfoResponse;
@@ -56,4 +57,5 @@
 
     // For ad response
     void onAdResponse(in AdResponse response);
+    void onAdBufferConsumed(in AdBuffer buffer);
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 8911f6c..634f102 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -74,6 +74,7 @@
     private static final int DO_REMOVE_BROADCAST_INFO = 25;
     private static final int DO_SET_IAPP_NOTIFICATION_ENABLED = 26;
     private static final int DO_REQUEST_AD = 27;
+    private static final int DO_NOTIFY_AD_BUFFER = 28;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -224,6 +225,7 @@
             case DO_START_RECORDING: {
                 SomeArgs args = (SomeArgs) msg.obj;
                 mTvInputRecordingSessionImpl.startRecording((Uri) args.arg1, (Bundle) args.arg2);
+                args.recycle();
                 break;
             }
             case DO_STOP_RECORDING: {
@@ -254,6 +256,10 @@
                 mTvInputSessionImpl.requestAd((AdRequest) msg.obj);
                 break;
             }
+            case DO_NOTIFY_AD_BUFFER: {
+                mTvInputSessionImpl.notifyAdBuffer((AdBuffer) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -424,6 +430,11 @@
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_REQUEST_AD, request));
     }
 
+    @Override
+    public void notifyAdBuffer(AdBuffer buffer) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER, buffer));
+    }
+
     private final class TvInputEventReceiver extends InputEventReceiver {
         public TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index e60d537..667a9ae 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -946,6 +946,10 @@
                 id = generateInputId(componentName, mTvInputHardwareInfo);
                 type = sHardwareTypeToTvInputType.get(mTvInputHardwareInfo.getType(), TYPE_TUNER);
                 isHardwareInput = true;
+                if (mTvInputHardwareInfo.getType() == TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) {
+                    mHdmiDeviceInfo = HdmiDeviceInfo.hardwarePort(
+                            HdmiDeviceInfo.PATH_INVALID, mTvInputHardwareInfo.getHdmiPortId());
+                }
             } else {
                 id = generateInputId(componentName);
                 type = TYPE_TUNER;
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 04d28e7..690fcb1 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -965,6 +965,19 @@
                 });
             }
         }
+
+        void postAdBufferConsumed(AdBuffer buffer) {
+            if (mSession.mIAppNotificationEnabled) {
+                mHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (mSession.getInteractiveAppSession() != null) {
+                            mSession.getInteractiveAppSession().notifyAdBufferConsumed(buffer);
+                        }
+                    }
+                });
+            }
+        }
     }
 
     /**
@@ -1412,6 +1425,18 @@
                     record.postAdResponse(response);
                 }
             }
+
+            @Override
+            public void onAdBufferConsumed(AdBuffer buffer, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdBufferConsumed(buffer);
+                }
+            }
         };
         ITvInputManagerCallback managerCallback = new ITvInputManagerCallback.Stub() {
             @Override
@@ -3204,6 +3229,21 @@
             }
         }
 
+        /**
+         * Notifies when the advertisement buffer is filled and ready to be read.
+         */
+        public void notifyAdBuffer(AdBuffer buffer) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyAdBuffer(mToken, buffer, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         private final class InputEventHandler extends Handler {
             public static final int MSG_SEND_INPUT_EVENT = 1;
             public static final int MSG_TIMEOUT_INPUT_EVENT = 2;
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 70acf25..c46cdbc 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -913,6 +913,27 @@
             });
         }
 
+        /**
+         * Notifies the advertisement buffer is consumed.
+         * @hide
+         */
+        public void notifyAdBufferConsumed(AdBuffer buffer) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) Log.d(TAG, "notifyAdBufferConsumed");
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdBufferConsumed(buffer);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAdBufferConsumed", e);
+                    }
+                }
+            });
+        }
+
         private void notifyTimeShiftStartPositionChanged(final long timeMs) {
             executeOrPostRunnableOnMainThread(new Runnable() {
                 @MainThread
@@ -1129,6 +1150,13 @@
         }
 
         /**
+         * Called when advertisement buffer is ready.
+         * @hide
+         */
+        public void onAdBuffer(AdBuffer buffer) {
+        }
+
+        /**
          * Tunes to a given channel.
          *
          * <p>No video will be displayed until {@link #notifyVideoAvailable()} is called.
@@ -1753,6 +1781,10 @@
             onRequestAd(request);
         }
 
+        void notifyAdBuffer(AdBuffer buffer) {
+            onAdBuffer(buffer);
+        }
+
         /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
index 9b8ec5e..98357fc 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppClient.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.net.Uri;
@@ -37,6 +38,7 @@
     void onSessionStateChanged(int state, int err, int seq);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId, int seq);
     void onTeletextAppStateChanged(int state, int seq);
+    void onAdBuffer(in AdBuffer buffer, int seq);
     void onCommandRequest(in String cmdType, in Bundle parameters, int seq);
     void onSetVideoBounds(in Rect rect, int seq);
     void onRequestCurrentChannelUri(int seq);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
index 38fc717..8bfceee 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppManager.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -71,6 +72,7 @@
     void notifyBroadcastInfoResponse(in IBinder sessionToken, in BroadcastInfoResponse response,
             int UserId);
     void notifyAdResponse(in IBinder sessionToken, in AdResponse response, int UserId);
+    void notifyAdBufferConsumed(in IBinder sessionToken, in AdBuffer buffer, int userId);
 
     void createMediaView(in IBinder sessionToken, in IBinder windowToken, in Rect frame,
             int userId);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
index 9e33536..1953117 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSession.aidl
@@ -19,6 +19,7 @@
 import android.graphics.Rect;
 import android.media.tv.BroadcastInfoResponse;
 import android.net.Uri;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvTrackInfo;
@@ -59,6 +60,7 @@
     void dispatchSurfaceChanged(int format, int width, int height);
     void notifyBroadcastInfoResponse(in BroadcastInfoResponse response);
     void notifyAdResponse(in AdResponse response);
+    void notifyAdBufferConsumed(in AdBuffer buffer);
 
     void createMediaView(in IBinder windowToken, in Rect frame);
     void relayoutMediaView(in Rect frame);
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
index 4ce5871..cd4f410 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionCallback.aidl
@@ -17,6 +17,7 @@
 package android.media.tv.interactive;
 
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.BroadcastInfoRequest;
 import android.media.tv.interactive.ITvInteractiveAppSession;
@@ -36,6 +37,7 @@
     void onSessionStateChanged(int state, int err);
     void onBiInteractiveAppCreated(in Uri biIAppUri, in String biIAppId);
     void onTeletextAppStateChanged(int state);
+    void onAdBuffer(in AdBuffer buffer);
     void onCommandRequest(in String cmdType, in Bundle parameters);
     void onSetVideoBounds(in Rect rect);
     void onRequestCurrentChannelUri();
diff --git a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
index a2fdfe0..b646326 100644
--- a/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
+++ b/media/java/android/media/tv/interactive/ITvInteractiveAppSessionWrapper.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoResponse;
 import android.media.tv.TvContentRating;
@@ -83,6 +84,7 @@
     private static final int DO_REMOVE_MEDIA_VIEW = 29;
     private static final int DO_NOTIFY_RECORDING_STARTED = 30;
     private static final int DO_NOTIFY_RECORDING_STOPPED = 31;
+    private static final int DO_NOTIFY_AD_BUFFER_CONSUMED = 32;
 
     private final HandlerCaller mCaller;
     private Session mSessionImpl;
@@ -253,6 +255,10 @@
                 mSessionImpl.removeMediaView(true);
                 break;
             }
+            case DO_NOTIFY_AD_BUFFER_CONSUMED: {
+                mSessionImpl.notifyAdBufferConsumed((AdBuffer) msg.obj);
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -425,6 +431,11 @@
     }
 
     @Override
+    public void notifyAdBufferConsumed(AdBuffer buffer) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_NOTIFY_AD_BUFFER_CONSUMED, buffer));
+    }
+
+    @Override
     public void createMediaView(IBinder windowToken, Rect frame) {
         mCaller.executeOrSendMessage(
                 mCaller.obtainMessageOO(DO_CREATE_MEDIA_VIEW, windowToken, frame));
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
index 287df40..c57efc8 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppManager.java
@@ -23,6 +23,7 @@
 import android.annotation.SystemService;
 import android.content.Context;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -558,6 +559,18 @@
                     record.postTeletextAppStateChanged(state);
                 }
             }
+
+            @Override
+            public void onAdBuffer(AdBuffer buffer, int seq) {
+                synchronized (mSessionCallbackRecordMap) {
+                    SessionCallbackRecord record = mSessionCallbackRecordMap.get(seq);
+                    if (record == null) {
+                        Log.e(TAG, "Callback not found for seq " + seq);
+                        return;
+                    }
+                    record.postAdBuffer(buffer);
+                }
+            }
         };
         ITvInteractiveAppManagerCallback managerCallback =
                 new ITvInteractiveAppManagerCallback.Stub() {
@@ -1278,6 +1291,21 @@
         }
 
         /**
+         * Notifies the advertisement buffer is consumed.
+         */
+        public void notifyAdBufferConsumed(AdBuffer buffer) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.notifyAdBufferConsumed(mToken, buffer, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
          * Releases this session.
          */
         public void release() {
@@ -1808,6 +1836,17 @@
                 }
             });
         }
+
+        void postAdBuffer(AdBuffer buffer) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (mSession.getInputSession() != null) {
+                        mSession.getInputSession().notifyAdBuffer(buffer);
+                    }
+                }
+            });
+        }
     }
 
     /**
diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
index 00150d5..4ed7ca5 100755
--- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java
+++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -616,6 +617,13 @@
         public void onAdResponse(@NonNull AdResponse response) {
         }
 
+        /**
+         * Called when an advertisement buffer is consumed.
+         * @hide
+         */
+        public void onAdBufferConsumed(AdBuffer buffer) {
+        }
+
         @Override
         public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
             return false;
@@ -1190,6 +1198,17 @@
         }
 
         /**
+         * Calls {@link #onAdBufferConsumed}.
+         */
+        void notifyAdBufferConsumed(AdBuffer buffer) {
+            if (DEBUG) {
+                Log.d(TAG,
+                        "notifyAdBufferConsumed (buffer=" + buffer + ")");
+            }
+            onAdBufferConsumed(buffer);
+        }
+
+        /**
          * Calls {@link #onRecordingStarted(String)}.
          */
         void notifyRecordingStarted(String recordingId) {
@@ -1290,6 +1309,33 @@
             });
         }
 
+
+        /**
+         * Notifies when the advertisement buffer is filled and ready to be read.
+         * @hide
+         */
+        @CallSuper
+        public void notifyAdBuffer(AdBuffer buffer) {
+            executeOrPostRunnableOnMainThread(new Runnable() {
+                @MainThread
+                @Override
+                public void run() {
+                    try {
+                        if (DEBUG) {
+                            Log.d(TAG,
+                                    "notifyAdBuffer(buffer=" + buffer + ")");
+                        }
+                        if (mSessionCallback != null) {
+                            mSessionCallback.onAdBuffer(buffer);
+                        }
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "error in notifyAdBuffer", e);
+                    }
+                }
+            });
+        }
+
+
         /**
          * Takes care of dispatching incoming input events and tells whether the event was handled.
          */
diff --git a/media/tests/AudioPolicyTest/Android.bp b/media/tests/AudioPolicyTest/Android.bp
index 7963ff2..63292ce 100644
--- a/media/tests/AudioPolicyTest/Android.bp
+++ b/media/tests/AudioPolicyTest/Android.bp
@@ -20,4 +20,5 @@
     platform_apis: true,
     certificate: "platform",
     resource_dirs: ["res"],
+    test_suites: ["device-tests"],
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
new file mode 100644
index 0000000..76543f4
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/AudioManagerUnitTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.mediaframeworktest.unit;
+
+
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.FX_KEY_CLICK;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioManager;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AudioManagerUnitTest {
+    private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+
+    @Test
+    public void testAudioManager_playSoundWithDefaultDeviceContext() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_CUSTOM);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(DEVICE_ID_DEFAULT, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect no interactions with VDM when running on default device.
+        verifyZeroInteractions(mockVdm);
+    }
+
+    @Test
+    public void testAudioManager_playSoundWithVirtualDeviceContextDefaultPolicy() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_DEFAULT);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect playback not to be delegated to VDM because of default device policy for audio.
+        verify(mockVdm, never()).playSoundEffect(anyInt(), anyInt());
+    }
+
+    @Test
+    public void testAudioManager_playSoundWithVirtualDeviceContextCustomPolicy() {
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                DEVICE_POLICY_CUSTOM);
+        Context defaultDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, /*vdm=*/
+                mockVdm);
+        AudioManager audioManager = new AudioManager(defaultDeviceContext);
+
+        audioManager.playSoundEffect(FX_KEY_CLICK);
+
+        // We expect playback to be delegated to VDM because of custom device policy for audio.
+        verify(mockVdm, times(1)).playSoundEffect(TEST_VIRTUAL_DEVICE_ID, FX_KEY_CLICK);
+    }
+
+    private static Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+        MockContext mockContext = mock(MockContext.class);
+        when(mockContext.getDeviceId()).thenReturn(deviceId);
+        when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        return mockContext;
+    }
+
+    private static VirtualDeviceManager getMockVirtualDeviceManager(
+            int deviceId, int audioDevicePolicy) {
+        VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+        when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
+        when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
+        return vdmMock;
+    }
+}
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
deleted file mode 100644
index ae65940..0000000
--- a/packages/CredentialManager/res/drawable/ic_profile.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
-        android:viewportWidth="46"
-        android:viewportHeight="46"
-        android:width="46dp"
-        android:height="46dp">
-    <path
-        android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
-        android:strokeColor="#202124"
-        android:strokeAlpha="0.13"
-        android:strokeWidth="1" />
-</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index a3ebf1e..91ffc44 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -9,6 +9,8 @@
   <string name="string_cancel">Cancel</string>
   <!-- Button label to confirm choosing the default dialog information and continue. [CHAR LIMIT=40] -->
   <string name="string_continue">Continue</string>
+  <!-- Button label to create this credential in other available places. [CHAR LIMIT=40] -->
+  <string name="string_more_options">More options</string>
   <!-- This appears as a text button where users can click to create this passkey in other available places. [CHAR LIMIT=80] -->
   <string name="string_create_in_another_place">Create in another place</string>
   <!-- This appears as a text button where users can click to create this password or other credential types in other available places. [CHAR LIMIT=80] -->
@@ -20,11 +22,11 @@
   <!-- 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">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>
+  <string name="passkey_creation_intro_body_password">With passkeys, you don’t 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>
+  <string name="passkey_creation_intro_body_fingerprint">Passkeys are encrypted digital keys you create using your fingerprint, face, or screen lock</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>
+  <string name="passkey_creation_intro_body_device">They 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] -->
@@ -33,26 +35,23 @@
   <string name="save_your_sign_in_info">save your sign-in info</string>
 
   <!-- This appears as the description body of the modal bottom sheet which provides all available providers for users to choose. [CHAR LIMIT=200] -->
-  <string name="choose_provider_body">Set a default password manager to save your passwords and passkeys and sign in faster next time.</string>
+  <string name="choose_provider_body">Select a password manager to save your info and sign in faster next time.</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is passkey. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_passkey_title">Create a passkey in <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_passkey_title">Create passkey for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is password. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_password_title">Save your password to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_password_title">Save password for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose the create option inside a provider when the credential type is others. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_sign_in_title">Save your sign-in info to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%1$s</xliff:g>?</string>
+  <string name="choose_create_option_sign_in_title">Save sign-in info for <xliff:g id="appName" example="Tribank">%1$s</xliff:g>?</string>
   <!-- This appears as the description body of the modal bottom sheet for users to choose the create option inside a provider. [CHAR LIMIT=200] -->
-  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="type" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g></string>
-  <!-- Types which are inserted as a placeholder for string choose_create_option_description. [CHAR LIMIT=200] -->
+  <string name="choose_create_option_description">You can use your <xliff:g id="appDomainName" example="Tribank">%1$s</xliff:g> <xliff:g id="credentialTypes" example="passkey">%2$s</xliff:g> on any device. It is saved to <xliff:g id="providerInfoDisplayName" example="Google Password Manager">%3$s</xliff:g> for <xliff:g id="createInfoDisplayName" example="elisa.beckett@gmail.com">%4$s</xliff:g>.</string>
+  <!-- Types which are inserted as a placeholder as credentialTypes for other strings. [CHAR LIMIT=200] -->
   <string name="passkey">passkey</string>
   <string name="password">password</string>
   <string name="sign_ins">sign-ins</string>
+  <string name="sign_in_info">sign-in info</string>
 
-  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created passkey can be created to. [CHAR LIMIT=200] -->
-  <string name="create_passkey_in_title">Create passkey in</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created password can be saved to. [CHAR LIMIT=200] -->
-  <string name="save_password_to_title">Save password to</string>
-  <!-- This appears as the title of the modal bottom sheet for users to choose other available places the created other credential types can be saved to. [CHAR LIMIT=200] -->
-  <string name="save_sign_in_to_title">Save sign-in to</string>
+  <string name="save_credential_to_title">Save <xliff:g id="credentialTypes" example="passkey">%1$s</xliff:g> to</string>
   <!-- This appears as the title of the modal bottom sheet for users to choose to create a passkey on another device. [CHAR LIMIT=200] -->
   <string name="create_passkey_in_other_device_title">Create a passkey in another device?</string>
   <!-- This appears as the title of the modal bottom sheet for users to confirm whether they should use the selected provider as default or not. [CHAR LIMIT=200] -->
@@ -65,11 +64,13 @@
   <!-- Button label to set the selected provider on the modal bottom sheet not as default but just use once. [CHAR LIMIT=40] -->
   <string name="use_once">Use once</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are passwords and passkeys. [CHAR LIMIT=80] -->
-  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords, <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
+  <string name="more_options_usage_passwords_passkeys"><xliff:g id="passwordsNumber" example="1">%1$s</xliff:g> passwords • <xliff:g id="passkeysNumber" example="2">%2$s</xliff:g> passkeys</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passwords. [CHAR LIMIT=80] -->
   <string name="more_options_usage_passwords"><xliff:g id="passwordsNumber" example="3">%1$s</xliff:g> passwords</string>
   <!-- Appears as an option row subtitle to show how many passwords and passkeys are saved in this option when there are only passkeys. [CHAR LIMIT=80] -->
   <string name="more_options_usage_passkeys"><xliff:g id="passkeysNumber" example="4">%1$s</xliff:g> passkeys</string>
+  <!-- Appears as an option row subtitle to show how many total credentials are saved in this option when the request type is other sign-ins. [CHAR LIMIT=80] -->
+  <string name="more_options_usage_credentials"><xliff:g id="totalCredentialsNumber" example="5">%1$s</xliff:g> credentials</string>
   <!-- Appears before a request display name when the credential type is passkey . [CHAR LIMIT=80] -->
   <string name="passkey_before_subtitle">Passkey</string>
   <!-- Appears as an option row title that users can choose to use another device for this creation. [CHAR LIMIT=80] -->
@@ -92,8 +93,8 @@
   <string name="get_dialog_title_choose_sign_in_for">Choose a saved sign-in for <xliff:g id="app_name" example="YouTube">%1$s</xliff:g></string>
   <!-- Appears as an option row for viewing all the available sign-in options. [CHAR LIMIT=80] -->
   <string name="get_dialog_use_saved_passkey_for">Sign in another way</string>
-  <!-- Button label to close the dialog when the user does not want to use any sign-in. [CHAR LIMIT=40] -->
-  <string name="get_dialog_button_label_no_thanks">No thanks</string>
+  <!-- Appears as a text button in the snackbar for users to click to view all options. [CHAR LIMIT=80] -->
+  <string name="snackbar_action">View options</string>
   <!-- Button label to continue with the selected sign-in. [CHAR LIMIT=40] -->
   <string name="get_dialog_button_label_continue">Continue</string>
   <!-- Separator for sign-in type and username in a sign-in entry. -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 37453c9..b7fb294 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -36,17 +36,22 @@
 import android.credentials.ui.BaseDialogResult
 import android.credentials.ui.ProviderPendingIntentResponse
 import android.credentials.ui.UserSelectionDialogResult
-import android.graphics.drawable.Icon
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
 import android.service.credentials.CredentialProviderService
 import android.util.ArraySet
-import com.android.credentialmanager.createflow.CreateCredentialUiState
+import com.android.credentialmanager.createflow.RequestDisplayInfo
+import com.android.credentialmanager.createflow.EnabledProviderInfo
+import com.android.credentialmanager.createflow.DisabledProviderInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 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
+import com.android.credentialmanager.jetpack.provider.Action
+import com.android.credentialmanager.jetpack.provider.CreateEntry
+import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
+import com.android.credentialmanager.jetpack.provider.CredentialEntry
 
 // Consider repo per screen, similar to view model?
 class CredentialManagerRepo(
@@ -130,19 +135,24 @@
     )
   }
 
-  fun createCredentialInitialUiState(): CreateCredentialUiState {
-    val requestDisplayInfo = CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
+  fun getCreateProviderEnableListInitialUiState(): List<EnabledProviderInfo> {
     val providerEnabledList = CreateFlowUtils.toEnabledProviderList(
       // Handle runtime cast error
-      providerEnabledList as List<CreateCredentialProviderData>, requestDisplayInfo, context)
-    val providerDisabledList = CreateFlowUtils.toDisabledProviderList(
-      // Handle runtime cast error
-      providerDisabledList, context)
+      providerEnabledList as List<CreateCredentialProviderData>, context)
     providerEnabledList.forEach{providerInfo -> providerInfo.createOptions =
       providerInfo.createOptions.sortedWith(compareBy { it.lastUsedTimeMillis }).reversed()
     }
-    return CreateFlowUtils.toCreateCredentialUiState(
-      providerEnabledList, providerDisabledList, requestDisplayInfo, false)
+    return providerEnabledList
+  }
+
+  fun getCreateProviderDisableListInitialUiState(): List<DisabledProviderInfo>? {
+    return CreateFlowUtils.toDisabledProviderList(
+      // Handle runtime cast error
+      providerDisabledList, context)
+  }
+
+  fun getCreateRequestDisplayInfoInitialUiState(): RequestDisplayInfo {
+    return CreateFlowUtils.toRequestDisplayInfo(requestInfo, context)
   }
 
   companion object {
@@ -163,33 +173,33 @@
 
   // TODO: below are prototype functionalities. To be removed for productionization.
   private fun testCreateCredentialEnabledProviderList(): List<CreateCredentialProviderData> {
-    return listOf(
-      CreateCredentialProviderData
-        .Builder("io.enpass.app")
-        .setSaveEntries(
-          listOf<Entry>(
-            newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              20, 7, 27, 10000),
-            newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
-              20, 7, 27, 11000),
-          )
-        )
-        .setRemoteEntry(
-          newRemoteEntry("key2", "subkey-1")
-        )
-        .build(),
-      CreateCredentialProviderData
-        .Builder("com.dashlane")
-        .setSaveEntries(
-          listOf<Entry>(
-            newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              20, 7, 27, 30000),
-            newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              20, 7, 27, 31000),
-          )
-        )
-        .build(),
-    )
+      return listOf(
+          CreateCredentialProviderData
+              .Builder("io.enpass.app")
+              .setSaveEntries(
+                  listOf<Entry>(
+                      newCreateEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+                          20, 7, 27, 10000),
+                      newCreateEntry("key1", "subkey-2", "elisa.work@google.com",
+                          20, 7, 27, 11000),
+                  )
+              )
+              .setRemoteEntry(
+                  newRemoteEntry("key2", "subkey-1")
+              )
+              .build(),
+          CreateCredentialProviderData
+              .Builder("com.dashlane")
+              .setSaveEntries(
+                  listOf<Entry>(
+                      newCreateEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+                          20, 7, 27, 30000),
+                      newCreateEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+                          20, 7, 27, 31000),
+                  )
+              )
+              .build(),
+      )
   }
 
   private fun testDisabledProviderList(): List<DisabledProviderData>? {
@@ -204,17 +214,21 @@
       GetCredentialProviderData.Builder("io.enpass.app")
         .setCredentialEntries(
           listOf<Entry>(
+              newGetEntry(
+                  "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
+                  "elisa.family@outlook.com", null, 3L
+              ),
             newGetEntry(
               "key1", "subkey-1", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
-              "elisa.bakery@gmail.com", "Elisa Beckett", 300L
+              "elisa.bakery@gmail.com", "Elisa Beckett", 0L
             ),
             newGetEntry(
               "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.bakery@gmail.com", null, 300L
+              "elisa.bakery@gmail.com", null, 10L
             ),
             newGetEntry(
-              "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.family@outlook.com", null, 100L
+              "key1", "subkey-3", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
+              "elisa.family@outlook.com", "Elisa Beckett", 1L
             ),
           )
         ).setAuthenticationEntry(
@@ -237,12 +251,12 @@
         .setCredentialEntries(
           listOf<Entry>(
             newGetEntry(
-              "key1", "subkey-1", TYPE_PASSWORD_CREDENTIAL, "Password",
-              "elisa.family@outlook.com", null, 600L
+              "key1", "subkey-2", TYPE_PASSWORD_CREDENTIAL, "Password",
+              "elisa.family@outlook.com", null, 4L
             ),
             newGetEntry(
-              "key1", "subkey-2", TYPE_PUBLIC_KEY_CREDENTIAL, "Passkey",
-              "elisa.family@outlook.com", null, 100L
+                  "key1", "subkey-3", TYPE_PASSWORD_CREDENTIAL, "Password",
+                  "elisa.work@outlook.com", null, 11L
             ),
           )
         ).setAuthenticationEntry(
@@ -258,137 +272,107 @@
     )
   }
 
-  private fun newActionEntry(
-    key: String,
-    subkey: String,
-    credentialType: String,
-    text: String,
-    subtext: String? = null,
-  ): Entry {
-    val slice = Slice.Builder(
-      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
-    ).addText(
-      text, null, listOf(Entry.HINT_ACTION_TITLE)
-    )
-    if (subtext != null) {
-      slice.addText(subtext, null, listOf(Entry.HINT_ACTION_SUBTEXT))
+    private fun newActionEntry(
+            key: String,
+            subkey: String,
+            credentialType: String,
+            text: String,
+            subtext: String? = null,
+    ): Entry {
+        val action = Action(text, subtext, null)
+
+        return Entry(
+                key,
+                subkey,
+                Action.toSlice(action)
+        )
     }
-    return Entry(
-      key,
-      subkey,
-      slice.build()
-    )
-  }
 
-  private fun newAuthenticationEntry(
-    key: String,
-    subkey: String,
-    credentialType: String,
-  ): Entry {
-    val slice = Slice.Builder(
-      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
-    )
-    return Entry(
-      key,
-      subkey,
-      slice.build()
-    )
-  }
-
-  private fun newGetEntry(
-    key: String,
-    subkey: String,
-    credentialType: String,
-    credentialTypeDisplayName: String,
-    userName: String,
-    userDisplayName: String?,
-    lastUsedTimeMillis: Long?,
-  ): Entry {
-    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
-      .setPackage("com.androidauth.androidvault")
-    intent.putExtra("provider_extra_sample", "testprovider")
-
-    val pendingIntent = PendingIntent.getActivity(context, 1,
-      intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
-              or PendingIntent.FLAG_ONE_SHOT))
-
-    val slice = Slice.Builder(
-      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
-    ).addText(
-      credentialTypeDisplayName, null, listOf(Entry.HINT_CREDENTIAL_TYPE_DISPLAY_NAME)
-    ).addText(
-      userName, null, listOf(Entry.HINT_USER_NAME)
-    ).addIcon(
-      Icon.createWithResource(context, R.drawable.ic_passkey),
-      null,
-      listOf(Entry.HINT_PROFILE_ICON))
-    if (userDisplayName != null) {
-      slice.addText(userDisplayName, null, listOf(Entry.HINT_PASSKEY_USER_DISPLAY_NAME))
+    private fun newAuthenticationEntry(
+            key: String,
+            subkey: String,
+            credentialType: String,
+    ): Entry {
+        val slice = Slice.Builder(
+                Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(credentialType, 1)
+        )
+        return Entry(
+                key,
+                subkey,
+                slice.build()
+        )
     }
-    if (lastUsedTimeMillis != null) {
-      slice.addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
+
+    private fun newGetEntry(
+            key: String,
+            subkey: String,
+            credentialType: String,
+            credentialTypeDisplayName: String,
+            userName: String,
+            userDisplayName: String?,
+            lastUsedTimeMillis: Long?,
+    ): Entry {
+        val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+                .setPackage("com.androidauth.androidvault")
+        intent.putExtra("provider_extra_sample", "testprovider")
+
+        val pendingIntent = PendingIntent.getActivity(context, 1,
+                intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+                or PendingIntent.FLAG_ONE_SHOT))
+
+        val credentialEntry = CredentialEntry(credentialType, credentialTypeDisplayName, userName,
+                userDisplayName, pendingIntent, lastUsedTimeMillis
+                ?: 0L, null, false)
+
+        return Entry(
+                key,
+                subkey,
+                CredentialEntry.toSlice(credentialEntry),
+                pendingIntent,
+                null
+        )
+  }
+
+    private fun newCreateEntry(
+            key: String,
+            subkey: String,
+            providerDisplayName: String,
+            passwordCount: Int,
+            passkeyCount: Int,
+            totalCredentialCount: Int,
+            lastUsedTimeMillis: Long,
+    ): Entry {
+        val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
+                .setPackage("com.androidauth.androidvault")
+        intent.putExtra("provider_extra_sample", "testprovider")
+        val pendingIntent = PendingIntent.getActivity(context, 1,
+                intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+                or PendingIntent.FLAG_ONE_SHOT))
+        val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
+                android.service.credentials.CallingAppInfo(
+                        context.applicationInfo.packageName, ArraySet<Signature>()),
+                TYPE_PASSWORD_CREDENTIAL,
+                toBundle("beckett-bakert@gmail.com", "password123")
+        )
+        val fillInIntent = Intent().putExtra(
+                CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
+                createPasswordRequest)
+
+        val createEntry = CreateEntry(
+                providerDisplayName, pendingIntent,
+                null, lastUsedTimeMillis,
+                listOf(
+                        CredentialCountInformation.createPasswordCountInformation(passwordCount),
+                        CredentialCountInformation.createPublicKeyCountInformation(passkeyCount),
+                ))
+        return Entry(
+                key,
+                subkey,
+                CreateEntry.toSlice(createEntry),
+                pendingIntent,
+                fillInIntent,
+        )
     }
-    return Entry(
-      key,
-      subkey,
-      slice.build(),
-      pendingIntent,
-      null
-    )
-  }
-
-  private fun newCreateEntry(
-    key: String,
-    subkey: String,
-    providerDisplayName: String,
-    passwordCount: Int,
-    passkeyCount: Int,
-    totalCredentialCount: Int,
-    lastUsedTimeMillis: Long,
-  ): Entry {
-    val intent = Intent("com.androidauth.androidvault.CONFIRM_PASSWORD")
-      .setPackage("com.androidauth.androidvault")
-    intent.putExtra("provider_extra_sample", "testprovider")
-    val pendingIntent = PendingIntent.getActivity(context, 1,
-      intent, (PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
-              or PendingIntent.FLAG_ONE_SHOT))
-    val createPasswordRequest = android.service.credentials.CreateCredentialRequest(
-            android.service.credentials.CallingAppInfo(
-                    context.applicationInfo.packageName, ArraySet<Signature>()),
-            TYPE_PASSWORD_CREDENTIAL,
-            toBundle("beckett-bakert@gmail.com", "password123")
-    )
-    val fillInIntent = Intent().putExtra(CredentialProviderService.EXTRA_CREATE_CREDENTIAL_REQUEST,
-            createPasswordRequest)
-
-    val slice = Slice.Builder(
-      Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
-    ).addText(
-        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
-      .addIcon(
-        Icon.createWithResource(context, R.drawable.ic_passkey),
-        null,
-        listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
-      .addIcon(
-        Icon.createWithResource(context, R.drawable.ic_profile),
-        null,
-        listOf(Entry.HINT_PROFILE_ICON))
-      .addInt(
-        passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
-      .addInt(
-        passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
-      .addInt(
-        totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
-      .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
-      .build()
-    return Entry(
-      key,
-      subkey,
-      slice,
-      pendingIntent,
-      fillInIntent,
-    )
-  }
 
   private fun newRemoteEntry(
     key: String,
@@ -487,7 +471,9 @@
   private fun testGetRequestInfo(): RequestInfo {
     return RequestInfo.newGetRequestInfo(
       Binder(),
-      GetCredentialRequest.Builder()
+      GetCredentialRequest.Builder(
+        Bundle()
+      )
         .addGetCredentialOption(
           GetCredentialOption(
             TYPE_PUBLIC_KEY_CREDENTIAL, Bundle(), Bundle(), /*requireSystemProvider=*/ false)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index db676b2..48aebec 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -42,9 +42,10 @@
 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
+import com.android.credentialmanager.jetpack.provider.Action
+import com.android.credentialmanager.jetpack.provider.CredentialCountInformation
+import com.android.credentialmanager.jetpack.provider.CredentialEntry
+import com.android.credentialmanager.jetpack.provider.CreateEntry
 import org.json.JSONObject
 
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
@@ -107,7 +108,8 @@
       context: Context,
     ): List<CredentialEntryInfo> {
       return credentialEntries.map {
-        val credentialEntryUi = CredentialEntryUi.fromSlice(it.slice)
+        // TODO: handle NPE gracefully
+        val credentialEntry = CredentialEntry.fromSlice(it.slice)!!
 
         // Consider directly move the UI object into the class.
         return@map CredentialEntryInfo(
@@ -116,14 +118,13 @@
           entrySubkey = it.subkey,
           pendingIntent = it.pendingIntent,
           fillInIntent = it.frameworkExtrasIntent,
-          credentialType = credentialEntryUi.credentialType.toString(),
-          credentialTypeDisplayName = credentialEntryUi.credentialTypeDisplayName.toString(),
-          userName = credentialEntryUi.userName.toString(),
-          displayName = credentialEntryUi.userDisplayName?.toString(),
+          credentialType = credentialEntry.type.toString(),
+          credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(),
+          userName = credentialEntry.username.toString(),
+          displayName = credentialEntry.displayName?.toString(),
           // TODO: proper fallback
-          icon = credentialEntryUi.entryIcon?.loadDrawable(context)
-            ?: context.getDrawable(R.drawable.ic_other_sign_in)!!,
-          lastUsedTimeMillis = credentialEntryUi.lastUsedTimeMillis,
+          icon = credentialEntry.icon?.loadDrawable(context),
+          lastUsedTimeMillis = credentialEntry.lastUsedTimeMillis,
         )
       }
     }
@@ -170,7 +171,8 @@
       providerIcon: Drawable,
     ): List<ActionEntryInfo> {
       return actionEntries.map {
-        val actionEntryUi = ActionUi.fromSlice(it.slice)
+        // TODO: handle NPE gracefully
+        val actionEntryUi = Action.fromSlice(it.slice)!!
 
         return@map ActionEntryInfo(
           providerId = providerId,
@@ -178,10 +180,10 @@
           entrySubkey = it.subkey,
           pendingIntent = it.pendingIntent,
           fillInIntent = it.frameworkExtrasIntent,
-          title = actionEntryUi.text.toString(),
+          title = actionEntryUi.title.toString(),
           // TODO: gracefully fail
           icon = providerIcon,
-          subTitle = actionEntryUi.subtext?.toString(),
+          subTitle = actionEntryUi.subTitle?.toString(),
         )
       }
     }
@@ -193,9 +195,8 @@
 
     fun toEnabledProviderList(
       providerDataList: List<CreateCredentialProviderData>,
-      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.EnabledProviderInfo> {
+    ): List<EnabledProviderInfo> {
       // TODO: get from the actual service info
       val packageManager = context.packageManager
 
@@ -216,7 +217,7 @@
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
           createOptions = toCreationOptionInfoList(
-            it.providerFlattenedComponentName, it.saveEntries, requestDisplayInfo, context),
+            it.providerFlattenedComponentName, it.saveEntries, context),
           remoteEntry = toRemoteInfo(it.providerFlattenedComponentName, it.remoteEntry),
         )
       }
@@ -225,14 +226,14 @@
     fun toDisabledProviderList(
       providerDataList: List<DisabledProviderData>?,
       context: Context,
-    ): List<com.android.credentialmanager.createflow.DisabledProviderInfo>? {
+    ): List<DisabledProviderInfo>? {
       // TODO: get from the actual service info
       val packageManager = context.packageManager
       return providerDataList?.map {
         val pkgInfo = packageManager
           .getPackageInfo(it.providerFlattenedComponentName,
             PackageManager.PackageInfoFlags.of(0))
-        com.android.credentialmanager.createflow.DisabledProviderInfo(
+        DisabledProviderInfo(
           icon = pkgInfo.applicationInfo.loadIcon(packageManager)!!,
           name = it.providerFlattenedComponentName,
           displayName = pkgInfo.applicationInfo.loadLabel(packageManager).toString(),
@@ -292,14 +293,15 @@
     fun toCreateCredentialUiState(
       enabledProviders: List<EnabledProviderInfo>,
       disabledProviders: List<DisabledProviderInfo>?,
+      defaultProviderId: String?,
       requestDisplayInfo: RequestDisplayInfo,
       isOnPasskeyIntroStateAlready: Boolean,
+      isPasskeyFirstUse: Boolean,
     ): CreateCredentialUiState {
       var createOptionSize = 0
       var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null
       var remoteEntry: RemoteInfo? = null
       var defaultProvider: EnabledProviderInfo? = null
-      val defaultProviderId = UserConfigRepo.getInstance().getDefaultProviderId()
       enabledProviders.forEach {
           enabledProvider ->
         if (defaultProviderId != null) {
@@ -319,13 +321,18 @@
         enabledProviders = enabledProviders,
         disabledProviders = disabledProviders,
         toCreateScreenState(
-          createOptionSize, isOnPasskeyIntroStateAlready,
-          requestDisplayInfo, defaultProvider, remoteEntry),
+          /*createOptionSize=*/createOptionSize,
+          /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready,
+          /*requestDisplayInfo=*/requestDisplayInfo,
+          /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry,
+          /*isPasskeyFirstUse=*/isPasskeyFirstUse),
         requestDisplayInfo,
-        isOnPasskeyIntroStateAlready,
+        defaultProvider != null,
         toActiveEntry(
-          /*defaultProvider=*/defaultProvider, createOptionSize,
-          lastSeenProviderWithNonEmptyCreateOptions, remoteEntry),
+          /*defaultProvider=*/defaultProvider,
+          /*createOptionSize=*/createOptionSize,
+          /*lastSeenProviderWithNonEmptyCreateOptions=*/lastSeenProviderWithNonEmptyCreateOptions,
+          /*remoteEntry=*/remoteEntry),
       )
     }
 
@@ -335,9 +342,10 @@
       requestDisplayInfo: RequestDisplayInfo,
       defaultProvider: EnabledProviderInfo?,
       remoteEntry: RemoteInfo?,
+      isPasskeyFirstUse: Boolean,
     ): CreateScreenState {
       return if (
-        UserConfigRepo.getInstance().getIsFirstUse() && requestDisplayInfo
+        isPasskeyFirstUse && requestDisplayInfo
           .type == TYPE_PUBLIC_KEY_CREDENTIAL && !isOnPasskeyIntroStateAlready) {
         CreateScreenState.PASSKEY_INTRO
       } else if (
@@ -379,11 +387,11 @@
     private fun toCreationOptionInfoList(
       providerId: String,
       creationEntries: List<Entry>,
-      requestDisplayInfo: RequestDisplayInfo,
       context: Context,
     ): List<CreateOptionInfo> {
       return creationEntries.map {
-        val saveEntryUi = SaveEntryUi.fromSlice(it.slice)
+        // TODO: handle NPE gracefully
+        val createEntry = CreateEntry.fromSlice(it.slice)!!
 
         return@map CreateOptionInfo(
           // TODO: remove fallbacks
@@ -392,13 +400,15 @@
           entrySubkey = it.subkey,
           pendingIntent = it.pendingIntent,
           fillInIntent = it.frameworkExtrasIntent,
-          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
-          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
-            ?: requestDisplayInfo.typeIcon,
-          passwordCount = saveEntryUi.passwordCount ?: 0,
-          passkeyCount = saveEntryUi.passkeyCount ?: 0,
-          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
-          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
+          userProviderDisplayName = createEntry.accountName.toString(),
+          profileIcon = createEntry.icon?.loadDrawable(context),
+          passwordCount = CredentialCountInformation.getPasswordCount(
+                  createEntry.credentialCountInformationList) ?: 0,
+          passkeyCount = CredentialCountInformation.getPasskeyCount(
+                  createEntry.credentialCountInformationList) ?: 0,
+          totalCredentialCount = CredentialCountInformation.getTotalCount(
+                  createEntry.credentialCountInformationList) ?: 0,
+          lastUsedTimeMillis = createEntry.lastUsedTimeMillis ?: 0,
         )
       }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
index 5e77663..021dcab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/UserConfigRepo.kt
@@ -32,7 +32,7 @@
         }
     }
 
-    fun setIsFirstUse(
+    fun setIsPasskeyFirstUse(
         isFirstUse: Boolean
     ) {
         sharedPreferences.edit().apply {
@@ -45,7 +45,7 @@
         return sharedPreferences.getString(DEFAULT_PROVIDER, null)
     }
 
-    fun getIsFirstUse(): Boolean {
+    fun getIsPasskeyFirstUse(): Boolean {
         return sharedPreferences.getBoolean(IS_PASSKEY_FIRST_USE, true)
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
similarity index 95%
rename from packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
index 80764b5..d0271ab 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/CancelButton.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/ActionButton.kt
@@ -23,7 +23,7 @@
 import androidx.compose.runtime.Composable
 
 @Composable
-fun CancelButton(text: String, onClick: () -> Unit) {
+fun ActionButton(text: String, onClick: () -> Unit) {
     TextButton(
         onClick = onClick,
         colors = ButtonDefaults.textButtonColors(
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 38e2caa..3d23613 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -14,14 +14,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.material3.ButtonDefaults
 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.Text
-import androidx.compose.material3.TextButton
 import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.material.icons.Icons
@@ -43,7 +40,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
 import com.android.credentialmanager.common.ui.ConfirmButton
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TextOnSurface
@@ -79,28 +76,30 @@
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         disabledProviderList = uiState.disabledProviders,
-                        onCancel = viewModel::onCancel,
                         onOptionSelected = viewModel::onEntrySelectedFromFirstUseScreen,
                         onDisabledPasswordManagerSelected =
                         viewModel::onDisabledPasswordManagerSelected,
-                        onRemoteEntrySelected = viewModel::onEntrySelected,
+                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnProviderSelection,
                     )
                     CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         providerInfo = uiState.activeEntry?.activeProvider!!,
                         createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
-                        showActiveEntryOnly = uiState.showActiveEntryOnly,
                         onOptionSelected = viewModel::onEntrySelected,
                         onConfirm = viewModel::onConfirmEntrySelected,
-                        onCancel = viewModel::onCancel,
-                        onMoreOptionsSelected = viewModel::onMoreOptionsSelected,
+                        onMoreOptionsSelected = viewModel::onMoreOptionsSelectedOnCreationSelection,
                     )
                     CreateScreenState.MORE_OPTIONS_SELECTION -> MoreOptionsSelectionCard(
                         requestDisplayInfo = uiState.requestDisplayInfo,
                         enabledProviderList = uiState.enabledProviders,
                         disabledProviderList = uiState.disabledProviders,
-                        onBackButtonSelected = viewModel::onBackButtonSelected,
+                        hasDefaultProvider = uiState.hasDefaultProvider,
+                        isFromProviderSelection = uiState.isFromProviderSelection!!,
+                        onBackProviderSelectionButtonSelected =
+                        viewModel::onBackProviderSelectionButtonSelected,
+                        onBackCreationSelectionButtonSelected =
+                        viewModel::onBackCreationSelectionButtonSelected,
                         onOptionSelected = viewModel::onEntrySelectedFromMoreOptionScreen,
                         onDisabledPasswordManagerSelected =
                         viewModel::onDisabledPasswordManagerSelected,
@@ -172,7 +171,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_password),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -192,7 +191,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_fingerprint),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -212,7 +211,7 @@
                 TextSecondary(
                     text = stringResource(R.string.passkey_creation_intro_body_device),
                     style = MaterialTheme.typography.bodyMedium,
-                    modifier = Modifier.padding(start = 16.dp),
+                    modifier = Modifier.padding(start = 16.dp, end = 4.dp),
                 )
             }
             Divider(
@@ -223,7 +222,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.string_cancel),
                     onClick = onCancel
                 )
@@ -249,8 +248,7 @@
     disabledProviderList: List<DisabledProviderInfo>?,
     onOptionSelected: (ActiveEntry) -> Unit,
     onDisabledPasswordManagerSelected: () -> Unit,
-    onCancel: () -> Unit,
-    onRemoteEntrySelected: (EntryInfo) -> Unit,
+    onMoreOptionsSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
@@ -301,124 +299,7 @@
                         enabledProviderInfo.createOptions.forEach { createOptionInfo ->
                             item {
                                 MoreOptionsInfoRow(
-                                    providerInfo = enabledProviderInfo,
-                                    createOptionInfo = createOptionInfo,
-                                    onOptionSelected = {
-                                        onOptionSelected(
-                                            ActiveEntry(
-                                                enabledProviderInfo,
-                                                createOptionInfo
-                                            )
-                                        )
-                                    })
-                            }
-                        }
-                    }
-                    if (disabledProviderList != null && disabledProviderList.isNotEmpty()) {
-                        item {
-                            MoreOptionsDisabledProvidersRow(
-                                disabledProviders = disabledProviderList,
-                                onDisabledPasswordManagerSelected =
-                                onDisabledPasswordManagerSelected,
-                            )
-                        }
-                    }
-                }
-            }
-            // TODO: handle the error situation that if multiple remoteInfos exists
-            enabledProviderList.forEach { enabledProvider ->
-                if (enabledProvider.remoteEntry != null) {
-                    TextButton(
-                        onClick = {
-                            onRemoteEntrySelected(enabledProvider.remoteEntry!!)
-                        },
-                        modifier = Modifier
-                            .padding(horizontal = 24.dp)
-                            .align(alignment = Alignment.CenterHorizontally),
-                        colors = ButtonDefaults.textButtonColors(
-                            contentColor = MaterialTheme.colorScheme.primary,
-                        )
-                    ) {
-                        Text(
-                            text = stringResource(R.string.string_save_to_another_device),
-                            textAlign = TextAlign.Center,
-                        )
-                    }
-                }
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.Start,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                CancelButton(stringResource(R.string.string_cancel), onCancel)
-            }
-            Divider(
-                thickness = 18.dp,
-                color = Color.Transparent,
-                modifier = Modifier.padding(bottom = 16.dp)
-            )
-        }
-    }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Composable
-fun MoreOptionsSelectionCard(
-    requestDisplayInfo: RequestDisplayInfo,
-    enabledProviderList: List<EnabledProviderInfo>,
-    disabledProviderList: List<DisabledProviderInfo>?,
-    onBackButtonSelected: () -> Unit,
-    onOptionSelected: (ActiveEntry) -> Unit,
-    onDisabledPasswordManagerSelected: () -> Unit,
-    onRemoteEntrySelected: (EntryInfo) -> Unit,
-) {
-    ContainerCard() {
-        Column() {
-            TopAppBar(
-                title = {
-                    TextOnSurface(
-                        text = when (requestDisplayInfo.type) {
-                            TYPE_PUBLIC_KEY_CREDENTIAL ->
-                                stringResource(R.string.create_passkey_in_title)
-                            TYPE_PASSWORD_CREDENTIAL ->
-                                stringResource(R.string.save_password_to_title)
-                            else -> stringResource(R.string.save_sign_in_to_title)
-                        },
-                        style = MaterialTheme.typography.titleMedium,
-                    )
-                },
-                navigationIcon = {
-                    IconButton(onClick = onBackButtonSelected) {
-                        Icon(
-                            Icons.Filled.ArrowBack,
-                            stringResource(R.string.accessibility_back_arrow_button)
-                        )
-                    }
-                },
-                colors = TopAppBarDefaults.smallTopAppBarColors
-                    (containerColor = Color.Transparent),
-            )
-            Divider(
-                thickness = 8.dp,
-                color = Color.Transparent
-            )
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally)
-            ) {
-                LazyColumn(
-                    verticalArrangement = Arrangement.spacedBy(2.dp)
-                ) {
-                    enabledProviderList.forEach { enabledProviderInfo ->
-                        enabledProviderInfo.createOptions.forEach { createOptionInfo ->
-                            item {
-                                MoreOptionsInfoRow(
+                                    requestDisplayInfo = requestDisplayInfo,
                                     providerInfo = enabledProviderInfo,
                                     createOptionInfo = createOptionInfo,
                                     onOptionSelected = {
@@ -439,6 +320,124 @@
                             onDisabledPasswordManagerSelected,
                         )
                     }
+                }
+            }
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
+            // TODO: handle the error situation that if multiple remoteInfos exists
+            enabledProviderList.forEach { enabledProvider ->
+                if (enabledProvider.remoteEntry != null) {
+                    Row(
+                        horizontalArrangement = Arrangement.Start,
+                        modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+                    ) {
+                        ActionButton(
+                            stringResource(R.string.string_more_options),
+                            onMoreOptionsSelected
+                        )
+                    }
+                }
+            }
+            Divider(
+                thickness = 18.dp,
+                color = Color.Transparent,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun MoreOptionsSelectionCard(
+    requestDisplayInfo: RequestDisplayInfo,
+    enabledProviderList: List<EnabledProviderInfo>,
+    disabledProviderList: List<DisabledProviderInfo>?,
+    hasDefaultProvider: Boolean,
+    isFromProviderSelection: Boolean,
+    onBackProviderSelectionButtonSelected: () -> Unit,
+    onBackCreationSelectionButtonSelected: () -> Unit,
+    onOptionSelected: (ActiveEntry) -> Unit,
+    onDisabledPasswordManagerSelected: () -> Unit,
+    onRemoteEntrySelected: (EntryInfo) -> Unit,
+) {
+    ContainerCard() {
+        Column() {
+            TopAppBar(
+                title = {
+                    TextOnSurface(
+                        text =
+                        stringResource(
+                            R.string.save_credential_to_title,
+                            when (requestDisplayInfo.type) {
+                                TYPE_PUBLIC_KEY_CREDENTIAL ->
+                                    stringResource(R.string.passkey)
+                                TYPE_PASSWORD_CREDENTIAL ->
+                                    stringResource(R.string.password)
+                                else -> stringResource(R.string.sign_in_info)
+                            }),
+                        style = MaterialTheme.typography.titleMedium,
+                    )
+                },
+                navigationIcon = {
+                    IconButton(
+                        onClick =
+                        if (isFromProviderSelection)
+                            onBackProviderSelectionButtonSelected
+                        else onBackCreationSelectionButtonSelected
+                    ) {
+                        Icon(
+                            Icons.Filled.ArrowBack,
+                            stringResource(R.string.accessibility_back_arrow_button)
+                        )
+                    }
+                },
+                colors = TopAppBarDefaults.smallTopAppBarColors
+                    (containerColor = Color.Transparent),
+                modifier = Modifier.padding(top = 12.dp)
+            )
+            Divider(
+                thickness = 8.dp,
+                color = Color.Transparent
+            )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(horizontal = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally)
+            ) {
+                LazyColumn(
+                    verticalArrangement = Arrangement.spacedBy(2.dp)
+                ) {
+                    if (hasDefaultProvider) {
+                        enabledProviderList.forEach { enabledProviderInfo ->
+                            enabledProviderInfo.createOptions.forEach { createOptionInfo ->
+                                item {
+                                    MoreOptionsInfoRow(
+                                        requestDisplayInfo = requestDisplayInfo,
+                                        providerInfo = enabledProviderInfo,
+                                        createOptionInfo = createOptionInfo,
+                                        onOptionSelected = {
+                                            onOptionSelected(
+                                                ActiveEntry(
+                                                    enabledProviderInfo,
+                                                    createOptionInfo
+                                                )
+                                            )
+                                        })
+                                }
+                            }
+                        }
+                        item {
+                            MoreOptionsDisabledProvidersRow(
+                                disabledProviders = disabledProviderList,
+                                onDisabledPasswordManagerSelected =
+                                onDisabledPasswordManagerSelected,
+                            )
+                        }
+                    }
                     // TODO: handle the error situation that if multiple remoteInfos exists
                     enabledProviderList.forEach {
                         if (it.remoteEntry != null) {
@@ -453,7 +452,7 @@
                 }
             }
             Divider(
-                thickness = 18.dp,
+                thickness = 8.dp,
                 color = Color.Transparent,
                 modifier = Modifier.padding(bottom = 40.dp)
             )
@@ -496,7 +495,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.use_once),
                     onClick = onUseOnceSelected
                 )
@@ -521,34 +520,42 @@
     enabledProviderList: List<EnabledProviderInfo>,
     providerInfo: EnabledProviderInfo,
     createOptionInfo: CreateOptionInfo,
-    showActiveEntryOnly: Boolean,
     onOptionSelected: (EntryInfo) -> Unit,
     onConfirm: () -> Unit,
-    onCancel: () -> Unit,
     onMoreOptionsSelected: () -> Unit,
 ) {
     ContainerCard() {
         Column() {
+            Divider(
+                thickness = 24.dp,
+                color = Color.Transparent
+            )
             Icon(
                 bitmap = providerInfo.icon.toBitmap().asImageBitmap(),
                 contentDescription = null,
                 tint = Color.Unspecified,
-                modifier = Modifier.align(alignment = Alignment.CenterHorizontally)
-                    .padding(all = 24.dp).size(32.dp)
+                modifier = Modifier.align(alignment = Alignment.CenterHorizontally).size(32.dp)
+            )
+            TextSecondary(
+                text = providerInfo.displayName,
+                style = MaterialTheme.typography.titleLarge,
+                modifier = Modifier.padding(vertical = 10.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+                textAlign = TextAlign.Center,
             )
             TextOnSurface(
                 text = when (requestDisplayInfo.type) {
                     TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(
                         R.string.choose_create_option_passkey_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                     TYPE_PASSWORD_CREDENTIAL -> stringResource(
                         R.string.choose_create_option_password_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                     else -> stringResource(
                         R.string.choose_create_option_sign_in_title,
-                        providerInfo.displayName
+                        requestDisplayInfo.appDomainName
                     )
                 },
                 style = MaterialTheme.typography.titleMedium,
@@ -556,6 +563,51 @@
                     .align(alignment = Alignment.CenterHorizontally),
                 textAlign = TextAlign.Center,
             )
+            ContainerCard(
+                shape = MaterialTheme.shapes.medium,
+                modifier = Modifier
+                    .padding(all = 24.dp)
+                    .align(alignment = Alignment.CenterHorizontally),
+            ) {
+                PrimaryCreateOptionRow(
+                    requestDisplayInfo = requestDisplayInfo,
+                    entryInfo = createOptionInfo,
+                    onOptionSelected = onOptionSelected
+                )
+            }
+            var shouldShowMoreOptionsButton = false
+            var createOptionsSize = 0
+            var remoteEntry: RemoteInfo? = null
+            enabledProviderList.forEach { enabledProvider ->
+                if (enabledProvider.remoteEntry != null) {
+                    remoteEntry = enabledProvider.remoteEntry
+                }
+                createOptionsSize += enabledProvider.createOptions.size
+            }
+            if (createOptionsSize > 1 || remoteEntry != null) {
+                shouldShowMoreOptionsButton = true
+            }
+            Row(
+                horizontalArrangement =
+                if (shouldShowMoreOptionsButton) Arrangement.SpaceBetween else Arrangement.End,
+                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
+            ) {
+                if (shouldShowMoreOptionsButton) {
+                    ActionButton(
+                        stringResource(R.string.string_more_options),
+                        onClick = onMoreOptionsSelected
+                    )
+                }
+                ConfirmButton(
+                    stringResource(R.string.string_continue),
+                    onClick = onConfirm
+                )
+            }
+            Divider(
+                thickness = 1.dp,
+                color = Color.LightGray,
+                modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 18.dp)
+            )
             if (createOptionInfo.userProviderDisplayName != null) {
                 TextSecondary(
                     text = stringResource(
@@ -570,88 +622,8 @@
                         createOptionInfo.userProviderDisplayName
                     ),
                     style = MaterialTheme.typography.bodyLarge,
-                    modifier = Modifier.padding(all = 24.dp)
-                        .align(alignment = Alignment.CenterHorizontally),
-                )
-            }
-            ContainerCard(
-                shape = MaterialTheme.shapes.medium,
-                modifier = Modifier
-                    .padding(horizontal = 24.dp)
-                    .align(alignment = Alignment.CenterHorizontally),
-            ) {
-                PrimaryCreateOptionRow(
-                    requestDisplayInfo = requestDisplayInfo,
-                    entryInfo = createOptionInfo,
-                    onOptionSelected = onOptionSelected
-                )
-            }
-            if (!showActiveEntryOnly) {
-                var createOptionsSize = 0
-                enabledProviderList.forEach { enabledProvider ->
-                    createOptionsSize += enabledProvider.createOptions.size
-                }
-                if (createOptionsSize > 1) {
-                    TextButton(
-                        onClick = onMoreOptionsSelected,
-                        modifier = Modifier
-                            .padding(horizontal = 24.dp)
-                            .align(alignment = Alignment.CenterHorizontally),
-                        colors = ButtonDefaults.textButtonColors(
-                            contentColor = MaterialTheme.colorScheme.primary,
-                        ),
-                    ) {
-                        Text(
-                            text =
-                            when (requestDisplayInfo.type) {
-                                TYPE_PUBLIC_KEY_CREDENTIAL ->
-                                    stringResource(R.string.string_create_in_another_place)
-                                else -> stringResource(R.string.string_save_to_another_place)
-                            },
-                            textAlign = TextAlign.Center,
-                        )
-                    }
-                } else if (
-                    requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL
-                ) {
-                    // TODO: handle the error situation that if multiple remoteInfos exists
-                    enabledProviderList.forEach { enabledProvider ->
-                        if (enabledProvider.remoteEntry != null) {
-                            TextButton(
-                                onClick = {
-                                    onOptionSelected(enabledProvider.remoteEntry!!)
-                                },
-                                modifier = Modifier
-                                    .padding(horizontal = 24.dp)
-                                    .align(alignment = Alignment.CenterHorizontally),
-                                colors = ButtonDefaults.textButtonColors(
-                                    contentColor = MaterialTheme.colorScheme.primary,
-                                ),
-                            ) {
-                                Text(
-                                    text = stringResource(R.string.string_use_another_device),
-                                    textAlign = TextAlign.Center,
-                                )
-                            }
-                        }
-                    }
-                }
-            }
-            Divider(
-                thickness = 24.dp,
-                color = Color.Transparent
-            )
-            Row(
-                horizontalArrangement = Arrangement.SpaceBetween,
-                modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
-            ) {
-                CancelButton(
-                    stringResource(R.string.string_cancel),
-                    onClick = onCancel
-                )
-                ConfirmButton(
-                    stringResource(R.string.string_continue),
-                    onClick = onConfirm
+                    modifier = Modifier.padding(
+                        start = 24.dp, top = 8.dp, bottom = 18.dp, end = 24.dp)
                 )
             }
             Divider(
@@ -712,7 +684,7 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(
+                ActionButton(
                     stringResource(R.string.string_cancel),
                     onClick = onCancel
                 )
@@ -740,16 +712,20 @@
     Entry(
         onClick = { onOptionSelected(entryInfo) },
         icon = {
-            Icon(
-                bitmap = if (entryInfo is CreateOptionInfo) {
-                    entryInfo.profileIcon.toBitmap().asImageBitmap()
-                } else {
-                    requestDisplayInfo.typeIcon.toBitmap().asImageBitmap()
-                },
-                contentDescription = null,
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-                modifier = Modifier.padding(start = 10.dp).size(32.dp)
-            )
+            if (entryInfo is CreateOptionInfo && entryInfo.profileIcon != null) {
+                Image(
+                    bitmap = entryInfo.profileIcon.toBitmap().asImageBitmap(),
+                    contentDescription = null,
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                )
+            } else {
+                Icon(
+                    bitmap = requestDisplayInfo.typeIcon.toBitmap().asImageBitmap(),
+                    contentDescription = null,
+                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                )
+            }
         },
         label = {
             Column() {
@@ -763,9 +739,9 @@
                         )
                         TextSecondary(
                             text = if (requestDisplayInfo.subtitle != null) {
-                                stringResource(
+                                requestDisplayInfo.subtitle + " • " + stringResource(
                                     R.string.passkey_before_subtitle
-                                ) + " - " + requestDisplayInfo.subtitle
+                                )
                             } else {
                                 stringResource(R.string.passkey_before_subtitle)
                             },
@@ -787,11 +763,25 @@
                         )
                     }
                     else -> {
-                        TextOnSurfaceVariant(
-                            text = requestDisplayInfo.title,
-                            style = MaterialTheme.typography.titleLarge,
-                            modifier = Modifier.padding(top = 16.dp, bottom = 16.dp, start = 5.dp),
-                        )
+                        if (requestDisplayInfo.subtitle != null) {
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.title,
+                                style = MaterialTheme.typography.titleLarge,
+                                modifier = Modifier.padding(top = 16.dp, start = 5.dp),
+                            )
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.subtitle,
+                                style = MaterialTheme.typography.bodyMedium,
+                                modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                            )
+                        } else {
+                            TextOnSurfaceVariant(
+                                text = requestDisplayInfo.title,
+                                style = MaterialTheme.typography.titleLarge,
+                                modifier = Modifier.padding(
+                                    top = 16.dp, bottom = 16.dp, start = 5.dp),
+                            )
+                        }
                     }
                 }
             }
@@ -802,6 +792,7 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MoreOptionsInfoRow(
+    requestDisplayInfo: RequestDisplayInfo,
     providerInfo: EnabledProviderInfo,
     createOptionInfo: CreateOptionInfo,
     onOptionSelected: () -> Unit
@@ -826,50 +817,67 @@
                     TextSecondary(
                         text = createOptionInfo.userProviderDisplayName,
                         style = MaterialTheme.typography.bodyMedium,
-                        // 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),
+                        modifier = Modifier.padding(start = 5.dp),
                     )
                 }
-                if (createOptionInfo.passwordCount != null &&
-                    createOptionInfo.passkeyCount != null
-                ) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passwords_passkeys,
-                            createOptionInfo.passwordCount,
-                            createOptionInfo.passkeyCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.passwordCount != null) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passwords,
-                            createOptionInfo.passwordCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.passkeyCount != null) {
-                    TextSecondary(
-                        text =
-                        stringResource(
-                            R.string.more_options_usage_passkeys,
-                            createOptionInfo.passkeyCount
-                        ),
-                        style = MaterialTheme.typography.bodyMedium,
-                        modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
-                    )
-                } else if (createOptionInfo.totalCredentialCount != null) {
-                    // TODO: Handle the case when there is total count
-                    // but no passwords and passkeys after design is set
+                if (requestDisplayInfo.type == TYPE_PUBLIC_KEY_CREDENTIAL ||
+                    requestDisplayInfo.type == TYPE_PASSWORD_CREDENTIAL) {
+                    if (createOptionInfo.passwordCount != null &&
+                        createOptionInfo.passkeyCount != null
+                    ) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passwords_passkeys,
+                                createOptionInfo.passwordCount,
+                                createOptionInfo.passkeyCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else if (createOptionInfo.passwordCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passwords,
+                                createOptionInfo.passwordCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else if (createOptionInfo.passkeyCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_passkeys,
+                                createOptionInfo.passkeyCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else {
+                        Divider(
+                            thickness = 16.dp,
+                            color = Color.Transparent,
+                        )
+                    }
+                } else {
+                    if (createOptionInfo.totalCredentialCount != null) {
+                        TextSecondary(
+                            text =
+                            stringResource(
+                                R.string.more_options_usage_credentials,
+                                createOptionInfo.totalCredentialCount
+                            ),
+                            style = MaterialTheme.typography.bodyMedium,
+                            modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
+                        )
+                    } else {
+                        Divider(
+                            thickness = 16.dp,
+                            color = Color.Transparent,
+                        )
+                    }
                 }
             }
         }
@@ -901,7 +909,7 @@
                     )
                     // TODO: Update the subtitle once design is confirmed
                     TextSecondary(
-                        text = disabledProviders.joinToString(separator = ", ") { it.displayName },
+                        text = disabledProviders.joinToString(separator = " • ") { it.displayName },
                         style = MaterialTheme.typography.bodyMedium,
                         modifier = Modifier.padding(bottom = 16.dp, start = 5.dp),
                     )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
index 9d029dff..7b9e113 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialViewModel.kt
@@ -39,18 +39,39 @@
   val disabledProviders: List<DisabledProviderInfo>? = null,
   val currentScreenState: CreateScreenState,
   val requestDisplayInfo: RequestDisplayInfo,
-  val showActiveEntryOnly: Boolean,
+  // Should not change with the real time update of default provider, only determine whether we're
+  // showing provider selection page at the beginning
+  val hasDefaultProvider: Boolean,
   val activeEntry: ActiveEntry? = null,
   val selectedEntry: EntryInfo? = null,
   val hidden: Boolean = false,
   val providerActivityPending: Boolean = false,
+  val isFromProviderSelection: Boolean? = null,
 )
 
 class CreateCredentialViewModel(
-  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance()
+  credManRepo: CredentialManagerRepo = CredentialManagerRepo.getInstance(),
+  userConfigRepo: UserConfigRepo = UserConfigRepo.getInstance()
 ) : ViewModel() {
 
-  var uiState by mutableStateOf(credManRepo.createCredentialInitialUiState())
+  var providerEnableListUiState = credManRepo.getCreateProviderEnableListInitialUiState()
+
+  var providerDisableListUiState = credManRepo.getCreateProviderDisableListInitialUiState()
+
+  var requestDisplayInfoUiState = credManRepo.getCreateRequestDisplayInfoInitialUiState()
+
+  var defaultProviderId = userConfigRepo.getDefaultProviderId()
+
+  var isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse()
+
+  var uiState by mutableStateOf(
+    CreateFlowUtils.toCreateCredentialUiState(
+      providerEnableListUiState,
+      providerDisableListUiState,
+      defaultProviderId,
+      requestDisplayInfoUiState,
+      false,
+      isPasskeyFirstUse))
     private set
 
   val dialogResult: MutableLiveData<DialogResult> by lazy {
@@ -63,9 +84,9 @@
 
   fun onConfirmIntro() {
     uiState = CreateFlowUtils.toCreateCredentialUiState(
-      uiState.enabledProviders, uiState.disabledProviders,
-      uiState.requestDisplayInfo, true)
-    UserConfigRepo.getInstance().setIsFirstUse(false)
+      providerEnableListUiState, providerDisableListUiState, defaultProviderId,
+      requestDisplayInfoUiState, true, isPasskeyFirstUse)
+    UserConfigRepo.getInstance().setIsPasskeyFirstUse(false)
   }
 
   fun getProviderInfoByName(providerName: String): EnabledProviderInfo {
@@ -74,22 +95,35 @@
     }
   }
 
-  fun onMoreOptionsSelected() {
+  fun onMoreOptionsSelectedOnProviderSelection() {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+      isFromProviderSelection = true
     )
   }
 
-  fun onBackButtonSelected() {
+  fun onMoreOptionsSelectedOnCreationSelection() {
     uiState = uiState.copy(
-        currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
+      currentScreenState = CreateScreenState.MORE_OPTIONS_SELECTION,
+      isFromProviderSelection = false
+    )
+  }
+
+  fun onBackProviderSelectionButtonSelected() {
+    uiState = uiState.copy(
+        currentScreenState = CreateScreenState.PROVIDER_SELECTION,
+    )
+  }
+
+  fun onBackCreationSelectionButtonSelected() {
+    uiState = uiState.copy(
+      currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
     )
   }
 
   fun onEntrySelectedFromMoreOptionScreen(activeEntry: ActiveEntry) {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.MORE_OPTIONS_ROW_INTRO,
-      showActiveEntryOnly = false,
       activeEntry = activeEntry
     )
   }
@@ -97,7 +131,6 @@
   fun onEntrySelectedFromFirstUseScreen(activeEntry: ActiveEntry) {
     uiState = uiState.copy(
       currentScreenState = CreateScreenState.CREATION_OPTION_SELECTION,
-      showActiveEntryOnly = true,
       activeEntry = activeEntry
     )
     val providerId = uiState.activeEntry?.activeProvider?.name
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index fda0b97..58db36c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -55,7 +55,7 @@
   pendingIntent: PendingIntent?,
   fillInIntent: Intent?,
   val userProviderDisplayName: String?,
-  val profileIcon: Drawable,
+  val profileIcon: Drawable?,
   val passwordCount: Int?,
   val passkeyCount: Int?,
   val totalCredentialCount: Int?,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index 619f5a3..ac0db5a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -62,7 +62,7 @@
 import com.android.credentialmanager.common.material.ModalBottomSheetLayout
 import com.android.credentialmanager.common.material.ModalBottomSheetValue
 import com.android.credentialmanager.common.material.rememberModalBottomSheetState
-import com.android.credentialmanager.common.ui.CancelButton
+import com.android.credentialmanager.common.ui.ActionButton
 import com.android.credentialmanager.common.ui.Entry
 import com.android.credentialmanager.common.ui.TextOnSurface
 import com.android.credentialmanager.common.ui.TextSecondary
@@ -96,7 +96,6 @@
                             requestDisplayInfo = uiState.requestDisplayInfo,
                             providerDisplayInfo = uiState.providerDisplayInfo,
                             onEntrySelected = viewModel::onEntrySelected,
-                            onCancel = viewModel::onCancel,
                             onMoreOptionSelected = viewModel::onMoreOptionSelected,
                         )
                     } else {
@@ -135,7 +134,6 @@
     requestDisplayInfo: RequestDisplayInfo,
     providerDisplayInfo: ProviderDisplayInfo,
     onEntrySelected: (EntryInfo) -> Unit,
-    onCancel: () -> Unit,
     onMoreOptionSelected: () -> Unit,
 ) {
     val sortedUserNameToCredentialEntryList =
@@ -148,13 +146,19 @@
                 textAlign = TextAlign.Center,
                 style = MaterialTheme.typography.headlineSmall,
                 text = stringResource(
-                    if (sortedUserNameToCredentialEntryList.size == 1) {
-                        if (sortedUserNameToCredentialEntryList.first().sortedCredentialEntryList
-                                .first().credentialType
+                    if (sortedUserNameToCredentialEntryList
+                            .size == 1 && authenticationEntryList.isEmpty()
+                    ) {
+                        if (sortedUserNameToCredentialEntryList.first()
+                                .sortedCredentialEntryList.first().credentialType
                             == PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL
-                        )
-                            R.string.get_dialog_title_use_passkey_for
+                        ) R.string.get_dialog_title_use_passkey_for
                         else R.string.get_dialog_title_use_sign_in_for
+                    } else if (
+                        sortedUserNameToCredentialEntryList
+                            .isEmpty() && authenticationEntryList.size == 1
+                    ) {
+                        R.string.get_dialog_title_use_sign_in_for
                     } else R.string.get_dialog_title_choose_sign_in_for,
                     requestDisplayInfo.appDomainName
                 ),
@@ -166,23 +170,46 @@
                     .padding(horizontal = 24.dp)
                     .align(alignment = Alignment.CenterHorizontally)
             ) {
+                val usernameForCredentialSize = sortedUserNameToCredentialEntryList
+                    .size
+                val authenticationEntrySize = authenticationEntryList.size
                 LazyColumn(
                     verticalArrangement = Arrangement.spacedBy(2.dp)
                 ) {
-                    items(sortedUserNameToCredentialEntryList) {
-                        CredentialEntryRow(
-                            credentialEntryInfo = it.sortedCredentialEntryList.first(),
-                            onEntrySelected = onEntrySelected,
-                        )
-                    }
-                    items(authenticationEntryList) {
-                        AuthenticationEntryRow(
-                            authenticationEntryInfo = it,
-                            onEntrySelected = onEntrySelected,
-                        )
-                    }
-                    item {
-                        SignInAnotherWayRow(onSelect = onMoreOptionSelected)
+                    // Show max 4 entries in this primary page
+                    if (usernameForCredentialSize + authenticationEntrySize <= 4) {
+                        items(sortedUserNameToCredentialEntryList) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                        items(authenticationEntryList) {
+                            AuthenticationEntryRow(
+                                authenticationEntryInfo = it,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    } else if (usernameForCredentialSize < 4) {
+                        items(sortedUserNameToCredentialEntryList) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                        items(authenticationEntryList.take(4 - usernameForCredentialSize)) {
+                            AuthenticationEntryRow(
+                                authenticationEntryInfo = it,
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
+                    } else {
+                        items(sortedUserNameToCredentialEntryList.take(4)) {
+                            CredentialEntryRow(
+                                credentialEntryInfo = it.sortedCredentialEntryList.first(),
+                                onEntrySelected = onEntrySelected,
+                            )
+                        }
                     }
                 }
             }
@@ -194,7 +221,9 @@
                 horizontalArrangement = Arrangement.SpaceBetween,
                 modifier = Modifier.fillMaxWidth().padding(horizontal = 24.dp)
             ) {
-                CancelButton(stringResource(R.string.get_dialog_button_label_no_thanks), onCancel)
+                ActionButton(
+                    stringResource(R.string.get_dialog_use_saved_passkey_for),
+                    onMoreOptionSelected)
             }
             Divider(
                 thickness = 18.dp,
@@ -260,7 +289,7 @@
                         )
                     }
                     // Locked password manager
-                    if (!authenticationEntryList.isEmpty()) {
+                    if (authenticationEntryList.isNotEmpty()) {
                         item {
                             LockedCredentials(
                                 authenticationEntryList = authenticationEntryList,
@@ -425,13 +454,22 @@
     Entry(
         onClick = { onEntrySelected(credentialEntryInfo) },
         icon = {
-            Icon(
-                modifier = Modifier.padding(start = 10.dp).size(32.dp),
-                bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
-                // TODO: add description.
-                contentDescription = "",
-                tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-            )
+            if (credentialEntryInfo.icon != null) {
+                Image(
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                    bitmap = credentialEntryInfo.icon.toBitmap().asImageBitmap(),
+                    // TODO: add description.
+                    contentDescription = "",
+                )
+            } else {
+                Icon(
+                    modifier = Modifier.padding(start = 10.dp).size(32.dp),
+                    painter = painterResource(R.drawable.ic_other_sign_in),
+                    // TODO: add description.
+                    contentDescription = "",
+                    tint = LocalAndroidColorScheme.current.colorAccentPrimaryVariant
+                )
+            }
         },
         label = {
             Column() {
@@ -544,49 +582,35 @@
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun SignInAnotherWayRow(onSelect: () -> Unit) {
-    Entry(
-        onClick = onSelect,
-        label = {
-            TextOnSurfaceVariant(
-                text = stringResource(R.string.get_dialog_use_saved_passkey_for),
-                style = MaterialTheme.typography.titleLarge,
-                modifier = Modifier.padding(vertical = 16.dp)
-            )
-        }
-    )
-}
-
-@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),
+    Snackbar(
+        modifier = Modifier.padding(horizontal = 40.dp).padding(top = 700.dp),
         shape = EntryShape.FullMediumRoundedCorner,
         containerColor = LocalAndroidColorScheme.current.colorBackground,
         contentColor = LocalAndroidColorScheme.current.colorAccentPrimaryVariant,
-    ) {
-        Row(
-            horizontalArrangement = Arrangement.SpaceBetween,
-            verticalAlignment = Alignment.CenterVertically,
-        ) {
+        action = {
             TextButton(
-                onClick = {onClick(true)},
+                onClick = { onClick(true) },
             ) {
-                Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
+                Text(text = stringResource(R.string.snackbar_action))
             }
+        },
+        dismissAction = {
             IconButton(onClick = onCancel) {
                 Icon(
                     Icons.Filled.Close,
                     contentDescription = stringResource(
                         R.string.accessibility_close_button
-                    )
+                    ),
+                    tint = LocalAndroidColorScheme.current.colorAccentTertiary
                 )
             }
-        }
+        },
+    ) {
+        Text(text = stringResource(R.string.get_dialog_use_saved_passkey_for))
     }
 }
\ 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 c182397..294e468 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialViewModel.kt
@@ -182,7 +182,7 @@
   Preconditions.checkState(remoteEntryList.size <= 1)
 
   // Compose sortedUserNameToCredentialEntryList
-  val comparator = CredentialEntryInfoComparator()
+  val comparator = CredentialEntryInfoComparatorByTypeThenTimestamp()
   // Sort per username
   userNameToCredentialEntryMap.values.forEach {
     it.sortWith(comparator)
@@ -191,7 +191,7 @@
   val sortedUserNameToCredentialEntryList = userNameToCredentialEntryMap.map {
     PerUserNameCredentialEntryList(it.key, it.value)
   }.sortedWith(
-    compareBy(comparator) { it.sortedCredentialEntryList.first() }
+    compareByDescending{ it.sortedCredentialEntryList.first().lastUsedTimeMillis }
   )
 
   return ProviderDisplayInfo(
@@ -219,7 +219,7 @@
     GetScreenState.REMOTE_ONLY else GetScreenState.PRIMARY_SELECTION
 }
 
-internal class CredentialEntryInfoComparator : Comparator<CredentialEntryInfo> {
+internal class CredentialEntryInfoComparatorByTypeThenTimestamp : Comparator<CredentialEntryInfo> {
   override fun compare(p0: CredentialEntryInfo, p1: CredentialEntryInfo): Int {
     // First prefer passkey type for its security benefits
     if (p0.credentialType != p1.credentialType) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 3a2a738..60939b5 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -67,7 +67,7 @@
   val credentialTypeDisplayName: String,
   val userName: String,
   val displayName: String?,
-  val icon: Drawable,
+  val icon: Drawable?,
   val lastUsedTimeMillis: Long?,
 ) : EntryInfo(providerId, entryKey, entrySubkey, pendingIntent, fillInIntent)
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.kt
new file mode 100644
index 0000000..1658858
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PasswordCredential.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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+class PasswordCredential constructor(
+        val id: String,
+        val password: String,
+) : Credential(android.credentials.Credential.TYPE_PASSWORD_CREDENTIAL, toBundle(id, password)) {
+
+    init {
+        require(password.isNotEmpty()) { "password should not be empty" }
+    }
+
+    /** @hide */
+    companion object {
+
+        const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
+        const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
+
+        @JvmStatic
+        internal fun toBundle(id: String, password: String): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_ID, id)
+            bundle.putString(BUNDLE_KEY_PASSWORD, password)
+            return bundle
+        }
+
+        @JvmStatic
+        internal fun createFrom(data: Bundle): PasswordCredential {
+            try {
+                val id = data.getString(BUNDLE_KEY_ID)
+                val password = data.getString(BUNDLE_KEY_PASSWORD)
+                return PasswordCredential(id!!, password!!)
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt
new file mode 100644
index 0000000..1abf911
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/Action.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack.provider
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
+import android.net.Uri
+import android.util.Log
+import java.util.Collections
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class Action constructor(
+        val title: CharSequence,
+        val subTitle: CharSequence?,
+        val pendingIntent: PendingIntent?,
+) {
+
+  init {
+    require(title.isNotEmpty()) { "title must not be empty" }
+  }
+
+  companion object {
+    private const val TAG = "Action"
+    internal const val SLICE_HINT_TITLE =
+            "androidx.credentials.provider.action.HINT_ACTION_TITLE"
+    internal const val SLICE_HINT_SUBTITLE =
+            "androidx.credentials.provider.action.HINT_ACTION_SUBTEXT"
+    internal const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.action.SLICE_HINT_PENDING_INTENT"
+
+    @JvmStatic
+    fun toSlice(action: Action): Slice {
+      // TODO("Put the right spec and version value")
+      val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
+              .addText(action.title, /*subType=*/null,
+                      listOf(SLICE_HINT_TITLE))
+              .addText(action.subTitle, /*subType=*/null,
+                      listOf(SLICE_HINT_SUBTITLE))
+      if (action.pendingIntent != null) {
+        sliceBuilder.addAction(action.pendingIntent,
+                Slice.Builder(sliceBuilder)
+                        .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
+                        .build(),
+                /*subType=*/null)
+      }
+      return sliceBuilder.build()
+    }
+
+    /**
+     * Returns an instance of [Action] derived from a [Slice] object.
+     *
+     * @param slice the [Slice] object constructed through [toSlice]
+     */
+    @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+    @JvmStatic
+    fun fromSlice(slice: Slice): Action? {
+      // TODO("Put the right spec and version value")
+      var title: CharSequence = ""
+      var subTitle: CharSequence? = null
+      var pendingIntent: PendingIntent? = null
+
+      slice.items.forEach {
+        if (it.hasHint(SLICE_HINT_TITLE)) {
+          title = it.text
+        } else if (it.hasHint(SLICE_HINT_SUBTITLE)) {
+          subTitle = it.text
+        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
+          pendingIntent = it.action
+        }
+      }
+
+      return try {
+        Action(title, subTitle, pendingIntent)
+      } catch (e: Exception) {
+        Log.i(TAG, "fromSlice failed with: " + e.message)
+        null
+      }
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
deleted file mode 100644
index 19c5c2d..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
+++ /dev/null
@@ -1,48 +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.credentialmanager.jetpack.provider
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class ActionUi(
-  val text: CharSequence,
-  val subtext: CharSequence?,
-) {
-  companion object {
-    fun fromSlice(slice: Slice): ActionUi {
-      var text: CharSequence? = null
-      var subtext: CharSequence? = null
-
-      val items = slice.items
-      items.forEach {
-        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(text!!, subtext)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt
new file mode 100644
index 0000000..bed02f8
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CreateEntry.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack.provider
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.Bundle
+import android.util.Log
+import java.util.Collections
+
+/**
+ * UI representation for a save entry used during the create credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+class CreateEntry internal constructor(
+        val accountName: CharSequence,
+        val pendingIntent: PendingIntent?,
+        val icon: Icon?,
+        val lastUsedTimeMillis: Long,
+        val credentialCountInformationList: List<CredentialCountInformation>
+) {
+
+  init {
+    require(accountName.isNotEmpty()) { "accountName must not be empty" }
+  }
+
+  /**
+   * A builder for [CreateEntry]
+   *
+   * @property accountName the name of the account where the credential will be registered
+   * @property pendingIntent the [PendingIntent] that will be fired when the user selects
+   * this entry
+   *
+   * @hide
+   */
+  class Builder constructor(
+          private val accountName: CharSequence,
+          private val pendingIntent: PendingIntent? = null
+  ) {
+
+    private var credentialCountInformationList: MutableList<CredentialCountInformation> =
+            mutableListOf()
+    private var icon: Icon? = null
+    private var lastUsedTimeMillis: Long = 0
+
+    /** Adds a [CredentialCountInformation] denoting a given credential
+     * type and the count of credentials that the provider has stored for that
+     * credential type.
+     *
+     * This information will be displayed on the [CreateEntry] to help the user
+     * make a choice.
+     */
+    @Suppress("MissingGetterMatchingBuilder")
+    fun addCredentialCountInformation(info: CredentialCountInformation): Builder {
+      credentialCountInformationList.add(info)
+      return this
+    }
+
+    /** Sets a list of [CredentialCountInformation]. Each item in the list denotes a given
+     * credential type and the count of credentials that the provider has stored of that
+     * credential type.
+     *
+     * This information will be displayed on the [CreateEntry] to help the user
+     * make a choice.
+     */
+    fun setCredentialCountInformationList(infoList: List<CredentialCountInformation>): Builder {
+      credentialCountInformationList = infoList as MutableList<CredentialCountInformation>
+      return this
+    }
+
+    /** Sets an icon to be displayed with the entry on the UI */
+    fun setIcon(icon: Icon?): Builder {
+      this.icon = icon
+      return this
+    }
+
+    /** Sets the last time this account was used */
+    fun setLastUsedTimeMillis(lastUsedTimeMillis: Long): Builder {
+      this.lastUsedTimeMillis = lastUsedTimeMillis
+      return this
+    }
+
+    /**
+     * Builds an instance of [CreateEntry]
+     *
+     * @throws IllegalArgumentException If [accountName] is empty
+     */
+    fun build(): CreateEntry {
+      return CreateEntry(accountName, pendingIntent, icon, lastUsedTimeMillis,
+              credentialCountInformationList)
+    }
+  }
+
+  companion object {
+    private const val TAG = "CreateEntry"
+    internal const val SLICE_HINT_ACCOUNT_NAME =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
+    internal const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
+    internal const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
+    internal const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+    internal const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
+
+    @JvmStatic
+    fun toSlice(createEntry: CreateEntry): Slice {
+      // TODO("Use the right type and revision")
+      val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec("type", 1))
+      sliceBuilder.addText(createEntry.accountName, /*subType=*/null,
+              listOf(SLICE_HINT_ACCOUNT_NAME))
+              .addLong(createEntry.lastUsedTimeMillis, /*subType=*/null, listOf(
+                      SLICE_HINT_LAST_USED_TIME_MILLIS))
+      if (createEntry.icon != null) {
+        sliceBuilder.addIcon(createEntry.icon, /*subType=*/null,
+                listOf(SLICE_HINT_ICON))
+      }
+
+      val credentialCountBundle = convertCredentialCountInfoToBundle(
+              createEntry.credentialCountInformationList)
+      if (credentialCountBundle != null) {
+        sliceBuilder.addBundle(convertCredentialCountInfoToBundle(
+                createEntry.credentialCountInformationList), null, listOf(
+                SLICE_HINT_CREDENTIAL_COUNT_INFORMATION))
+      }
+      if (createEntry.pendingIntent != null) {
+        sliceBuilder.addAction(createEntry.pendingIntent,
+                Slice.Builder(sliceBuilder)
+                        .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
+                        .build(),
+                /*subType=*/null)
+      }
+      return sliceBuilder.build()
+    }
+
+    /**
+     * Returns an instance of [CreateEntry] derived from a [Slice] object.
+     *
+     * @param slice the [Slice] object constructed through [toSlice]
+     */
+    @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+    @JvmStatic
+    fun fromSlice(slice: Slice): CreateEntry? {
+      // TODO("Put the right spec and version value")
+      var accountName: CharSequence = ""
+      var icon: Icon? = null
+      var pendingIntent: PendingIntent? = null
+      var credentialCountInfo: List<CredentialCountInformation> = listOf()
+      var lastUsedTimeMillis: Long = 0
+
+      slice.items.forEach {
+        if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
+          accountName = it.text
+        } else if (it.hasHint(SLICE_HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
+          pendingIntent = it.action
+        } else if (it.hasHint(SLICE_HINT_CREDENTIAL_COUNT_INFORMATION)) {
+          credentialCountInfo = convertBundleToCredentialCountInfo(it.bundle)
+        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
+          lastUsedTimeMillis = it.long
+        }
+      }
+
+      return try {
+        CreateEntry(accountName, pendingIntent, icon,
+                lastUsedTimeMillis, credentialCountInfo)
+      } catch (e: Exception) {
+        Log.i(TAG, "fromSlice failed with: " + e.message)
+        null
+      }
+    }
+
+    @JvmStatic
+    internal fun convertBundleToCredentialCountInfo(bundle: Bundle?):
+            List<CredentialCountInformation> {
+      val credentialCountList = ArrayList<CredentialCountInformation>()
+      if (bundle == null) {
+        return credentialCountList
+      }
+      bundle.keySet().forEach {
+        try {
+          credentialCountList.add(
+                  CredentialCountInformation(it, bundle.getInt(it)))
+        } catch (e: Exception) {
+          Log.i(TAG, "Issue unpacking credential count info bundle: " + e.message)
+        }
+      }
+      return credentialCountList
+    }
+
+    @JvmStatic
+    internal fun convertCredentialCountInfoToBundle(
+            credentialCountInformationList: List<CredentialCountInformation>
+    ): Bundle? {
+      if (credentialCountInformationList.isEmpty()) {
+        return null
+      }
+      val bundle = Bundle()
+      credentialCountInformationList.forEach {
+        bundle.putInt(it.type, it.count)
+      }
+      return bundle
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt
new file mode 100644
index 0000000..aa77b74
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialCountInformation.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.credentialmanager.jetpack.provider
+
+import android.credentials.Credential
+import com.android.credentialmanager.jetpack.developer.PublicKeyCredential
+
+class CredentialCountInformation constructor(
+        val type: String,
+        val count: Int
+) {
+    companion object {
+        @JvmStatic
+        fun createPasswordCountInformation(count: Int): CredentialCountInformation {
+            return CredentialCountInformation(Credential.TYPE_PASSWORD_CREDENTIAL, count)
+        }
+
+        @JvmStatic
+        fun getPasswordCount(infos: List<CredentialCountInformation>): Int? {
+            return getCountForType(infos, Credential.TYPE_PASSWORD_CREDENTIAL)
+        }
+
+        @JvmStatic
+        fun createPublicKeyCountInformation(count: Int): CredentialCountInformation {
+            return CredentialCountInformation(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL, count)
+        }
+
+        @JvmStatic
+        fun getPasskeyCount(infos: List<CredentialCountInformation>): Int? {
+            return getCountForType(infos, PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
+        }
+
+        @JvmStatic
+        fun createTotalCountInformation(count: Int): CredentialCountInformation {
+            return CredentialCountInformation("TOTAL_COUNT", count)
+        }
+
+        @JvmStatic
+        fun getTotalCount(infos: List<CredentialCountInformation>): Int? {
+            return getCountForType(infos, "TOTAL_COUNT")
+        }
+
+        private fun getCountForType(infos: List<CredentialCountInformation>, type: String): Int? {
+            return infos.firstOrNull { info -> info.type == type }?.count
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.kt
new file mode 100644
index 0000000..61a104b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntry.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.credentialmanager.jetpack.provider
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.app.slice.Slice
+import android.app.slice.SliceSpec
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.util.Log
+import java.util.Collections
+
+/**
+ * UI representation for a credential entry used during the get credential flow.
+ *
+ * TODO: move to jetpack.
+ */
+open class CredentialEntry constructor(
+        // TODO("Add credential type display name for both CredentialEntry & CreateEntry")
+        val type: String,
+        val typeDisplayName: CharSequence,
+        val username: CharSequence,
+        val displayName: CharSequence?,
+        val pendingIntent: PendingIntent?,
+        // TODO("Consider using Instant or other strongly typed time data type")
+        val lastUsedTimeMillis: Long,
+        val icon: Icon?,
+        var autoSelectAllowed: Boolean
+) {
+  init {
+    require(type.isNotEmpty()) { "type must not be empty" }
+    require(username.isNotEmpty()) { "type must not be empty" }
+  }
+
+  companion object {
+    private const val TAG = "CredentialEntry"
+    internal const val SLICE_HINT_TYPE_DISPLAY_NAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
+    internal const val SLICE_HINT_USERNAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
+    internal const val SLICE_HINT_DISPLAYNAME =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
+    internal const val SLICE_HINT_LAST_USED_TIME_MILLIS =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
+    internal const val SLICE_HINT_ICON =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
+    internal const val SLICE_HINT_PENDING_INTENT =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
+    internal const val SLICE_HINT_AUTO_ALLOWED =
+            "androidx.credentials.provider.credentialEntry.SLICE_HINT_AUTO_ALLOWED"
+    internal const val AUTO_SELECT_TRUE_STRING = "true"
+    internal const val AUTO_SELECT_FALSE_STRING = "false"
+
+    @JvmStatic
+    internal fun toSlice(credentialEntry: CredentialEntry): Slice {
+      // TODO("Put the right revision value")
+      val autoSelectAllowed = if (credentialEntry.autoSelectAllowed) {
+        AUTO_SELECT_TRUE_STRING
+      } else {
+        AUTO_SELECT_FALSE_STRING
+      }
+      val sliceBuilder = Slice.Builder(Uri.EMPTY, SliceSpec(
+              credentialEntry.type, 1))
+              .addText(credentialEntry.typeDisplayName, /*subType=*/null,
+                      listOf(SLICE_HINT_TYPE_DISPLAY_NAME))
+              .addText(credentialEntry.username, /*subType=*/null,
+                      listOf(SLICE_HINT_USERNAME))
+              .addText(credentialEntry.displayName, /*subType=*/null,
+                      listOf(SLICE_HINT_DISPLAYNAME))
+              .addLong(credentialEntry.lastUsedTimeMillis, /*subType=*/null,
+                      listOf(SLICE_HINT_LAST_USED_TIME_MILLIS))
+              .addText(autoSelectAllowed, /*subType=*/null,
+                      listOf(SLICE_HINT_AUTO_ALLOWED))
+      if (credentialEntry.icon != null) {
+        sliceBuilder.addIcon(credentialEntry.icon, /*subType=*/null,
+                listOf(SLICE_HINT_ICON))
+      }
+      if (credentialEntry.pendingIntent != null) {
+        sliceBuilder.addAction(credentialEntry.pendingIntent,
+                Slice.Builder(sliceBuilder)
+                        .addHints(Collections.singletonList(SLICE_HINT_PENDING_INTENT))
+                        .build(),
+                /*subType=*/null)
+      }
+      return sliceBuilder.build()
+    }
+
+    /**
+     * Returns an instance of [CredentialEntry] derived from a [Slice] object.
+     *
+     * @param slice the [Slice] object constructed through [toSlice]
+     */
+    @SuppressLint("WrongConstant") // custom conversion between jetpack and framework
+    @JvmStatic
+    fun fromSlice(slice: Slice): CredentialEntry? {
+      var typeDisplayName: CharSequence? = null
+      var username: CharSequence? = null
+      var displayName: CharSequence? = null
+      var icon: Icon? = null
+      var pendingIntent: PendingIntent? = null
+      var lastUsedTimeMillis: Long = 0
+      var autoSelectAllowed = false
+
+      slice.items.forEach {
+        if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
+          typeDisplayName = it.text
+        } else if (it.hasHint(SLICE_HINT_USERNAME)) {
+          username = it.text
+        } else if (it.hasHint(SLICE_HINT_DISPLAYNAME)) {
+          displayName = it.text
+        } else if (it.hasHint(SLICE_HINT_ICON)) {
+          icon = it.icon
+        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
+          pendingIntent = it.action
+        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
+          lastUsedTimeMillis = it.long
+        } else if (it.hasHint(SLICE_HINT_AUTO_ALLOWED)) {
+          val autoSelectValue = it.text
+          if (autoSelectValue == AUTO_SELECT_TRUE_STRING) {
+            autoSelectAllowed = true
+          }
+        }
+      }
+
+      return try {
+        CredentialEntry(slice.spec!!.type, typeDisplayName!!, username!!,
+                displayName, pendingIntent,
+                lastUsedTimeMillis, icon, autoSelectAllowed)
+      } catch (e: Exception) {
+        Log.i(TAG, "fromSlice failed with: " + e.message)
+        null
+      }
+    }
+  }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
deleted file mode 100644
index 47b5af0..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
+++ /dev/null
@@ -1,89 +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.credentialmanager.jetpack.provider
-
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a credential entry used during the get credential flow.
- *
- * TODO: move to jetpack.
- */
-class CredentialEntryUi(
-  val credentialType: CharSequence,
-  val credentialTypeDisplayName: CharSequence,
-  val userName: CharSequence,
-  val userDisplayName: CharSequence?,
-  val entryIcon: Icon?,
-  val lastUsedTimeMillis: Long?,
-  // TODO: Remove note
-  val note: CharSequence?,
-) {
-  companion object {
-    // Copied over from jetpack
-    const val SLICE_HINT_TYPE_DISPLAY_NAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_TYPE_DISPLAY_NAME"
-    const val SLICE_HINT_USERNAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_USER_NAME"
-    const val SLICE_HINT_DISPLAYNAME =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_CREDENTIAL_TYPE_DISPLAY_NAME"
-    const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-    const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PROFILE_ICON"
-    const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.credentialEntry.SLICE_HINT_PENDING_INTENT"
-
-    /**
-     * Returns an instance of [CredentialEntryUi] derived from a [Slice] object.
-     *
-     * @param slice the [Slice] object constructed through jetpack library
-     */
-    @JvmStatic
-    fun fromSlice(slice: Slice): CredentialEntryUi {
-      var username: CharSequence? = null
-      var displayName: CharSequence = ""
-      var icon: Icon? = null
-      var pendingIntent: PendingIntent? = null
-      var lastUsedTimeMillis: Long = 0
-      var note: CharSequence? = null
-      var typeDisplayName: CharSequence = ""
-
-      slice.items.forEach {
-        if (it.hasHint(SLICE_HINT_TYPE_DISPLAY_NAME)) {
-          typeDisplayName = it.text
-        } else if (it.hasHint(SLICE_HINT_USERNAME)) {
-          username = it.text
-        } else if (it.hasHint(SLICE_HINT_DISPLAYNAME)) {
-          displayName = it.text
-        } else if (it.hasHint(SLICE_HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
-          pendingIntent = it.action
-        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        }
-      }
-      return CredentialEntryUi(
-              slice.spec!!.type, typeDisplayName, username!!, displayName, icon,
-              lastUsedTimeMillis, note,
-      )
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
deleted file mode 100644
index 313f0f9..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
+++ /dev/null
@@ -1,80 +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.credentialmanager.jetpack.provider
-
-import android.app.PendingIntent
-import android.app.slice.Slice
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class SaveEntryUi(
-  val userProviderAccountName: CharSequence?,
-  val credentialTypeIcon: Icon?,
-  val profileIcon: Icon?,
-  val passwordCount: Int?,
-  val passkeyCount: Int?,
-  val totalCredentialCount: Int?,
-  val lastUsedTimeMillis: Long?,
-) {
-  companion object {
-    const val SLICE_HINT_ACCOUNT_NAME =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_USER_PROVIDER_ACCOUNT_NAME"
-    const val SLICE_HINT_ICON =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_PROFILE_ICON"
-    const val SLICE_HINT_CREDENTIAL_COUNT_INFORMATION =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_CREDENTIAL_COUNT_INFORMATION"
-    const val SLICE_HINT_LAST_USED_TIME_MILLIS =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_LAST_USED_TIME_MILLIS"
-    const val SLICE_HINT_PENDING_INTENT =
-            "androidx.credentials.provider.createEntry.SLICE_HINT_PENDING_INTENT"
-
-    /**
-     * Returns an instance of [SaveEntryUi] derived from a [Slice] object.
-     *
-     * @param slice the [Slice] object constructed through the jetpack library
-     */
-    @JvmStatic
-    fun fromSlice(slice: Slice): SaveEntryUi {
-      var accountName: CharSequence? = null
-      var icon: Icon? = null
-      var pendingIntent: PendingIntent? = null
-      var lastUsedTimeMillis: Long = 0
-
-      slice.items.forEach {
-        if (it.hasHint(SLICE_HINT_ACCOUNT_NAME)) {
-          accountName = it.text
-        } else if (it.hasHint(SLICE_HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(SLICE_HINT_PENDING_INTENT)) {
-          pendingIntent = it.action
-        } else if (it.hasHint(SLICE_HINT_LAST_USED_TIME_MILLIS)) {
-          lastUsedTimeMillis = it.long
-        }
-      }
-
-      return SaveEntryUi(
-              // TODO: Add count parsing
-              accountName!!, icon, icon,
-              0, 0, 0, lastUsedTimeMillis,
-      )
-    }
-  }
-}
diff --git a/packages/InputDevices/res/xml/keyboard_layouts.xml b/packages/InputDevices/res/xml/keyboard_layouts.xml
index 976a279..88bb30b 100644
--- a/packages/InputDevices/res/xml/keyboard_layouts.xml
+++ b/packages/InputDevices/res/xml/keyboard_layouts.xml
@@ -1,190 +1,328 @@
 <?xml version="1.0" encoding="utf-8"?>
 <keyboard-layouts xmlns:android="http://schemas.android.com/apk/res/android">
-    <keyboard-layout android:name="keyboard_layout_english_uk"
-            android:label="@string/keyboard_layout_english_uk_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_uk" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_uk"
+        android:label="@string/keyboard_layout_english_uk_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_uk"
+        android:keyboardLocale="en-Latn-GB"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us"
-            android:label="@string/keyboard_layout_english_us_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us"
+        android:label="@string/keyboard_layout_english_us_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us"
+        android:keyboardLocale="en-Latn,en-Latn-US"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_intl"
-            android:label="@string/keyboard_layout_english_us_intl"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_intl" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_intl"
+        android:label="@string/keyboard_layout_english_us_intl"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_intl"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_colemak"
-            android:label="@string/keyboard_layout_english_us_colemak_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_colemak" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_colemak"
+        android:label="@string/keyboard_layout_english_us_colemak_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_colemak"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="colemak" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_dvorak"
-            android:label="@string/keyboard_layout_english_us_dvorak_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_dvorak"
+        android:label="@string/keyboard_layout_english_us_dvorak_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_dvorak"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="dvorak" />
 
-    <keyboard-layout android:name="keyboard_layout_english_us_workman"
-            android:label="@string/keyboard_layout_english_us_workman_label"
-            android:keyboardLayout="@raw/keyboard_layout_english_us_workman" />
+    <keyboard-layout
+        android:name="keyboard_layout_english_us_workman"
+        android:label="@string/keyboard_layout_english_us_workman_label"
+        android:keyboardLayout="@raw/keyboard_layout_english_us_workman"
+        android:keyboardLocale="en-Latn-US"
+        android:keyboardLayoutType="workman" />
 
-    <keyboard-layout android:name="keyboard_layout_german"
-            android:label="@string/keyboard_layout_german_label"
-            android:keyboardLayout="@raw/keyboard_layout_german" />
+    <keyboard-layout
+        android:name="keyboard_layout_german"
+        android:label="@string/keyboard_layout_german_label"
+        android:keyboardLayout="@raw/keyboard_layout_german"
+        android:keyboardLocale="de-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_french"
-            android:label="@string/keyboard_layout_french_label"
-            android:keyboardLayout="@raw/keyboard_layout_french" />
+    <keyboard-layout
+        android:name="keyboard_layout_french"
+        android:label="@string/keyboard_layout_french_label"
+        android:keyboardLayout="@raw/keyboard_layout_french"
+        android:keyboardLocale="fr-Latn-FR"
+        android:keyboardLayoutType="azerty" />
 
-    <keyboard-layout android:name="keyboard_layout_french_ca"
-            android:label="@string/keyboard_layout_french_ca_label"
-            android:keyboardLayout="@raw/keyboard_layout_french_ca" />
+    <keyboard-layout
+        android:name="keyboard_layout_french_ca"
+        android:label="@string/keyboard_layout_french_ca_label"
+        android:keyboardLayout="@raw/keyboard_layout_french_ca"
+        android:keyboardLocale="fr-Latn-CA"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_russian"
-            android:label="@string/keyboard_layout_russian_label"
-            android:keyboardLayout="@raw/keyboard_layout_russian" />
+    <keyboard-layout
+        android:name="keyboard_layout_russian"
+        android:label="@string/keyboard_layout_russian_label"
+        android:keyboardLayout="@raw/keyboard_layout_russian"
+        android:keyboardLocale="ru-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_russian_mac"
-            android:label="@string/keyboard_layout_russian_mac_label"
-            android:keyboardLayout="@raw/keyboard_layout_russian_mac" />
+    <keyboard-layout
+        android:name="keyboard_layout_russian_mac"
+        android:label="@string/keyboard_layout_russian_mac_label"
+        android:keyboardLayout="@raw/keyboard_layout_russian_mac"
+        android:keyboardLocale="ru-Cyrl"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_spanish"
-            android:label="@string/keyboard_layout_spanish_label"
-            android:keyboardLayout="@raw/keyboard_layout_spanish" />
+    <keyboard-layout
+        android:name="keyboard_layout_spanish"
+        android:label="@string/keyboard_layout_spanish_label"
+        android:keyboardLayout="@raw/keyboard_layout_spanish"
+        android:keyboardLocale="es-Latn-ES"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_swiss_french"
-            android:label="@string/keyboard_layout_swiss_french_label"
-            android:keyboardLayout="@raw/keyboard_layout_swiss_french" />
+    <keyboard-layout
+        android:name="keyboard_layout_swiss_french"
+        android:label="@string/keyboard_layout_swiss_french_label"
+        android:keyboardLayout="@raw/keyboard_layout_swiss_french"
+        android:keyboardLocale="fr-Latn-CH"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_swiss_german"
-            android:label="@string/keyboard_layout_swiss_german_label"
-            android:keyboardLayout="@raw/keyboard_layout_swiss_german" />
+    <keyboard-layout
+        android:name="keyboard_layout_swiss_german"
+        android:label="@string/keyboard_layout_swiss_german_label"
+        android:keyboardLayout="@raw/keyboard_layout_swiss_german"
+        android:keyboardLocale="de-Latn-CH"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_belgian"
-            android:label="@string/keyboard_layout_belgian"
-            android:keyboardLayout="@raw/keyboard_layout_belgian" />
+    <keyboard-layout
+        android:name="keyboard_layout_belgian"
+        android:label="@string/keyboard_layout_belgian"
+        android:keyboardLayout="@raw/keyboard_layout_belgian"
+        android:keyboardLocale="fr-Latn-BE"
+        android:keyboardLayoutType="azerty" />
 
-    <keyboard-layout android:name="keyboard_layout_bulgarian"
-            android:label="@string/keyboard_layout_bulgarian"
-            android:keyboardLayout="@raw/keyboard_layout_bulgarian" />
+    <keyboard-layout
+        android:name="keyboard_layout_bulgarian"
+        android:label="@string/keyboard_layout_bulgarian"
+        android:keyboardLayout="@raw/keyboard_layout_bulgarian"
+        android:keyboardLocale="bg-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_bulgarian_phonetic"
-            android:label="@string/keyboard_layout_bulgarian_phonetic"
-            android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic" />
+    <keyboard-layout
+        android:name="keyboard_layout_bulgarian_phonetic"
+        android:label="@string/keyboard_layout_bulgarian_phonetic"
+        android:keyboardLayout="@raw/keyboard_layout_bulgarian_phonetic"
+        android:keyboardLocale="bg-Cyrl"
+        android:keyboardLayoutType="extended" />
 
-    <keyboard-layout android:name="keyboard_layout_italian"
-            android:label="@string/keyboard_layout_italian"
-            android:keyboardLayout="@raw/keyboard_layout_italian" />
+    <keyboard-layout
+        android:name="keyboard_layout_italian"
+        android:label="@string/keyboard_layout_italian"
+        android:keyboardLayout="@raw/keyboard_layout_italian"
+        android:keyboardLocale="it-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_danish"
-            android:label="@string/keyboard_layout_danish"
-            android:keyboardLayout="@raw/keyboard_layout_danish" />
+    <keyboard-layout
+        android:name="keyboard_layout_danish"
+        android:label="@string/keyboard_layout_danish"
+        android:keyboardLayout="@raw/keyboard_layout_danish"
+        android:keyboardLocale="da-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_norwegian"
-            android:label="@string/keyboard_layout_norwegian"
-            android:keyboardLayout="@raw/keyboard_layout_norwegian" />
+    <keyboard-layout
+        android:name="keyboard_layout_norwegian"
+        android:label="@string/keyboard_layout_norwegian"
+        android:keyboardLayout="@raw/keyboard_layout_norwegian"
+        android:keyboardLocale="nb-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_swedish"
-            android:label="@string/keyboard_layout_swedish"
-            android:keyboardLayout="@raw/keyboard_layout_swedish" />
+    <keyboard-layout
+        android:name="keyboard_layout_swedish"
+        android:label="@string/keyboard_layout_swedish"
+        android:keyboardLayout="@raw/keyboard_layout_swedish"
+        android:keyboardLocale="sv-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_finnish"
-            android:label="@string/keyboard_layout_finnish"
-            android:keyboardLayout="@raw/keyboard_layout_finnish" />
+    <keyboard-layout
+        android:name="keyboard_layout_finnish"
+        android:label="@string/keyboard_layout_finnish"
+        android:keyboardLayout="@raw/keyboard_layout_finnish"
+        android:keyboardLocale="fi-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_croatian"
-            android:label="@string/keyboard_layout_croatian"
-            android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+    <keyboard-layout
+        android:name="keyboard_layout_croatian"
+        android:label="@string/keyboard_layout_croatian"
+        android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+        android:keyboardLocale="hr-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_czech"
-            android:label="@string/keyboard_layout_czech"
-            android:keyboardLayout="@raw/keyboard_layout_czech" />
+    <keyboard-layout
+        android:name="keyboard_layout_czech"
+        android:label="@string/keyboard_layout_czech"
+        android:keyboardLayout="@raw/keyboard_layout_czech"
+        android:keyboardLocale="cs-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_czech_qwerty"
-            android:label="@string/keyboard_layout_czech_qwerty"
-            android:keyboardLayout="@raw/keyboard_layout_czech_qwerty" />
+    <keyboard-layout
+        android:name="keyboard_layout_czech_qwerty"
+        android:label="@string/keyboard_layout_czech_qwerty"
+        android:keyboardLayout="@raw/keyboard_layout_czech_qwerty"
+        android:keyboardLocale="cs-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_estonian"
-            android:label="@string/keyboard_layout_estonian"
-            android:keyboardLayout="@raw/keyboard_layout_estonian" />
+    <keyboard-layout
+        android:name="keyboard_layout_estonian"
+        android:label="@string/keyboard_layout_estonian"
+        android:keyboardLayout="@raw/keyboard_layout_estonian"
+        android:keyboardLocale="et-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_hungarian"
-            android:label="@string/keyboard_layout_hungarian"
-            android:keyboardLayout="@raw/keyboard_layout_hungarian" />
+    <keyboard-layout
+        android:name="keyboard_layout_hungarian"
+        android:label="@string/keyboard_layout_hungarian"
+        android:keyboardLayout="@raw/keyboard_layout_hungarian"
+        android:keyboardLocale="hu-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_icelandic"
-            android:label="@string/keyboard_layout_icelandic"
-            android:keyboardLayout="@raw/keyboard_layout_icelandic" />
+    <keyboard-layout
+        android:name="keyboard_layout_icelandic"
+        android:label="@string/keyboard_layout_icelandic"
+        android:keyboardLayout="@raw/keyboard_layout_icelandic"
+        android:keyboardLocale="is-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_brazilian"
-            android:label="@string/keyboard_layout_brazilian"
-            android:keyboardLayout="@raw/keyboard_layout_brazilian" />
+    <keyboard-layout
+        android:name="keyboard_layout_brazilian"
+        android:label="@string/keyboard_layout_brazilian"
+        android:keyboardLayout="@raw/keyboard_layout_brazilian"
+        android:keyboardLocale="pt-Latn-BR"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_portuguese"
-            android:label="@string/keyboard_layout_portuguese"
-            android:keyboardLayout="@raw/keyboard_layout_portuguese" />
+    <keyboard-layout
+        android:name="keyboard_layout_portuguese"
+        android:label="@string/keyboard_layout_portuguese"
+        android:keyboardLayout="@raw/keyboard_layout_portuguese"
+        android:keyboardLocale="pt-Latn-PT"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_slovak"
-            android:label="@string/keyboard_layout_slovak"
-            android:keyboardLayout="@raw/keyboard_layout_slovak" />
+    <keyboard-layout
+        android:name="keyboard_layout_slovak"
+        android:label="@string/keyboard_layout_slovak"
+        android:keyboardLayout="@raw/keyboard_layout_slovak"
+        android:keyboardLocale="sk-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_slovenian"
-            android:label="@string/keyboard_layout_slovenian"
-            android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian" />
+    <keyboard-layout
+        android:name="keyboard_layout_slovenian"
+        android:label="@string/keyboard_layout_slovenian"
+        android:keyboardLayout="@raw/keyboard_layout_croatian_and_slovenian"
+        android:keyboardLocale="sl-Latn"
+        android:keyboardLayoutType="qwertz" />
 
-    <keyboard-layout android:name="keyboard_layout_turkish"
-            android:label="@string/keyboard_layout_turkish"
-            android:keyboardLayout="@raw/keyboard_layout_turkish" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish"
+        android:label="@string/keyboard_layout_turkish"
+        android:keyboardLayout="@raw/keyboard_layout_turkish"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_turkish_f"
-            android:label="@string/keyboard_layout_turkish_f"
-            android:keyboardLayout="@raw/keyboard_layout_turkish_f" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish"
+        android:label="@string/keyboard_layout_turkish"
+        android:keyboardLayout="@raw/keyboard_layout_turkish"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="turkish_q" />
 
-    <keyboard-layout android:name="keyboard_layout_ukrainian"
-            android:label="@string/keyboard_layout_ukrainian"
-            android:keyboardLayout="@raw/keyboard_layout_ukrainian" />
+    <keyboard-layout
+        android:name="keyboard_layout_turkish_f"
+        android:label="@string/keyboard_layout_turkish_f"
+        android:keyboardLayout="@raw/keyboard_layout_turkish_f"
+        android:keyboardLocale="tr-Latn"
+        android:keyboardLayoutType="turkish_f" />
 
-    <keyboard-layout android:name="keyboard_layout_arabic"
-            android:label="@string/keyboard_layout_arabic"
-            android:keyboardLayout="@raw/keyboard_layout_arabic" />
+    <keyboard-layout
+        android:name="keyboard_layout_ukrainian"
+        android:label="@string/keyboard_layout_ukrainian"
+        android:keyboardLayout="@raw/keyboard_layout_ukrainian"
+        android:keyboardLocale="uk-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_greek"
-            android:label="@string/keyboard_layout_greek"
-            android:keyboardLayout="@raw/keyboard_layout_greek" />
+    <keyboard-layout
+        android:name="keyboard_layout_arabic"
+        android:label="@string/keyboard_layout_arabic"
+        android:keyboardLayout="@raw/keyboard_layout_arabic"
+        android:keyboardLocale="ar-Arab" />
 
-    <keyboard-layout android:name="keyboard_layout_hebrew"
-            android:label="@string/keyboard_layout_hebrew"
-            android:keyboardLayout="@raw/keyboard_layout_hebrew" />
+    <keyboard-layout
+        android:name="keyboard_layout_greek"
+        android:label="@string/keyboard_layout_greek"
+        android:keyboardLayout="@raw/keyboard_layout_greek"
+        android:keyboardLocale="el-Grek" />
 
-    <keyboard-layout android:name="keyboard_layout_lithuanian"
-            android:label="@string/keyboard_layout_lithuanian"
-            android:keyboardLayout="@raw/keyboard_layout_lithuanian" />
+    <keyboard-layout
+        android:name="keyboard_layout_hebrew"
+        android:label="@string/keyboard_layout_hebrew"
+        android:keyboardLayout="@raw/keyboard_layout_hebrew"
+        android:keyboardLocale="iw-Hebr" />
 
-    <keyboard-layout android:name="keyboard_layout_spanish_latin"
-            android:label="@string/keyboard_layout_spanish_latin"
-            android:keyboardLayout="@raw/keyboard_layout_spanish_latin" />
+    <keyboard-layout
+        android:name="keyboard_layout_lithuanian"
+        android:label="@string/keyboard_layout_lithuanian"
+        android:keyboardLayout="@raw/keyboard_layout_lithuanian"
+        android:keyboardLocale="lt-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_latvian"
-            android:label="@string/keyboard_layout_latvian"
-            android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty" />
+    <keyboard-layout
+        android:name="keyboard_layout_spanish_latin"
+        android:label="@string/keyboard_layout_spanish_latin"
+        android:keyboardLayout="@raw/keyboard_layout_spanish_latin"
+        android:keyboardLocale="es-Latn-419"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_persian"
-            android:label="@string/keyboard_layout_persian"
-            android:keyboardLayout="@raw/keyboard_layout_persian" />
+    <keyboard-layout
+        android:name="keyboard_layout_latvian"
+        android:label="@string/keyboard_layout_latvian"
+        android:keyboardLayout="@raw/keyboard_layout_latvian_qwerty"
+        android:keyboardLocale="lv-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_azerbaijani"
-            android:label="@string/keyboard_layout_azerbaijani"
-            android:keyboardLayout="@raw/keyboard_layout_azerbaijani" />
+    <keyboard-layout
+        android:name="keyboard_layout_persian"
+        android:label="@string/keyboard_layout_persian"
+        android:keyboardLayout="@raw/keyboard_layout_persian"
+        android:keyboardLocale="fa-Arab" />
 
-    <keyboard-layout android:name="keyboard_layout_polish"
-            android:label="@string/keyboard_layout_polish"
-            android:keyboardLayout="@raw/keyboard_layout_polish" />
+    <keyboard-layout
+        android:name="keyboard_layout_azerbaijani"
+        android:label="@string/keyboard_layout_azerbaijani"
+        android:keyboardLayout="@raw/keyboard_layout_azerbaijani"
+        android:keyboardLocale="az-Latn-AZ"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_belarusian"
-            android:label="@string/keyboard_layout_belarusian"
-            android:keyboardLayout="@raw/keyboard_layout_belarusian" />
+    <keyboard-layout
+        android:name="keyboard_layout_polish"
+        android:label="@string/keyboard_layout_polish"
+        android:keyboardLayout="@raw/keyboard_layout_polish"
+        android:keyboardLocale="pl-Latn"
+        android:keyboardLayoutType="qwerty" />
 
-    <keyboard-layout android:name="keyboard_layout_mongolian"
-            android:label="@string/keyboard_layout_mongolian"
-            android:keyboardLayout="@raw/keyboard_layout_mongolian" />
+    <keyboard-layout
+        android:name="keyboard_layout_belarusian"
+        android:label="@string/keyboard_layout_belarusian"
+        android:keyboardLayout="@raw/keyboard_layout_belarusian"
+        android:keyboardLocale="be-Cyrl" />
 
-    <keyboard-layout android:name="keyboard_layout_georgian"
-            android:label="@string/keyboard_layout_georgian"
-            android:keyboardLayout="@raw/keyboard_layout_georgian" />
+    <keyboard-layout
+        android:name="keyboard_layout_mongolian"
+        android:label="@string/keyboard_layout_mongolian"
+        android:keyboardLayout="@raw/keyboard_layout_mongolian"
+        android:keyboardLocale="mn-Cyrl" />
+
+    <keyboard-layout
+        android:name="keyboard_layout_georgian"
+        android:label="@string/keyboard_layout_georgian"
+        android:keyboardLayout="@raw/keyboard_layout_georgian"
+        android:keyboardLocale="ka-Geor" />
 </keyboard-layouts>
diff --git a/packages/PackageInstaller/Android.bp b/packages/PackageInstaller/Android.bp
index fe640ad..fd982f5 100644
--- a/packages/PackageInstaller/Android.bp
+++ b/packages/PackageInstaller/Android.bp
@@ -39,12 +39,13 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
-
     static_libs: [
         "xz-java",
         "androidx.leanback_leanback",
+        "androidx.annotation_annotation",
     ],
 }
 
@@ -56,7 +57,8 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
@@ -75,13 +77,15 @@
 
     certificate: "platform",
     privileged: true,
-    platform_apis: true,
+    platform_apis: false,
+    sdk_version: "system_current",
     rename_resources_package: false,
     overrides: ["PackageInstaller"],
 
     static_libs: [
         "xz-java",
         "androidx.leanback_leanback",
+        "androidx.annotation_annotation",
     ],
     aaptflags: ["--product tv"],
 }
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
new file mode 100644
index 0000000..6e5fbb5
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
new file mode 100644
index 0000000..3434b2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
new file mode 100644
index 0000000..673a509
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_bottom_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
new file mode 100644
index 0000000..c2a739c
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
new file mode 100644
index 0000000..9d2bfb1
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
new file mode 100644
index 0000000..4375bf2d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_center_medium.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
new file mode 100644
index 0000000..6b8aa9d
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
new file mode 100644
index 0000000..2884abe
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_full_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
new file mode 100644
index 0000000..76c35ec
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_bright.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
new file mode 100644
index 0000000..f317330
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable-hdpi/popup_top_dark.9.png
Binary files differ
diff --git a/packages/PackageInstaller/res/drawable/ic_dialog_info.png b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
new file mode 100644
index 0000000..efee1ef
--- /dev/null
+++ b/packages/PackageInstaller/res/drawable/ic_dialog_info.png
Binary files differ
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
new file mode 100644
index 0000000..3ced1db
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_button_bar_leanback.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?android:attr/buttonBarStyle">
+    <com.android.packageinstaller.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:gravity="start">
+
+        <Button
+            android:id="@+id/button1"
+            style="?android:attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button2"
+            style="?android:attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <Button
+            android:id="@+id/button3"
+            style="?attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+    </com.android.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
new file mode 100644
index 0000000..0290624
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<com.android.packageinstaller.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:orientation="vertical">
+
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <Space
+                    android:id="@+id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="?android:attr/dialogPreferredPadding"
+                    android:paddingStart="?android:attr/dialogPreferredPadding"
+                    style="@android:style/TextAppearance.Material.Subhead" />
+
+                <Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/alert_dialog_button_bar_leanback" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
new file mode 100644
index 0000000..f9668dd
--- /dev/null
+++ b/packages/PackageInstaller/res/layout-television/alert_dialog_leanback_button_panel_side.xml
@@ -0,0 +1,126 @@
+<?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.
+-->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+
+   <LinearLayout
+       android:id="@+id/leftPanel"
+       android:layout_width="0dp"
+       android:layout_weight="1"
+       android:layout_height="wrap_content"
+       android:orientation="vertical">
+
+    <LinearLayout android:id="@+id/topPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <LinearLayout android:id="@+id/title_template"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:gravity="center_vertical|start"
+            android:paddingStart="16dip"
+            android:paddingEnd="16dip"
+            android:paddingTop="16dip"
+            android:paddingBottom="8dip">
+            <ImageView android:id="@+id/icon"
+                android:layout_width="32dip"
+                android:layout_height="32dip"
+                android:layout_marginEnd="8dip"
+                android:scaleType="fitCenter"
+                android:src="@null" />
+            <com.android.packageinstaller.DialogTitle android:id="@+id/alertTitle"
+                style="?android:attr/windowTitleStyle"
+                android:singleLine="true"
+                android:ellipsize="end"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textAlignment="viewStart" />
+        </LinearLayout>
+        <!-- If the client uses a customTitle, it will be added here. -->
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="vertical"
+        android:minHeight="64dp">
+        <ScrollView android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+            <TextView android:id="@+id/message"
+                style="?android:attr/textAppearanceMedium"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingStart="16dip"
+                android:paddingEnd="16dip"
+                android:paddingTop="16dip"
+                android:paddingBottom="16dip" />
+        </ScrollView>
+    </LinearLayout>
+
+    <FrameLayout android:id="@+id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:minHeight="64dp">
+        <FrameLayout android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+    </LinearLayout>
+
+    <LinearLayout android:id="@+id/buttonPanel"
+        style="?attr/buttonBarStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical"
+        android:gravity="end">
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layoutDirection="locale"
+            android:orientation="vertical">
+            <Button android:id="@+id/button3"
+                style="?attr/buttonBarNeutralButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button2"
+                style="?attr/buttonBarNegativeButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+            <Button android:id="@+id/button1"
+                style="?attr/buttonBarPositiveButtonStyle"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:maxLines="2"
+                android:minHeight="@dimen/alert_dialog_button_bar_height" />
+        </LinearLayout>
+     </LinearLayout>
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
new file mode 100644
index 0000000..e4977e7
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_button_bar_material.xml
@@ -0,0 +1,62 @@
+<?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.
+-->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:id="@+id/buttonPanel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:scrollbarAlwaysDrawVerticalTrack="true"
+            android:scrollIndicators="top|bottom"
+            android:fillViewport="true"
+            style="?android:attr/buttonBarStyle">
+    <com.android.packageinstaller.ButtonBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layoutDirection="locale"
+        android:orientation="horizontal"
+        android:paddingStart="12dp"
+        android:paddingEnd="12dp"
+        android:paddingTop="4dp"
+        android:paddingBottom="4dp"
+        android:gravity="bottom">
+
+        <Button
+            android:id="@+id/button3"
+            style="?android:attr/buttonBarNeutralButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Space
+            android:id="@+id/spacer"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:visibility="invisible" />
+
+        <Button
+            android:id="@+id/button2"
+            style="?android:attr/buttonBarNegativeButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+
+        <Button
+            android:id="@+id/button1"
+            style="?android:attr/buttonBarPositiveButtonStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" />
+    </com.android.packageinstaller.ButtonBarLayout>
+</ScrollView>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
new file mode 100644
index 0000000..10e9149
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_material.xml
@@ -0,0 +1,84 @@
+<?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.
+-->
+
+<com.android.packageinstaller.AlertDialogLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/parentPanel"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:gravity="start|top"
+    android:orientation="vertical">
+
+    <include layout="@layout/alert_dialog_title_material" />
+
+    <FrameLayout
+        android:id="@+id/contentPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <ScrollView
+            android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipToPadding="false">
+
+            <LinearLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical">
+
+                <Space
+                    android:id="@+id/textSpacerNoTitle"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+
+                <TextView
+                    android:id="@+id/message"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:paddingEnd="?android:attr/dialogPreferredPadding"
+                    android:paddingStart="?android:attr/dialogPreferredPadding"
+                    style="@android:style/TextAppearance.Material.Subhead" />
+
+                <Space
+                    android:id="@+id/textSpacerNoButtons"
+                    android:visibility="gone"
+                    android:layout_width="match_parent"
+                    android:layout_height="@dimen/dialog_padding_top_material" />
+            </LinearLayout>
+        </ScrollView>
+    </FrameLayout>
+
+    <FrameLayout
+        android:id="@+id/customPanel"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="48dp">
+
+        <FrameLayout
+            android:id="@+id/custom"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </FrameLayout>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        layout="@layout/alert_dialog_button_bar_material" />
+</com.android.packageinstaller.AlertDialogLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
new file mode 100644
index 0000000..fe06b65
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2007 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+  
+          http://www.apache.org/licenses/LICENSE-2.0
+  
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content" android:layout_height="match_parent">
+        <ProgressBar android:id="@+id/progress"
+            style="?android:attr/progressBarStyleHorizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="12dip"
+            android:layout_marginBottom="1dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_centerHorizontal="true" />
+        <TextView
+            android:id="@+id/progress_percent"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_alignParentStart="true"
+            android:layout_below="@id/progress"
+        />
+        <TextView
+            android:id="@+id/progress_number"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:paddingBottom="12dip"
+            android:layout_marginStart="10dip"
+            android:layout_marginEnd="10dip"
+            android:layout_alignParentEnd="true"
+            android:layout_below="@id/progress"
+        />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
new file mode 100644
index 0000000..fb98259
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_progress_material.xml
@@ -0,0 +1,43 @@
+<?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.
+-->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:paddingStart="?attr/dialogPreferredPadding"
+    android:paddingTop="@dimen/dialog_padding_top_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:paddingBottom="@dimen/dialog_padding_top_material">
+    <ProgressBar
+        android:id="@+id/progress"
+        style="?android:attr/progressBarStyleHorizontal"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_centerHorizontal="true" />
+    <TextView
+        android:id="@+id/progress_percent"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentStart="true"
+        android:layout_below="@id/progress" />
+    <TextView
+        android:id="@+id/progress_number"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentEnd="true"
+        android:layout_below="@id/progress" />
+</RelativeLayout>
diff --git a/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
new file mode 100644
index 0000000..45d9bb6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/alert_dialog_title_material.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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"
+              android:id="@+id/topPanel"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical">
+
+    <!-- If the client uses a customTitle, it will be added here. -->
+
+    <LinearLayout
+        android:id="@+id/title_template"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal"
+        android:gravity="center_vertical|start"
+        android:paddingStart="?android:attr/dialogPreferredPadding"
+        android:paddingEnd="?android:attr/dialogPreferredPadding"
+        android:paddingTop="@dimen/dialog_padding_top_material">
+
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="32dip"
+            android:layout_height="32dip"
+            android:layout_marginEnd="8dip"
+            android:scaleType="fitCenter"
+            android:src="@null" />
+
+        <com.android.packageinstaller.DialogTitle
+            android:id="@+id/alertTitle"
+            android:singleLine="true"
+            android:ellipsize="end"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textAlignment="viewStart"
+            style="?android:attr/windowTitleStyle" />
+    </LinearLayout>
+
+    <Space
+        android:id="@+id/titleDividerNoCustom"
+        android:visibility="gone"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/dialog_title_divider_material" />
+</LinearLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog.xml b/packages/PackageInstaller/res/layout/progress_dialog.xml
new file mode 100644
index 0000000..0d3cd4a
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/* //device/apps/common/res/layout/alert_dialog.xml
+**
+** Copyright 2006, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT 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:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout android:id="@+id/body"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:baselineAligned="false"
+        android:paddingStart="8dip"
+        android:paddingTop="10dip"
+        android:paddingEnd="8dip"
+        android:paddingBottom="10dip">
+
+        <ProgressBar android:id="@android:id/progress"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginEnd="12dip" />
+
+        <TextView android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/progress_dialog_material.xml b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
new file mode 100644
index 0000000..2417965
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/progress_dialog_material.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <LinearLayout
+        android:id="@+id/body"
+        android:orientation="horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:baselineAligned="false"
+        android:paddingStart="?attr/dialogPreferredPadding"
+        android:paddingTop="@dimen/dialog_padding_top_material"
+        android:paddingEnd="?attr/dialogPreferredPadding"
+        android:paddingBottom="@dimen/dialog_padding_top_material">
+
+        <ProgressBar
+            android:id="@id/progress"
+            style="?android:attr/progressBarStyle"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:max="10000"
+            android:layout_marginEnd="?attr/dialogPreferredPadding" />
+
+        <TextView
+            android:id="@+id/message"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical" />
+    </LinearLayout>
+</FrameLayout>
diff --git a/packages/PackageInstaller/res/layout/select_dialog_item_material.xml b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
new file mode 100644
index 0000000..b45edc6
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_item_material.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+
+<!--
+    This layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the TextView to display individual
+    items.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceListItemSmall"
+    android:textColor="?android:attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="?attr/listPreferredItemPaddingStart"
+    android:paddingEnd="?attr/listPreferredItemPaddingEnd"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_material.xml b/packages/PackageInstaller/res/layout/select_dialog_material.xml
new file mode 100644
index 0000000..125b9b8
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_material.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<!--
+    This layout file is used by the AlertDialog when displaying a list of items.
+    This layout file is inflated and used as the ListView to display the items.
+    Assign an ID so its state will be saved/restored.
+-->
+<view class="com.android.packageinstaller.AlertController$RecycleListView"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@id/select_dialog_listview"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:cacheColorHint="@null"
+    android:divider="?attr/listDividerAlertDialog"
+    android:scrollbars="vertical"
+    android:overScrollMode="ifContentScrolls"
+    android:textAlignment="viewStart"
+    android:clipToPadding="false"/>
+    <!--android:paddingBottomNoButtons="@dimen/dialog_list_padding_bottom_no_buttons"
+    android:paddingTopNoTitle="@dimen/dialog_list_padding_top_no_title"/>-->
diff --git a/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
new file mode 100644
index 0000000..52f709e
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_multichoice_material.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/select_dialog_padding_start_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
+    android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
new file mode 100644
index 0000000..8345b18
--- /dev/null
+++ b/packages/PackageInstaller/res/layout/select_dialog_singlechoice_material.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@android:id/text1"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?attr/listPreferredItemHeightSmall"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textColor="?attr/textColorAlertDialogListItem"
+    android:gravity="center_vertical"
+    android:paddingStart="@dimen/select_dialog_padding_start_material"
+    android:paddingEnd="?attr/dialogPreferredPadding"
+    android:drawableStart="?android:attr/listChoiceIndicatorSingle"
+    android:drawablePadding="@dimen/select_dialog_drawable_padding_start_material"
+    android:ellipsize="marquee" />
diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml
index 483b0cf..18320f7 100644
--- a/packages/PackageInstaller/res/values-night/themes.xml
+++ b/packages/PackageInstaller/res/values-night/themes.xml
@@ -18,6 +18,8 @@
 <resources>
 
     <style name="Theme.AlertDialogActivity"
-            parent="@android:style/Theme.DeviceDefault.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog</item>
+    </style>
 
 </resources>
diff --git a/packages/PackageInstaller/res/values-television/styles.xml b/packages/PackageInstaller/res/values-television/styles.xml
new file mode 100644
index 0000000..936fff0
--- /dev/null
+++ b/packages/PackageInstaller/res/values-television/styles.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>
+    <style name="AlertDialog.Leanback" parent="@style/AlertDialog">
+        <item name="buttonPanelSideLayout">@layout/alert_dialog_leanback_button_panel_side</item>
+        <item name="layout">@layout/alert_dialog_leanback</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values-television/themes.xml b/packages/PackageInstaller/res/values-television/themes.xml
index 5ae4957..1cc6933 100644
--- a/packages/PackageInstaller/res/values-television/themes.xml
+++ b/packages/PackageInstaller/res/values-television/themes.xml
@@ -17,15 +17,20 @@
 
 <resources>
 
-    <style name="Theme.AlertDialogActivity.NoAnimation">
+    <style name="Theme.AlertDialogActivity.NoAnimation"
+           parent="@style/Theme.AlertDialogActivity.NoActionBar">
         <item name="android:windowAnimationStyle">@null</item>
     </style>
 
     <style name="Theme.AlertDialogActivity"
-           parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog.Leanback</item>
+    </style>
 
     <style name="Theme.AlertDialogActivity.NoActionBar"
-           parent="@android:style/Theme.DeviceDefault.Light.NoActionBar">
+        parent="@android:style/Theme.Material.Light.NoActionBar">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 
 </resources>
diff --git a/packages/PackageInstaller/res/values/attrs.xml b/packages/PackageInstaller/res/values/attrs.xml
index e220f4c..e3070e2 100644
--- a/packages/PackageInstaller/res/values/attrs.xml
+++ b/packages/PackageInstaller/res/values/attrs.xml
@@ -32,4 +32,49 @@
         <attr name="circle_radius_pressed_percent" format="fraction" />
     </declare-styleable>
     <!-- END: Ported from WearableSupport -->
+    <declare-styleable name="Theme">
+        <attr name="alertDialogCenterButtons" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="AlertDialog">
+        <attr name="fullDark" format="reference|color" />
+        <attr name="topDark" format="reference|color" />
+        <attr name="centerDark" format="reference|color" />
+        <attr name="bottomDark" format="reference|color" />
+        <attr name="fullBright" format="reference|color" />
+        <attr name="topBright" format="reference|color" />
+        <attr name="centerBright" format="reference|color" />
+        <attr name="bottomBright" format="reference|color" />
+        <attr name="bottomMedium" format="reference|color" />
+        <attr name="centerMedium" format="reference|color" />
+        <attr name="layout" format="reference" />
+        <attr name="buttonPanelSideLayout" format="reference" />
+        <attr name="listLayout" format="reference" />
+        <attr name="multiChoiceItemLayout" format="reference" />
+        <attr name="singleChoiceItemLayout" format="reference" />
+        <attr name="listItemLayout" format="reference" />
+        <attr name="progressLayout" format="reference" />
+        <attr name="horizontalProgressLayout" format="reference" />
+        <!-- @hide Not ready for public use. -->
+        <attr name="showTitle" format="boolean" />
+        <!-- Whether fullDark, etc. should use default values if null. -->
+        <attr name="needsDefaultBackgrounds" format="boolean" />
+        <!-- Workaround until we replace AlertController with custom layout. -->
+        <attr name="controllerType">
+            <!-- The default controller. -->
+            <enum name="normal" value="0" />
+            <!-- Controller for micro specific layout. -->
+            <enum name="micro" value="1" />
+        </attr>
+        <!-- Offset when scrolling to a selection. -->
+        <attr name="selectionScrollOffset" format="dimension" />
+    </declare-styleable>
+    <declare-styleable name="ButtonBarLayout">
+        <!-- Whether to automatically stack the buttons when there is not
+             enough space to lay them out side-by-side. -->
+        <attr name="allowStacking" format="boolean" />
+    </declare-styleable>
+    <declare-styleable name="TextAppearance">
+        <!-- Size of the text. Recommended dimension type for text is "sp" for scaled-pixels (example: 15sp). -->
+        <attr name="textSize" format="dimension" />
+    </declare-styleable>
 </resources>
diff --git a/packages/PackageInstaller/res/values/dimens.xml b/packages/PackageInstaller/res/values/dimens.xml
index 112723f..bfea05e 100644
--- a/packages/PackageInstaller/res/values/dimens.xml
+++ b/packages/PackageInstaller/res/values/dimens.xml
@@ -41,4 +41,16 @@
 
     <dimen name="wear_permission_review_pref_padding">8dp</dimen>
     <dimen name="wear_permission_review_icon_size">24dp</dimen>
+
+    <!-- Dialog title height -->
+    <dimen name="alert_dialog_title_height">64dip</dimen>
+    <!-- Dialog button bar height -->
+    <dimen name="alert_dialog_button_bar_height">48dip</dimen>
+    <!-- The amount to offset when scrolling to a selection in an AlertDialog -->
+    <dimen name="config_alertDialogSelectionScrollOffset">0dp</dimen>
+    <dimen name="dialog_padding_top_material">18dp</dimen>
+    <dimen name="dialog_title_divider_material">8dp</dimen>
+    <!-- Dialog padding minus control padding, used to fix alignment. -->
+    <dimen name="select_dialog_padding_start_material">20dp</dimen>
+    <dimen name="select_dialog_drawable_padding_start_material">20dp</dimen>
 </resources>
diff --git a/packages/PackageInstaller/res/values/integers.xml b/packages/PackageInstaller/res/values/integers.xml
new file mode 100644
index 0000000..22ad3a3
--- /dev/null
+++ b/packages/PackageInstaller/res/values/integers.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>
+    <!-- The alert controller to use for alert dialogs. -->
+    <integer name="config_alertDialogController">0</integer>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/styles.xml b/packages/PackageInstaller/res/values/styles.xml
new file mode 100644
index 0000000..ca797e1
--- /dev/null
+++ b/packages/PackageInstaller/res/values/styles.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.
+  -->
+<resources>
+    <style name="AlertDialog">
+        <item name="fullDark">@empty</item>
+        <item name="topDark">@empty</item>
+        <item name="centerDark">@empty</item>
+        <item name="bottomDark">@empty</item>
+        <item name="fullBright">@empty</item>
+        <item name="topBright">@empty</item>
+        <item name="centerBright">@empty</item>
+        <item name="bottomBright">@empty</item>
+        <item name="bottomMedium">@empty</item>
+        <item name="centerMedium">@empty</item>
+        <item name="layout">@layout/alert_dialog_material</item>
+        <item name="listLayout">@layout/select_dialog_material</item>
+        <item name="progressLayout">@layout/progress_dialog_material</item>
+        <item name="horizontalProgressLayout">@layout/alert_dialog_progress_material</item>
+        <item name="listItemLayout">@layout/select_dialog_item_material</item>
+        <item name="multiChoiceItemLayout">@layout/select_dialog_multichoice_material</item>
+        <item name="singleChoiceItemLayout">@layout/select_dialog_singlechoice_material</item>
+        <item name="controllerType">@integer/config_alertDialogController</item>
+        <item name="selectionScrollOffset">@dimen/config_alertDialogSelectionScrollOffset</item>
+        <item name="needsDefaultBackgrounds">false</item>
+    </style>
+    <style name="TextAppearance">
+        <item name="textSize">16sp</item>
+    </style>
+</resources>
\ No newline at end of file
diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml
index eecf9a1..9a06229 100644
--- a/packages/PackageInstaller/res/values/themes.xml
+++ b/packages/PackageInstaller/res/values/themes.xml
@@ -23,7 +23,9 @@
     </style>
 
     <style name="Theme.AlertDialogActivity"
-            parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
+        parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert">
+        <item name="alertDialogStyle">@style/AlertDialog</item>
+    </style>
 
     <style name="Theme.AlertDialogActivity.NoActionBar">
         <item name="android:windowActionBar">false</item>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
new file mode 100644
index 0000000..7947400
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertActivity.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packageinstaller;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * An activity that follows the visual style of an AlertDialog.
+ * 
+ * @see #mAlert
+ * @see #setupAlert()
+ */
+public abstract class AlertActivity extends Activity implements DialogInterface {
+
+    public AlertActivity() {
+    }
+
+    /**
+     * The model for the alert.
+     * 
+     */
+    protected AlertController mAlert;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mAlert = new AlertController(this, this, getWindow());
+    }
+
+    public void cancel() {
+        finish();
+    }
+
+    public void dismiss() {
+        // This is called after the click, since we finish when handling the
+        // click, don't do that again here.
+        if (!isFinishing()) {
+            finish();
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        return dispatchPopulateAccessibilityEvent(this, event);
+    }
+
+    public static boolean dispatchPopulateAccessibilityEvent(Activity act,
+            AccessibilityEvent event) {
+        event.setClassName(Dialog.class.getName());
+        event.setPackageName(act.getPackageName());
+
+        ViewGroup.LayoutParams params = act.getWindow().getAttributes();
+        boolean isFullScreen = (params.width == ViewGroup.LayoutParams.MATCH_PARENT) &&
+                (params.height == ViewGroup.LayoutParams.MATCH_PARENT);
+        event.setFullScreen(isFullScreen);
+
+        return false;
+    }
+
+    /**
+     * Sets up the alert, including applying the parameters to the alert model,
+     * and installing the alert's content.
+     *
+     * @see #mAlert
+     */
+    protected void setupAlert() {
+        mAlert.installContent();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyDown(keyCode, event)) return true;
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyUp(keyCode, event)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
new file mode 100644
index 0000000..33f38a6
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertController.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packageinstaller;
+
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewParent;
+import android.view.ViewStub;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.R;
+
+import java.lang.ref.WeakReference;
+
+public class AlertController {
+    private final Context mContext;
+    private final DialogInterface mDialogInterface;
+    protected final Window mWindow;
+
+    private CharSequence mTitle;
+    protected CharSequence mMessage;
+    protected ListView mListView;
+    private View mView;
+
+    private int mViewLayoutResId;
+
+    private int mViewSpacingLeft;
+    private int mViewSpacingTop;
+    private int mViewSpacingRight;
+    private int mViewSpacingBottom;
+    private boolean mViewSpacingSpecified = false;
+
+    private Button mButtonPositive;
+    private CharSequence mButtonPositiveText;
+    private Message mButtonPositiveMessage;
+
+    private Button mButtonNegative;
+    private CharSequence mButtonNegativeText;
+    private Message mButtonNegativeMessage;
+
+    private Button mButtonNeutral;
+    private CharSequence mButtonNeutralText;
+    private Message mButtonNeutralMessage;
+
+    protected ScrollView mScrollView;
+
+    private int mIconId = 0;
+    private Drawable mIcon;
+
+    private ImageView mIconView;
+    private TextView mTitleView;
+    protected TextView mMessageView;
+
+    private ListAdapter mAdapter;
+
+    private int mAlertDialogLayout;
+    private int mButtonPanelSideLayout;
+    private int mListLayout;
+    private int mMultiChoiceItemLayout;
+    private int mSingleChoiceItemLayout;
+    private int mListItemLayout;
+
+    private boolean mShowTitle;
+
+    private Handler mHandler;
+
+    private final View.OnClickListener mButtonHandler = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            final Message m;
+            if (v == mButtonPositive && mButtonPositiveMessage != null) {
+                m = Message.obtain(mButtonPositiveMessage);
+            } else if (v == mButtonNegative && mButtonNegativeMessage != null) {
+                m = Message.obtain(mButtonNegativeMessage);
+            } else if (v == mButtonNeutral && mButtonNeutralMessage != null) {
+                m = Message.obtain(mButtonNeutralMessage);
+            } else {
+                m = null;
+            }
+
+            if (m != null) {
+                m.sendToTarget();
+            }
+
+            // Post a message so we dismiss after the above handlers are executed
+            mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface)
+                    .sendToTarget();
+        }
+    };
+
+    private static final class ButtonHandler extends Handler {
+        // Button clicks have Message.what as the BUTTON{1,2,3} constant
+        private static final int MSG_DISMISS_DIALOG = 1;
+
+        private WeakReference<DialogInterface> mDialog;
+
+        public ButtonHandler(DialogInterface dialog) {
+            mDialog = new WeakReference<>(dialog);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                case DialogInterface.BUTTON_POSITIVE:
+                case DialogInterface.BUTTON_NEGATIVE:
+                case DialogInterface.BUTTON_NEUTRAL:
+                    ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what);
+                    break;
+
+                case MSG_DISMISS_DIALOG:
+                    ((DialogInterface) msg.obj).dismiss();
+            }
+        }
+    }
+
+    public AlertController(Context context, DialogInterface di, Window window) {
+        mContext = context;
+        mDialogInterface = di;
+        mWindow = window;
+        mHandler = new ButtonHandler(di);
+
+        final TypedArray a = context.obtainStyledAttributes(null,
+                R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+
+        mAlertDialogLayout = a.getResourceId(
+                R.styleable.AlertDialog_layout, R.layout.alert_dialog_material);
+        mButtonPanelSideLayout = a.getResourceId(
+                R.styleable.AlertDialog_buttonPanelSideLayout, 0);
+        mListLayout = a.getResourceId(
+                R.styleable.AlertDialog_listLayout, R.layout.select_dialog_material);
+
+        mMultiChoiceItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_multiChoiceItemLayout,
+                R.layout.select_dialog_multichoice_material);
+        mSingleChoiceItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_singleChoiceItemLayout,
+                R.layout.select_dialog_singlechoice_material);
+        mListItemLayout = a.getResourceId(
+                R.styleable.AlertDialog_listItemLayout,
+                R.layout.select_dialog_item_material);
+        mShowTitle = a.getBoolean(R.styleable.AlertDialog_showTitle, true);
+
+        a.recycle();
+
+        /* We use a custom title so never request a window title */
+        window.requestFeature(Window.FEATURE_NO_TITLE);
+    }
+
+    static boolean canTextInput(View v) {
+        if (v.onCheckIsTextEditor()) {
+            return true;
+        }
+
+        if (!(v instanceof ViewGroup)) {
+            return false;
+        }
+
+        ViewGroup vg = (ViewGroup)v;
+        int i = vg.getChildCount();
+        while (i > 0) {
+            i--;
+            v = vg.getChildAt(i);
+            if (canTextInput(v)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public void installContent() {
+        mWindow.setContentView(mAlertDialogLayout);
+        setupView();
+    }
+
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        if (mTitleView != null) {
+            mTitleView.setText(title);
+        }
+        mWindow.setTitle(title);
+    }
+
+    /**
+     * Set the view resource to display in the dialog.
+     */
+    public void setView(int layoutResId) {
+        mView = null;
+        mViewLayoutResId = layoutResId;
+        mViewSpacingSpecified = false;
+    }
+
+    /**
+     * Sets a click listener or a message to be sent when the button is clicked.
+     * You only need to pass one of {@code listener} or {@code msg}.
+     *
+     * @param whichButton Which button, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @param msg The {@link Message} to be sent when clicked.
+     */
+    public void setButton(int whichButton, CharSequence text,
+            DialogInterface.OnClickListener listener, Message msg) {
+
+        if (msg == null && listener != null) {
+            msg = mHandler.obtainMessage(whichButton, listener);
+        }
+
+        switch (whichButton) {
+
+            case DialogInterface.BUTTON_POSITIVE:
+                mButtonPositiveText = text;
+                mButtonPositiveMessage = msg;
+                break;
+
+            case DialogInterface.BUTTON_NEGATIVE:
+                mButtonNegativeText = text;
+                mButtonNegativeMessage = msg;
+                break;
+
+            case DialogInterface.BUTTON_NEUTRAL:
+                mButtonNeutralText = text;
+                mButtonNeutralMessage = msg;
+                break;
+
+            default:
+                throw new IllegalArgumentException("Button does not exist");
+        }
+    }
+
+    /**
+     * Specifies the icon to display next to the alert title.
+     *
+     * @param resId the resource identifier of the drawable to use as the icon,
+     *            or 0 for no icon
+     */
+    public void setIcon(int resId) {
+        mIcon = null;
+        mIconId = resId;
+
+        if (mIconView != null) {
+            if (resId != 0) {
+                mIconView.setVisibility(View.VISIBLE);
+                mIconView.setImageResource(mIconId);
+            } else {
+                mIconView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    /**
+     * Specifies the icon to display next to the alert title.
+     *
+     * @param icon the drawable to use as the icon or null for no icon
+     */
+    public void setIcon(Drawable icon) {
+        mIcon = icon;
+        mIconId = 0;
+
+        if (mIconView != null) {
+            if (icon != null) {
+                mIconView.setVisibility(View.VISIBLE);
+                mIconView.setImageDrawable(icon);
+            } else {
+                mIconView.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    public Button getButton(int whichButton) {
+        switch (whichButton) {
+            case DialogInterface.BUTTON_POSITIVE:
+                return mButtonPositive;
+            case DialogInterface.BUTTON_NEGATIVE:
+                return mButtonNegative;
+            case DialogInterface.BUTTON_NEUTRAL:
+                return mButtonNeutral;
+            default:
+                return null;
+        }
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        return mScrollView != null && mScrollView.executeKeyEvent(event);
+    }
+
+    @SuppressWarnings({"UnusedDeclaration"})
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        return mScrollView != null && mScrollView.executeKeyEvent(event);
+    }
+
+    /**
+     * Resolves whether a custom or default panel should be used. Removes the
+     * default panel if a custom panel should be used. If the resolved panel is
+     * a view stub, inflates before returning.
+     *
+     * @param customPanel the custom panel
+     * @param defaultPanel the default panel
+     * @return the panel to use
+     */
+    @Nullable
+    private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) {
+        if (customPanel == null) {
+            // Inflate the default panel, if needed.
+            if (defaultPanel instanceof ViewStub) {
+                defaultPanel = ((ViewStub) defaultPanel).inflate();
+            }
+
+            return (ViewGroup) defaultPanel;
+        }
+
+        // Remove the default panel entirely.
+        if (defaultPanel != null) {
+            final ViewParent parent = defaultPanel.getParent();
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(defaultPanel);
+            }
+        }
+
+        // Inflate the custom panel, if needed.
+        if (customPanel instanceof ViewStub) {
+            customPanel = ((ViewStub) customPanel).inflate();
+        }
+
+        return (ViewGroup) customPanel;
+    }
+
+    private void setupView() {
+        final View parentPanel = mWindow.findViewById(R.id.parentPanel);
+        final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel);
+        final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel);
+        final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel);
+
+        // Install custom content before setting up the title or buttons so
+        // that we can handle panel overrides.
+        final ViewGroup customPanel = parentPanel.findViewById(R.id.customPanel);
+        setupCustomContent(customPanel);
+
+        final View customTopPanel = customPanel.findViewById(R.id.topPanel);
+        final View customContentPanel = customPanel.findViewById(R.id.contentPanel);
+        final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel);
+
+        // Resolve the correct panels and remove the defaults, if needed.
+        final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel);
+        final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel);
+        final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel);
+
+        setupContent(contentPanel);
+        setupButtons(buttonPanel);
+        setupTitle(topPanel);
+
+        final boolean hasCustomPanel = customPanel != null
+                && customPanel.getVisibility() != View.GONE;
+        final boolean hasTopPanel = topPanel != null
+                && topPanel.getVisibility() != View.GONE;
+        final boolean hasButtonPanel = buttonPanel != null
+                && buttonPanel.getVisibility() != View.GONE;
+
+        if (!parentPanel.isInTouchMode()) {
+            final View content = hasCustomPanel ? customPanel : contentPanel;
+            if (!requestFocusForContent(content)) {
+                requestFocusForDefaultButton();
+            }
+        }
+
+        if (hasTopPanel) {
+            // Only clip scrolling content to padding if we have a title.
+            if (mScrollView != null) {
+                mScrollView.setClipToPadding(true);
+            }
+
+            // Only show the divider if we have a title.
+            View divider = null;
+            if (mMessage != null || hasCustomPanel) {
+                divider = topPanel.findViewById(R.id.titleDividerNoCustom);
+            }
+
+            if (divider != null) {
+                divider.setVisibility(View.VISIBLE);
+            }
+        } else {
+            if (contentPanel != null) {
+                final View spacer = contentPanel.findViewById(R.id.textSpacerNoTitle);
+                if (spacer != null) {
+                    spacer.setVisibility(View.VISIBLE);
+                }
+            }
+        }
+
+        // Update scroll indicators as needed.
+        if (!hasCustomPanel) {
+            final View content = mScrollView;
+            if (content != null) {
+                final int indicators = (hasTopPanel ? View.SCROLL_INDICATOR_TOP : 0)
+                        | (hasButtonPanel ? View.SCROLL_INDICATOR_BOTTOM : 0);
+                content.setScrollIndicators(indicators,
+                        View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
+            }
+        }
+
+        final TypedArray a = mContext.obtainStyledAttributes(
+                null, R.styleable.AlertDialog, R.attr.alertDialogStyle, 0);
+        setBackground(a, topPanel, contentPanel, customPanel, buttonPanel,
+                hasTopPanel, hasCustomPanel, hasButtonPanel);
+        a.recycle();
+    }
+
+    private boolean requestFocusForContent(View content) {
+        return content != null && content.requestFocus();
+    }
+
+    private void requestFocusForDefaultButton() {
+        if (mButtonPositive.getVisibility() == View.VISIBLE) {
+            mButtonPositive.requestFocus();
+        } else if (mButtonNegative.getVisibility() == View.VISIBLE) {
+            mButtonNegative.requestFocus();
+        } else if (mButtonNeutral.getVisibility() == View.VISIBLE) {
+            mButtonNeutral.requestFocus();
+        }
+    }
+
+    private void setupCustomContent(ViewGroup customPanel) {
+        final View customView;
+        if (mView != null) {
+            customView = mView;
+        } else if (mViewLayoutResId != 0) {
+            final LayoutInflater inflater = LayoutInflater.from(mContext);
+            customView = inflater.inflate(mViewLayoutResId, customPanel, false);
+        } else {
+            customView = null;
+        }
+
+        final boolean hasCustomView = customView != null;
+        if (!hasCustomView || !canTextInput(customView)) {
+            mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+                    WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+        }
+
+        if (hasCustomView) {
+            final FrameLayout custom = mWindow.findViewById(R.id.custom);
+            custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+            if (mViewSpacingSpecified) {
+                custom.setPadding(
+                        mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom);
+            }
+        } else {
+            customPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setupTitle(ViewGroup topPanel) {
+        mIconView = mWindow.findViewById(R.id.icon);
+
+        final boolean hasTextTitle = !TextUtils.isEmpty(mTitle);
+        if (hasTextTitle && mShowTitle) {
+            // Display the title if a title is supplied, else hide it.
+            mTitleView = mWindow.findViewById(R.id.alertTitle);
+            mTitleView.setText(mTitle);
+
+            // Do this last so that if the user has supplied any icons we
+            // use them instead of the default ones. If the user has
+            // specified 0 then make it disappear.
+            if (mIconId != 0) {
+                mIconView.setImageResource(mIconId);
+            } else if (mIcon != null) {
+                mIconView.setImageDrawable(mIcon);
+            } else {
+                // Apply the padding from the icon to ensure the title is
+                // aligned correctly.
+                mTitleView.setPadding(mIconView.getPaddingLeft(),
+                        mIconView.getPaddingTop(),
+                        mIconView.getPaddingRight(),
+                        mIconView.getPaddingBottom());
+                mIconView.setVisibility(View.GONE);
+            }
+        } else {
+            // Hide the title template
+            final View titleTemplate = mWindow.findViewById(R.id.title_template);
+            titleTemplate.setVisibility(View.GONE);
+            mIconView.setVisibility(View.GONE);
+            topPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setupContent(ViewGroup contentPanel) {
+        mScrollView = contentPanel.findViewById(R.id.scrollView);
+        mScrollView.setFocusable(false);
+
+        // Special case for users that only want to display a String
+        mMessageView = contentPanel.findViewById(R.id.message);
+        if (mMessageView == null) {
+            return;
+        }
+
+        mMessageView.setVisibility(View.GONE);
+        mScrollView.removeView(mMessageView);
+
+        contentPanel.setVisibility(View.GONE);
+    }
+
+    private void setupButtons(ViewGroup buttonPanel) {
+        int BIT_BUTTON_POSITIVE = 1;
+        int BIT_BUTTON_NEGATIVE = 2;
+        int BIT_BUTTON_NEUTRAL = 4;
+        int whichButtons = 0;
+        mButtonPositive = buttonPanel.findViewById(R.id.button1);
+        mButtonPositive.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonPositiveText)) {
+            mButtonPositive.setVisibility(View.GONE);
+        } else {
+            mButtonPositive.setText(mButtonPositiveText);
+            mButtonPositive.setVisibility(View.VISIBLE);
+            whichButtons = whichButtons | BIT_BUTTON_POSITIVE;
+        }
+
+        mButtonNegative = buttonPanel.findViewById(R.id.button2);
+        mButtonNegative.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNegativeText)) {
+            mButtonNegative.setVisibility(View.GONE);
+        } else {
+            mButtonNegative.setText(mButtonNegativeText);
+            mButtonNegative.setVisibility(View.VISIBLE);
+
+            whichButtons = whichButtons | BIT_BUTTON_NEGATIVE;
+        }
+
+        mButtonNeutral = buttonPanel.findViewById(R.id.button3);
+        mButtonNeutral.setOnClickListener(mButtonHandler);
+
+        if (TextUtils.isEmpty(mButtonNeutralText)) {
+            mButtonNeutral.setVisibility(View.GONE);
+        } else {
+            mButtonNeutral.setText(mButtonNeutralText);
+            mButtonNeutral.setVisibility(View.VISIBLE);
+
+            whichButtons = whichButtons | BIT_BUTTON_NEUTRAL;
+        }
+
+        final boolean hasButtons = whichButtons != 0;
+        if (!hasButtons) {
+            buttonPanel.setVisibility(View.GONE);
+        }
+    }
+
+    private void setBackground(TypedArray a, View topPanel, View contentPanel, View customPanel,
+            View buttonPanel, boolean hasTitle, boolean hasCustomView, boolean hasButtons) {
+        int fullDark = 0;
+        int topDark = 0;
+        int centerDark = 0;
+        int bottomDark = 0;
+        int fullBright = 0;
+        int topBright = 0;
+        int centerBright = 0;
+        int bottomBright = 0;
+        int bottomMedium = 0;
+
+        topBright = a.getResourceId(R.styleable.AlertDialog_topBright, topBright);
+        topDark = a.getResourceId(R.styleable.AlertDialog_topDark, topDark);
+        centerBright = a.getResourceId(R.styleable.AlertDialog_centerBright, centerBright);
+        centerDark = a.getResourceId(R.styleable.AlertDialog_centerDark, centerDark);
+
+        /* We now set the background of all of the sections of the alert.
+         * First collect together each section that is being displayed along
+         * with whether it is on a light or dark background, then run through
+         * them setting their backgrounds.  This is complicated because we need
+         * to correctly use the full, top, middle, and bottom graphics depending
+         * on how many views they are and where they appear.
+         */
+
+        final View[] views = new View[4];
+        final boolean[] light = new boolean[4];
+        View lastView = null;
+        boolean lastLight = false;
+
+        int pos = 0;
+        if (hasTitle) {
+            views[pos] = topPanel;
+            light[pos] = false;
+            pos++;
+        }
+
+        /* The contentPanel displays either a custom text message or
+         * a ListView. If it's text we should use the dark background
+         * for ListView we should use the light background. PIA does not use
+         * a list view. Hence, we set it to use dark background.  If neither
+         * are there the contentPanel will be hidden so set it as null.
+         */
+        views[pos] = contentPanel.getVisibility() == View.GONE ? null : contentPanel;
+        light[pos] = false;
+        pos++;
+
+        if (hasCustomView) {
+            views[pos] = customPanel;
+            light[pos] = false;
+            pos++;
+        }
+
+        if (hasButtons) {
+            views[pos] = buttonPanel;
+            light[pos] = true;
+        }
+
+        boolean setView = false;
+        for (pos = 0; pos < views.length; pos++) {
+            final View v = views[pos];
+            if (v == null) {
+                continue;
+            }
+
+            if (lastView != null) {
+                if (!setView) {
+                    lastView.setBackgroundResource(lastLight ? topBright : topDark);
+                } else {
+                    lastView.setBackgroundResource(lastLight ? centerBright : centerDark);
+                }
+                setView = true;
+            }
+
+            lastView = v;
+            lastLight = light[pos];
+        }
+
+        if (lastView != null) {
+            if (setView) {
+                bottomBright = a.getResourceId(R.styleable.AlertDialog_bottomBright, bottomBright);
+                bottomMedium = a.getResourceId(R.styleable.AlertDialog_bottomMedium, bottomMedium);
+                bottomDark = a.getResourceId(R.styleable.AlertDialog_bottomDark, bottomDark);
+
+                // ListViews will use the Bright background, but buttons use the
+                // Medium background.
+                lastView.setBackgroundResource(
+                        lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark);
+            } else {
+                fullBright = a.getResourceId(R.styleable.AlertDialog_fullBright, fullBright);
+                fullDark = a.getResourceId(R.styleable.AlertDialog_fullDark, fullDark);
+
+                lastView.setBackgroundResource(lastLight ? fullBright : fullDark);
+            }
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
new file mode 100644
index 0000000..e22171e
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/AlertDialogLayout.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packageinstaller;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.annotation.AttrRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+
+import com.android.packageinstaller.R;
+
+/**
+ * Special implementation of linear layout that's capable of laying out alert
+ * dialog components.
+ * <p>
+ * A dialog consists of up to three panels. All panels are optional, and a
+ * dialog may contain only a single panel. The panels are laid out according
+ * to the following guidelines:
+ * <ul>
+ *     <li>topPanel: exactly wrap_content</li>
+ *     <li>contentPanel OR customPanel: at most fill_parent, first priority for
+ *         extra space</li>
+ *     <li>buttonPanel: at least minHeight, at most wrap_content, second
+ *         priority for extra space</li>
+ * </ul>
+ */
+public class AlertDialogLayout extends LinearLayout {
+
+    public AlertDialogLayout(@Nullable Context context) {
+        super(context);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlertDialogLayout(@Nullable Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!tryOnMeasure(widthMeasureSpec, heightMeasureSpec)) {
+            // Failed to perform custom measurement, let superclass handle it.
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+    }
+
+    private boolean tryOnMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        View topPanel = null;
+        View buttonPanel = null;
+        View middlePanel = null;
+
+        final int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+
+            final int id = child.getId();
+            switch (id) {
+                case R.id.topPanel:
+                    topPanel = child;
+                    break;
+                case R.id.buttonPanel:
+                    buttonPanel = child;
+                    break;
+                case R.id.contentPanel:
+                case R.id.customPanel:
+                    if (middlePanel != null) {
+                        // Both the content and custom are visible. Abort!
+                        return false;
+                    }
+                    middlePanel = child;
+                    break;
+                default:
+                    // Unknown top-level child. Abort!
+                    return false;
+            }
+        }
+
+        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+        final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+
+        int childState = 0;
+        int usedHeight = getPaddingTop() + getPaddingBottom();
+
+        if (topPanel != null) {
+            topPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+
+            usedHeight += topPanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, topPanel.getMeasuredState());
+        }
+
+        int buttonHeight = 0;
+        int buttonWantsHeight = 0;
+        if (buttonPanel != null) {
+            buttonPanel.measure(widthMeasureSpec, MeasureSpec.UNSPECIFIED);
+            buttonHeight = resolveMinimumHeight(buttonPanel);
+            buttonWantsHeight = buttonPanel.getMeasuredHeight() - buttonHeight;
+
+            usedHeight += buttonHeight;
+            childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+        }
+
+        int middleHeight = 0;
+        if (middlePanel != null) {
+            final int childHeightSpec;
+            if (heightMode == MeasureSpec.UNSPECIFIED) {
+                childHeightSpec = MeasureSpec.UNSPECIFIED;
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(
+                        Math.max(0, heightSize - usedHeight), heightMode);
+            }
+
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+            middleHeight = middlePanel.getMeasuredHeight();
+
+            usedHeight += middleHeight;
+            childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+        }
+
+        int remainingHeight = heightSize - usedHeight;
+
+        // Time for the "real" button measure pass. If we have remaining space,
+        // make the button pane bigger up to its target height. Otherwise,
+        // just remeasure the button at whatever height it needs.
+        if (buttonPanel != null) {
+            usedHeight -= buttonHeight;
+
+            final int heightToGive = Math.min(remainingHeight, buttonWantsHeight);
+            if (heightToGive > 0) {
+                remainingHeight -= heightToGive;
+                buttonHeight += heightToGive;
+            }
+
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    buttonHeight, MeasureSpec.EXACTLY);
+            buttonPanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += buttonPanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, buttonPanel.getMeasuredState());
+        }
+
+        // If we still have remaining space, make the middle pane bigger up
+        // to the maximum height.
+        if (middlePanel != null && remainingHeight > 0) {
+            usedHeight -= middleHeight;
+
+            final int heightToGive = remainingHeight;
+            remainingHeight -= heightToGive;
+            middleHeight += heightToGive;
+
+            // Pass the same height mode as we're using for the dialog itself.
+            // If it's EXACTLY, then the middle pane MUST use the entire
+            // height.
+            final int childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    middleHeight, heightMode);
+            middlePanel.measure(widthMeasureSpec, childHeightSpec);
+
+            usedHeight += middlePanel.getMeasuredHeight();
+            childState = combineMeasuredStates(childState, middlePanel.getMeasuredState());
+        }
+
+        // Compute desired width as maximum child width.
+        int maxWidth = 0;
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != View.GONE) {
+                maxWidth = Math.max(maxWidth, child.getMeasuredWidth());
+            }
+        }
+
+        maxWidth += getPaddingLeft() + getPaddingRight();
+
+        final int widthSizeAndState = resolveSizeAndState(maxWidth, widthMeasureSpec, childState);
+        final int heightSizeAndState = resolveSizeAndState(usedHeight, heightMeasureSpec, 0);
+        setMeasuredDimension(widthSizeAndState, heightSizeAndState);
+
+        // If the children weren't already measured EXACTLY, we need to run
+        // another measure pass to for MATCH_PARENT widths.
+        if (widthMode != MeasureSpec.EXACTLY) {
+            forceUniformWidth(count, heightMeasureSpec);
+        }
+
+        return true;
+    }
+
+    /**
+     * Remeasures child views to exactly match the layout's measured width.
+     *
+     * @param count the number of child views
+     * @param heightMeasureSpec the original height measure spec
+     */
+    private void forceUniformWidth(int count, int heightMeasureSpec) {
+        // Pretend that the linear layout has an exact size.
+        final int uniformMeasureSpec = MeasureSpec.makeMeasureSpec(
+                getMeasuredWidth(), MeasureSpec.EXACTLY);
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.getVisibility() != GONE) {
+                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    // Temporarily force children to reuse their old measured
+                    // height.
+                    final int oldHeight = lp.height;
+                    lp.height = child.getMeasuredHeight();
+
+                    // Remeasure with new dimensions.
+                    measureChildWithMargins(child, uniformMeasureSpec, 0, heightMeasureSpec, 0);
+                    lp.height = oldHeight;
+                }
+            }
+        }
+    }
+
+    /**
+     * Attempts to resolve the minimum height of a view.
+     * <p>
+     * If the view doesn't have a minimum height set and only contains a single
+     * child, attempts to resolve the minimum height of the child view.
+     *
+     * @param v the view whose minimum height to resolve
+     * @return the minimum height
+     */
+    private int resolveMinimumHeight(View v) {
+        final int minHeight = v.getMinimumHeight();
+        if (minHeight > 0) {
+            return minHeight;
+        }
+
+        if (v instanceof ViewGroup) {
+            final ViewGroup vg = (ViewGroup) v;
+            if (vg.getChildCount() == 1) {
+                return resolveMinimumHeight(vg.getChildAt(0));
+            }
+        }
+
+        return 0;
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        final int paddingLeft = getPaddingLeft();
+        final int paddingRight = getPaddingRight();
+        final int paddingTop = getPaddingTop();
+
+        // Where right end of child should go
+        final int width = right - left;
+        final int childRight = width - paddingRight;
+
+        // Space available for child
+        final int childSpace = width - paddingLeft - paddingRight;
+
+        final int totalLength = getMeasuredHeight();
+        final int count = getChildCount();
+        final int gravity = getGravity();
+        final int majorGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
+        final int minorGravity = gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
+
+        int childTop;
+        switch (majorGravity) {
+            case Gravity.BOTTOM:
+                // totalLength contains the padding already
+                childTop = paddingTop + bottom - top - totalLength;
+                break;
+
+            // totalLength contains the padding already
+            case Gravity.CENTER_VERTICAL:
+                childTop = paddingTop + (bottom - top - totalLength) / 2;
+                break;
+
+            case Gravity.TOP:
+            default:
+                childTop = paddingTop;
+                break;
+        }
+
+        final Drawable dividerDrawable = getDividerDrawable();
+        final int dividerHeight = dividerDrawable == null ?
+                0 : dividerDrawable.getIntrinsicHeight();
+
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child != null && child.getVisibility() != GONE) {
+                final int childWidth = child.getMeasuredWidth();
+                final int childHeight = child.getMeasuredHeight();
+
+                final LayoutParams lp =
+                        (LayoutParams) child.getLayoutParams();
+
+                int layoutGravity = lp.gravity;
+                if (layoutGravity < 0) {
+                    layoutGravity = minorGravity;
+                }
+                final int layoutDirection = getLayoutDirection();
+                final int absoluteGravity = Gravity.getAbsoluteGravity(
+                        layoutGravity, layoutDirection);
+
+                final int childLeft;
+                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                    case Gravity.CENTER_HORIZONTAL:
+                        childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+                                + lp.leftMargin - lp.rightMargin;
+                        break;
+
+                    case Gravity.RIGHT:
+                        childLeft = childRight - childWidth - lp.rightMargin;
+                        break;
+
+                    case Gravity.LEFT:
+                    default:
+                        childLeft = paddingLeft + lp.leftMargin;
+                        break;
+                }
+
+                childTop += lp.topMargin;
+                setChildFrame(child, childLeft, childTop, childWidth, childHeight);
+                childTop += childHeight + lp.bottomMargin;
+            }
+        }
+    }
+
+    private void setChildFrame(View child, int left, int top, int width, int height) {
+        child.layout(left, top, left + width, top + height);
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
new file mode 100644
index 0000000..8d478c4
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/ButtonBarLayout.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.packageinstaller.R;
+
+/**
+ * An extension of LinearLayout that automatically switches to vertical
+ * orientation when it can't fit its child views horizontally.
+ */
+public class ButtonBarLayout extends LinearLayout {
+    /** Amount of the second button to "peek" above the fold when stacked. */
+    private static final int PEEK_BUTTON_DP = 16;
+
+    /** Whether the current configuration allows stacking. */
+    private boolean mAllowStacking;
+
+    private int mLastWidthSize = -1;
+
+    private int mMinimumHeight = 0;
+
+    public ButtonBarLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
+        mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, true);
+        ta.recycle();
+    }
+
+    public void setAllowStacking(boolean allowStacking) {
+        if (mAllowStacking != allowStacking) {
+            mAllowStacking = allowStacking;
+            if (!mAllowStacking && getOrientation() == LinearLayout.VERTICAL) {
+                setStacked(false);
+            }
+            requestLayout();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
+
+        if (mAllowStacking) {
+            if (widthSize > mLastWidthSize && isStacked()) {
+                // We're being measured wider this time, try un-stacking.
+                setStacked(false);
+            }
+
+            mLastWidthSize = widthSize;
+        }
+
+        boolean needsRemeasure = false;
+
+        // If we're not stacked, make sure the measure spec is AT_MOST rather
+        // than EXACTLY. This ensures that we'll still get TOO_SMALL so that we
+        // know to stack the buttons.
+        final int initialWidthMeasureSpec;
+        if (!isStacked() && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {
+            initialWidthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.AT_MOST);
+
+            // We'll need to remeasure again to fill excess space.
+            needsRemeasure = true;
+        } else {
+            initialWidthMeasureSpec = widthMeasureSpec;
+        }
+
+        super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
+
+        if (mAllowStacking && !isStacked()) {
+            final int measuredWidth = getMeasuredWidthAndState();
+            final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
+            if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+                setStacked(true);
+
+                // Measure again in the new orientation.
+                needsRemeasure = true;
+            }
+        }
+
+        if (needsRemeasure) {
+            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        }
+
+        // Compute minimum height such that, when stacked, some portion of the
+        // second button is visible.
+        int minHeight = 0;
+        final int firstVisible = getNextVisibleChildIndex(0);
+        if (firstVisible >= 0) {
+            final View firstButton = getChildAt(firstVisible);
+            final LayoutParams firstParams = (LayoutParams) firstButton.getLayoutParams();
+            minHeight += getPaddingTop() + firstButton.getMeasuredHeight()
+                    + firstParams.topMargin + firstParams.bottomMargin;
+            if (isStacked()) {
+                final int secondVisible = getNextVisibleChildIndex(firstVisible + 1);
+                if (secondVisible >= 0) {
+                    minHeight += getChildAt(secondVisible).getPaddingTop()
+                            + PEEK_BUTTON_DP * getResources().getDisplayMetrics().density;
+                }
+            } else {
+                minHeight += getPaddingBottom();
+            }
+        }
+
+        if (getMinimumHeight() != minHeight) {
+            setMinimumHeight(minHeight);
+        }
+    }
+
+    private int getNextVisibleChildIndex(int index) {
+        for (int i = index, count = getChildCount(); i < count; i++) {
+            if (getChildAt(i).getVisibility() == View.VISIBLE) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    @Override
+    public int getMinimumHeight() {
+        return Math.max(mMinimumHeight, super.getMinimumHeight());
+    }
+
+    private void setStacked(boolean stacked) {
+        setOrientation(stacked ? LinearLayout.VERTICAL : LinearLayout.HORIZONTAL);
+        setGravity(stacked ? Gravity.END : Gravity.BOTTOM);
+
+        final View spacer = findViewById(R.id.spacer);
+        if (spacer != null) {
+            spacer.setVisibility(stacked ? View.GONE : View.INVISIBLE);
+        }
+
+        // Reverse the child order. This is specific to the Material button
+        // bar's layout XML and will probably not generalize.
+        final int childCount = getChildCount();
+        for (int i = childCount - 2; i >= 0; i--) {
+            bringChildToFront(getChildAt(i));
+        }
+    }
+
+    private boolean isStacked() {
+        return getOrientation() == LinearLayout.VERTICAL;
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 33e5231..4c5875b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
+
 import java.io.File;
 
 /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
new file mode 100644
index 0000000..068834c
--- /dev/null
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DialogTitle.java
@@ -0,0 +1,78 @@
+/* 
+ * Copyright (C) 2008 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.packageinstaller;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.widget.TextView;
+
+import com.android.packageinstaller.R;
+/**
+ * Used by dialogs to change the font size and number of lines to try to fit
+ * the text to the available space.
+ */
+public class DialogTitle extends TextView {
+
+    public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public DialogTitle(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public DialogTitle(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public DialogTitle(Context context) {
+        super(context);
+    }
+    
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+        final Layout layout = getLayout();
+        if (layout != null) {
+            final int lineCount = layout.getLineCount();
+            if (lineCount > 0) {
+                final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
+                if (ellipsisCount > 0) {
+                    setSingleLine(false);
+                    setMaxLines(2);
+
+                    final TypedArray a = getContext().obtainStyledAttributes(null,
+                            R.styleable.TextAppearance, android.R.attr.textAppearanceMedium,
+                            android.R.style.TextAppearance_Medium);
+                    final int textSize = a.getDimensionPixelSize(
+                            R.styleable.TextAppearance_textSize, 0);
+                    if (textSize != 0) {
+                        // textSize is already expressed in pixels
+                        setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+                    }
+                    a.recycle();
+
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);      
+                }
+            }
+        }
+    }
+}
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
index 3a94fdc..8639f47 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/EventResultPersister.java
@@ -16,8 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
@@ -27,6 +25,9 @@
 import android.util.SparseArray;
 import android.util.Xml;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -40,17 +41,18 @@
 /**
  * Persists results of events and calls back observers when a matching result arrives.
  */
-class EventResultPersister {
+public class EventResultPersister {
     private static final String LOG_TAG = EventResultPersister.class.getSimpleName();
 
     /** Id passed to {@link #addObserver(int, EventResultObserver)} to generate new id */
-    static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
+    public static final int GENERATE_NEW_ID = Integer.MIN_VALUE;
 
     /**
      * The extra with the id to set in the intent delivered to
      * {@link #onEventReceived(Context, Intent)}
      */
-    static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+    public static final String EXTRA_ID = "EventResultPersister.EXTRA_ID";
+    public static final String EXTRA_SERVICE_ID = "EventResultPersister.EXTRA_SERVICE_ID";
 
     /** Persisted state of this object */
     private final AtomicFile mResultsFile;
@@ -89,8 +91,8 @@
     }
 
     /** Call back when a result is received. Observer is removed when onResult it called. */
-    interface EventResultObserver {
-        void onResult(int status, int legacyStatus, @Nullable String message);
+    public interface EventResultObserver {
+        void onResult(int status, int legacyStatus, @Nullable String message, int serviceId);
     }
 
     /**
@@ -153,12 +155,14 @@
                     int status = readIntAttribute(parser, "status");
                     int legacyStatus = readIntAttribute(parser, "legacyStatus");
                     String statusMessage = readStringAttribute(parser, "statusMessage");
+                    int serviceId = readIntAttribute(parser, "serviceId");
 
                     if (mResults.get(id) != null) {
                         throw new Exception("id " + id + " has two results");
                     }
 
-                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+                    mResults.put(id, new EventResult(status, legacyStatus, statusMessage,
+                            serviceId));
                 } else {
                     throw new Exception("unexpected tag");
                 }
@@ -190,6 +194,7 @@
         int id = intent.getIntExtra(EXTRA_ID, 0);
         String statusMessage = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
         int legacyStatus = intent.getIntExtra(PackageInstaller.EXTRA_LEGACY_STATUS, 0);
+        int serviceId = intent.getIntExtra(EXTRA_SERVICE_ID, 0);
 
         EventResultObserver observerToCall = null;
         synchronized (mLock) {
@@ -204,9 +209,9 @@
             }
 
             if (observerToCall != null) {
-                observerToCall.onResult(status, legacyStatus, statusMessage);
+                observerToCall.onResult(status, legacyStatus, statusMessage, serviceId);
             } else {
-                mResults.put(id, new EventResult(status, legacyStatus, statusMessage));
+                mResults.put(id, new EventResult(status, legacyStatus, statusMessage, serviceId));
                 writeState();
             }
         }
@@ -258,6 +263,8 @@
                                     serializer.attribute(null, "statusMessage",
                                             results.valueAt(i).message);
                                 }
+                                serializer.attribute(null, "serviceId",
+                                        Integer.toString(results.valueAt(i).serviceId));
                                 serializer.endTag(null, "result");
                             }
 
@@ -311,7 +318,8 @@
             if (resultIndex >= 0) {
                 EventResult result = mResults.valueAt(resultIndex);
 
-                observer.onResult(result.status, result.legacyStatus, result.message);
+                observer.onResult(result.status, result.legacyStatus, result.message,
+                        result.serviceId);
                 mResults.removeAt(resultIndex);
                 writeState();
             } else {
@@ -341,13 +349,15 @@
         public final int status;
         public final int legacyStatus;
         @Nullable public final String message;
+        public final int serviceId;
 
-        private EventResult(int status, int legacyStatus, @Nullable String message) {
+        private EventResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
             this.status = status;
             this.legacyStatus = legacyStatus;
             this.message = message;
+            this.serviceId = serviceId;
         }
     }
 
-    class OutOfIdsException extends Exception {}
+    public class OutOfIdsException extends Exception {}
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
index c70d7db..be8eabb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallEventReceiver.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 /**
  * Receives install events and perists them using a {@link EventResultPersister}.
  */
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
index 54105bb..3505cfb 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallFailed.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -32,7 +31,7 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 
@@ -79,6 +78,8 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
+
         int statusCode = getIntent().getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 3aa8dbf..93387e2 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -16,28 +16,22 @@
 
 package com.android.packageinstaller;
 
-import static android.content.pm.PackageInstaller.SessionParams.UID_UNKNOWN;
-
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.InstallInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.parsing.ApkLiteParseUtils;
-import android.content.pm.parsing.PackageLite;
-import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Process;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
-import com.android.internal.content.InstallLocationUtils;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -139,34 +133,30 @@
                 params.setOriginatingUri(getIntent()
                         .getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));
                 params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                        UID_UNKNOWN));
+                        Process.INVALID_UID));
                 params.setInstallerPackageName(getIntent().getStringExtra(
                         Intent.EXTRA_INSTALLER_PACKAGE_NAME));
                 params.setInstallReason(PackageManager.INSTALL_REASON_USER);
 
                 File file = new File(mPackageURI.getPath());
                 try {
-                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-                    final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(
-                            input.reset(), file, /* flags */ 0);
-                    if (result.isError()) {
-                        Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");
-                        Log.e(LOG_TAG,
-                                "Cannot calculate installed size " + file + ". Try only apk size.");
+                    final InstallInfo result = getPackageManager().getPackageInstaller()
+                            .getInstallInfo(file, 0);
+                    params.setAppPackageName(result.getPackageName());
+                    params.setInstallLocation(result.getInstallLocation());
+                    try {
+                        params.setSize(result.calculateInstalledSize(params));
+                    } catch (IOException e) {
+                        e.printStackTrace();
                         params.setSize(file.length());
-                    } else {
-                        final PackageLite pkg = result.getResult();
-                        params.setAppPackageName(pkg.getPackageName());
-                        params.setInstallLocation(pkg.getInstallLocation());
-                        params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,
-                                params.abiOverride));
                     }
-                } catch (IOException e) {
+                } catch (PackageInstaller.PackageParsingException e) {
+
+                    Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.", e);
                     Log.e(LOG_TAG,
                             "Cannot calculate installed size " + file + ". Try only apk size.");
                     params.setSize(file.length());
                 }
-
                 try {
                     mInstallId = InstallEventReceiver
                             .addObserver(this, EventResultPersister.GENERATE_NEW_ID,
@@ -281,8 +271,11 @@
      * @param statusCode    The installation result.
      * @param legacyStatus  The installation as used internally in the package manager.
      * @param statusMessage The detailed installation result.
+     * @param serviceId     Id for PowerManager.WakeLock service. Used only by Wear devices
+     *                      during an uninstall.
      */
-    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {
+    private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage,
+            int serviceId /* ignore */) {
         if (statusCode == PackageInstaller.STATUS_SUCCESS) {
             launchSuccess();
         } else {
@@ -292,7 +285,7 @@
 
     /**
      * Send the package to the package installer and then register a event result observer that
-     * will call {@link #launchFinishBasedOnResult(int, int, String)}
+     * will call {@link #launchFinishBasedOnResult(int, int, String, int)}
      */
     private final class InstallingAsyncTask extends AsyncTask<Void, Void,
             PackageInstaller.Session> {
@@ -318,6 +311,7 @@
 
                 try (InputStream in = new FileInputStream(file)) {
                     long sizeBytes = file.length();
+                    long totalRead = 0;
                     try (OutputStream out = session
                             .openWrite("PackageInstaller", 0, sizeBytes)) {
                         byte[] buffer = new byte[1024 * 1024];
@@ -336,8 +330,9 @@
 
                             out.write(buffer, 0, numRead);
                             if (sizeBytes > 0) {
-                                float fraction = ((float) numRead / (float) sizeBytes);
-                                session.addProgress(fraction);
+                                totalRead += numRead;
+                                float fraction = ((float) totalRead / (float) sizeBytes);
+                                session.setStagingProgress(fraction);
                             }
                         }
                     }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
index b6b8837..68de2f6 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
@@ -31,7 +30,7 @@
 import android.util.Log;
 import android.view.View;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -58,6 +57,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
         mAlert.setIcon(R.drawable.ic_file_download);
         mAlert.setTitle(getString(R.string.app_name_unknown));
         mAlert.setView(R.layout.install_content_view);
@@ -123,7 +123,8 @@
      * Show an error message and set result as error.
      */
     private void showError() {
-        (new ErrorDialog()).showAllowingStateLoss(getFragmentManager(), "error");
+        getFragmentManager().beginTransaction()
+                .add(new ErrorDialog(), "error").commitAllowingStateLoss();
 
         Intent result = new Intent();
         result.putExtra(Intent.EXTRA_INSTALL_RESULT,
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
index 88c1036..7338e64 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
@@ -19,26 +19,25 @@
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.RemoteException;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.util.Arrays;
 
 /**
@@ -80,12 +79,11 @@
         final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
         final int originatingUid = getOriginatingUid(sourceInfo);
         boolean isTrustedSource = false;
-        if (sourceInfo != null
-                && (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+        if (sourceInfo != null && sourceInfo.isPrivilegedApp()) {
             isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
         }
 
-        if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+        if (!isTrustedSource && originatingUid != Process.INVALID_UID) {
             final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
             if (targetSdkVersion < 0) {
                 Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
@@ -99,6 +97,10 @@
             }
         }
 
+        if (sessionId != -1 && !isCallerSessionOwner(originatingUid, sessionId)) {
+            mAbortInstall = true;
+        }
+
         final String installerPackageNameFromIntent = getIntent().getStringExtra(
                 Intent.EXTRA_INSTALLER_PACKAGE_NAME);
         if (installerPackageNameFromIntent != null) {
@@ -208,30 +210,27 @@
     }
 
     /**
-     * Get the originating uid if possible, or
-     * {@link android.content.pm.PackageInstaller.SessionParams#UID_UNKNOWN} if not available
+     * Get the originating uid if possible, or {@link Process#INVALID_UID} if not available
      *
      * @param sourceInfo The source of this installation
-     * @return The UID of the installation source or UID_UNKNOWN
+     * @return The UID of the installation source or INVALID_UID
      */
     private int getOriginatingUid(@Nullable ApplicationInfo sourceInfo) {
         // The originating uid from the intent. We only trust/use this if it comes from either
         // the document manager app or the downloads provider
         final int uidFromIntent = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                PackageInstaller.SessionParams.UID_UNKNOWN);
+                Process.INVALID_UID);
 
         final int callingUid;
         if (sourceInfo != null) {
             callingUid = sourceInfo.uid;
         } else {
-            try {
-                callingUid = ActivityManager.getService()
-                        .getLaunchedFromUid(getActivityToken());
-            } catch (RemoteException ex) {
+            callingUid = getLaunchedFromUid();
+            if (callingUid == Process.INVALID_UID) {
                 // Cannot reach ActivityManager. Aborting install.
                 Log.e(LOG_TAG, "Could not determine the launching uid.");
                 mAbortInstall = true;
-                return PackageInstaller.SessionParams.UID_UNKNOWN;
+                return Process.INVALID_UID;
             }
         }
         if (checkPermission(Manifest.permission.MANAGE_DOCUMENTS, -1, callingUid)
@@ -253,7 +252,8 @@
             return false;
         }
         final ApplicationInfo appInfo = downloadProviderPackage.applicationInfo;
-        return (appInfo.isSystemApp() && uid == appInfo.uid);
+        return ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+                && uid == appInfo.uid);
     }
 
     @NonNull
@@ -268,8 +268,14 @@
 
         try {
             return mPackageManager.canPackageQuery(callingPackage, targetPackage);
-        } catch (NameNotFoundException e) {
+        } catch (PackageManager.NameNotFoundException e) {
             return false;
         }
     }
+
+    private boolean isCallerSessionOwner(int originatingUid, int sessionId) {
+        PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+        int installerUid = packageInstaller.getSessionInfo(sessionId).getInstallerUid();
+        return (originatingUid == Process.ROOT_UID) || (originatingUid == installerUid);
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 38c06dd..73c03a5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
 import android.content.DialogInterface;
@@ -30,7 +29,7 @@
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.Nullable;
 
 import java.io.File;
 import java.util.List;
@@ -54,6 +53,8 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
+        setFinishOnTouchOutside(true);
+
         if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
             // Return result if requested
             Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
index eea12ec..228a9f5 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledNotificationUtils.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -24,7 +23,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -32,8 +30,11 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 /**
  * A util class that handle and post new app installed notifications.
  */
@@ -107,8 +108,8 @@
             @NonNull String packageName) {
         CharSequence label = appInfo.loadSafeLabel(context.getPackageManager(),
                 DEFAULT_MAX_LABEL_SIZE_PX,
-                PackageItemInfo.SAFE_LABEL_FLAG_TRIM
-                        | PackageItemInfo.SAFE_LABEL_FLAG_FIRST_LINE).toString();
+                TextUtils.SAFE_STRING_FLAG_TRIM
+                        | TextUtils.SAFE_STRING_FLAG_FIRST_LINE).toString();
         if (label != null) {
             return label.toString();
         }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
index 74c7b58..2278f7c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstalledReceiver.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
-import android.provider.Settings;
 import android.util.Log;
 
 /**
@@ -33,8 +32,7 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (Settings.Global.getInt(context.getContentResolver(),
-                Settings.Global.SHOW_NEW_APP_INSTALLED_NOTIFICATION_ENABLED, 0) == 0) {
+        if (!context.getPackageManager().shouldShowNewAppInstalledNotification()) {
             return;
         }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index fa93670..3138158 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -21,11 +21,8 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.AlertDialog;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -36,24 +33,26 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.BitmapDrawable;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 
-import com.android.internal.app.AlertActivity;
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -86,13 +85,12 @@
     private Uri mPackageURI;
     private Uri mOriginatingURI;
     private Uri mReferrerURI;
-    private int mOriginatingUid = PackageInstaller.SessionParams.UID_UNKNOWN;
+    private int mOriginatingUid = Process.INVALID_UID;
     private String mOriginatingPackage; // The package name corresponding to #mOriginatingUid
     private int mActivityResultCode = Activity.RESULT_CANCELED;
 
     private final boolean mLocalLOGV = false;
     PackageManager mPm;
-    IPackageManager mIpm;
     AppOpsManager mAppOpsManager;
     UserManager mUserManager;
     PackageInstaller mInstaller;
@@ -166,7 +164,8 @@
 
         DialogFragment newDialog = createDialog(id);
         if (newDialog != null) {
-            newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
+            getFragmentManager().beginTransaction()
+                    .add(newDialog, "dialog").commitAllowingStateLoss();
         }
     }
 
@@ -211,9 +210,9 @@
             // Log the fact that the app is requesting an install, and is now allowed to do it
             // (before this point we could only log that it's requesting an install, but isn't
             // allowed to do it yet).
-            int appOpCode =
-                    AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-            mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage,
+            String appOpStr =
+                    AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+            mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid, mOriginatingPackage,
                     mCallingAttributionTag,
                     "Successfully started package installation activity");
 
@@ -250,12 +249,9 @@
     private boolean isInstallRequestFromUnknownSource(Intent intent) {
         if (mCallingPackage != null && intent.getBooleanExtra(
                 Intent.EXTRA_NOT_UNKNOWN_SOURCE, false)) {
-            if (mSourceInfo != null) {
-                if ((mSourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                        != 0) {
-                    // Privileged apps can bypass unknown sources check if they want.
-                    return false;
-                }
+            if (mSourceInfo != null && mSourceInfo.isPrivilegedApp()) {
+                // Privileged apps can bypass unknown sources check if they want.
+                return false;
             }
         }
         return true;
@@ -305,7 +301,7 @@
 
     @Override
     protected void onCreate(Bundle icicle) {
-        if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
+        if (mLocalLOGV) Log.i(TAG, "creating for user " + UserHandle.myUserId());
         getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
 
         super.onCreate(null);
@@ -316,7 +312,6 @@
         setFinishOnTouchOutside(true);
 
         mPm = getPackageManager();
-        mIpm = AppGlobals.getPackageManager();
         mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
         mInstaller = mPm.getPackageInstaller();
         mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
@@ -328,8 +323,8 @@
         mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
         mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
         mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
-                PackageInstaller.SessionParams.UID_UNKNOWN);
-        mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
+                Process.INVALID_UID);
+        mOriginatingPackage = (mOriginatingUid != Process.INVALID_UID)
                 ? getPackageNameForUid(mOriginatingUid) : null;
 
         final Object packageSource;
@@ -337,21 +332,23 @@
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
+            final String resolvedBaseCodePath = intent.getStringExtra(
+                    PackageInstaller.EXTRA_RESOLVED_BASE_PATH);
+            if (info == null || !info.isSealed() || resolvedBaseCodePath == null) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
                 return;
             }
 
             mSessionId = sessionId;
-            packageSource = Uri.fromFile(new File(info.resolvedBaseCodePath));
+            packageSource = Uri.fromFile(new File(resolvedBaseCodePath));
             mOriginatingURI = null;
             mReferrerURI = null;
         } else if (PackageInstaller.ACTION_CONFIRM_PRE_APPROVAL.equals(action)) {
             final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID,
                     -1 /* defaultValue */);
             final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-            if (info == null || !info.isPreapprovalRequested) {
+            if (info == null || !info.getIsPreApprovalRequested()) {
                 Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
                 finish();
                 return;
@@ -547,15 +544,15 @@
             return;
         }
         // Shouldn't use static constant directly, see b/65534401.
-        final int appOpCode =
-                AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
-        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid,
+        final String appOpStr =
+                AppOpsManager.permissionToOp(Manifest.permission.REQUEST_INSTALL_PACKAGES);
+        final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpStr, mOriginatingUid,
                 mOriginatingPackage, mCallingAttributionTag,
                 "Started package installation activity");
         if (mLocalLOGV) Log.i(TAG, "handleUnknownSources(): appMode=" + appOpMode);
         switch (appOpMode) {
             case AppOpsManager.MODE_DEFAULT:
-                mAppOpsManager.setMode(appOpCode, mOriginatingUid,
+                mAppOpsManager.setMode(appOpStr, mOriginatingUid,
                         mOriginatingPackage, AppOpsManager.MODE_ERRORED);
                 // fall through
             case AppOpsManager.MODE_ERRORED:
@@ -588,8 +585,8 @@
 
         switch (scheme) {
             case SCHEME_PACKAGE: {
-                for (UserInfo info : mUserManager.getUsers()) {
-                    PackageManager pmForUser = createContextAsUser(info.getUserHandle(), 0)
+                for (UserHandle handle : mUserManager.getUserHandles(true)) {
+                    PackageManager pmForUser = createContextAsUser(handle, 0)
                                                 .getPackageManager();
                     try {
                         if (pmForUser.canPackageQuery(mCallingPackage, packageName)) {
@@ -645,9 +642,9 @@
      * @return {@code true} iff the installer could be set up
      */
     private boolean processSessionInfo(@NonNull SessionInfo info) {
-        mPkgInfo = generateStubPackageInfo(info.appPackageName);
-        mAppSnippet = new PackageUtil.AppSnippet(info.appLabel,
-                info.appIcon != null ? new BitmapDrawable(getResources(), info.appIcon)
+        mPkgInfo = generateStubPackageInfo(info.getAppPackageName());
+        mAppSnippet = new PackageUtil.AppSnippet(info.getAppLabel(),
+                info.getAppIcon() != null ? new BitmapDrawable(getResources(), info.getAppIcon())
                         : getPackageManager().getDefaultActivityIcon());
         return true;
     }
@@ -693,7 +690,7 @@
         if (mReferrerURI != null) {
             newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
         }
-        if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
+        if (mOriginatingUid != Process.INVALID_UID) {
             newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
         }
         if (installerPackageName != null) {
@@ -829,7 +826,7 @@
             if (isDestroyed()) {
                 return;
             }
-            getMainThreadHandler().postDelayed(() -> {
+            new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
                     startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
                 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
index f5570df..698159f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
@@ -17,14 +17,11 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
 import android.os.UserHandle;
@@ -33,6 +30,9 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.File;
 
 /**
@@ -131,16 +131,15 @@
     public static AppSnippet getAppSnippet(
             Activity pContext, ApplicationInfo appInfo, File sourceFile) {
         final String archiveFilePath = sourceFile.getAbsolutePath();
-        Resources pRes = pContext.getResources();
-        AssetManager assmgr = new AssetManager();
-        assmgr.addAssetPath(archiveFilePath);
-        Resources res = new Resources(assmgr, pRes.getDisplayMetrics(), pRes.getConfiguration());
+        PackageManager pm = pContext.getPackageManager();
+        appInfo.publicSourceDir = archiveFilePath;
+
         CharSequence label = null;
         // Try to load the label from the package's resources. If an app has not explicitly
         // specified any label, just use the package name.
         if (appInfo.labelRes != 0) {
             try {
-                label = res.getText(appInfo.labelRes);
+                label = appInfo.loadLabel(pm);
             } catch (Resources.NotFoundException e) {
             }
         }
@@ -154,7 +153,7 @@
         try {
             if (appInfo.icon != 0) {
                 try {
-                    icon = res.getDrawable(appInfo.icon);
+                    icon = appInfo.loadIcon(pm);
                 } catch (Resources.NotFoundException e) {
                 }
             }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
index f77318c..afb2ea4 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/TemporaryFileManager.java
@@ -16,13 +16,14 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.os.SystemClock;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 import java.io.File;
 import java.io.IOException;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
index c3e9c23..86b0321 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallEventReceiver.java
@@ -16,11 +16,12 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 
+import androidx.annotation.NonNull;
+
 /**
  * Receives uninstall events and persists them using a {@link EventResultPersister}.
  */
@@ -58,7 +59,7 @@
      *
      * @return The id for this event
      */
-    static int addObserver(@NonNull Context context, int id,
+    public static int addObserver(@NonNull Context context, int id,
             @NonNull EventResultPersister.EventResultObserver observer)
             throws EventResultPersister.OutOfIdsException {
         return getReceiver(context).addObserver(id, observer);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
index 973ab89..b9552fc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallFinish.java
@@ -16,29 +16,27 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
-import android.app.admin.IDevicePolicyManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.graphics.drawable.Icon;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.annotation.NonNull;
+
 import java.util.List;
 
 /**
@@ -94,28 +92,24 @@
 
                 switch (legacyStatus) {
                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
-                        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
-                                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
                         // Find out if the package is an active admin for some non-current user.
-                        int myUserId = UserHandle.myUserId();
-                        UserInfo otherBlockingUser = null;
-                        for (UserInfo user : userManager.getUsers()) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        UserHandle otherBlockingUserHandle = null;
+                        for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
                             // We only catch the case when the user in question is neither the
                             // current user nor its profile.
-                            if (isProfileOfOrSame(userManager, myUserId, user.id)) {
+                            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
                                 continue;
                             }
-
-                            try {
-                                if (dpm.packageHasActiveAdmins(appInfo.packageName, user.id)) {
-                                    otherBlockingUser = user;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                Log.e(LOG_TAG, "Failed to talk to package manager", e);
+                            DevicePolicyManager dpm =
+                                    context.createContextAsUser(otherUserHandle, 0)
+                                    .getSystemService(DevicePolicyManager.class);
+                            if (dpm.packageHasActiveAdmins(appInfo.packageName)) {
+                                otherBlockingUserHandle = otherUserHandle;
+                                break;
                             }
                         }
-                        if (otherBlockingUser == null) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
                                     + " is a device admin");
 
@@ -124,46 +118,41 @@
                                     R.string.uninstall_failed_device_policy_manager));
                         } else {
                             Log.d(LOG_TAG, "Uninstall failed because " + appInfo.packageName
-                                    + " is a device admin of user " + otherBlockingUser);
+                                    + " is a device admin of user " + otherBlockingUserHandle);
 
+                            String userName =
+                                    context.createContextAsUser(otherBlockingUserHandle, 0)
+                                            .getSystemService(UserManager.class).getUserName();
                             setBigText(uninstallFailedNotification, String.format(context.getString(
                                     R.string.uninstall_failed_device_policy_manager_of_user),
-                                    otherBlockingUser.name));
+                                    userName));
                         }
                         break;
                     }
                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
-                        IPackageManager packageManager = IPackageManager.Stub.asInterface(
-                                ServiceManager.getService("package"));
-
-                        List<UserInfo> users = userManager.getUsers();
-                        int blockingUserId = UserHandle.USER_NULL;
-                        for (int i = 0; i < users.size(); ++i) {
-                            final UserInfo user = users.get(i);
-                            try {
-                                if (packageManager.getBlockUninstallForUser(appInfo.packageName,
-                                        user.id)) {
-                                    blockingUserId = user.id;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                // Shouldn't happen.
-                                Log.e(LOG_TAG, "Failed to talk to package manager", e);
+                        PackageManager packageManager = context.getPackageManager();
+                        List<UserHandle> userHandles = userManager.getUserHandles(true);
+                        UserHandle otherBlockingUserHandle = null;
+                        for (int i = 0; i < userHandles.size(); ++i) {
+                            final UserHandle handle = userHandles.get(i);
+                            if (packageManager.canUserUninstall(appInfo.packageName, handle)) {
+                                otherBlockingUserHandle = handle;
+                                break;
                             }
                         }
 
-                        int myUserId = UserHandle.myUserId();
-                        if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
                             addDeviceManagerButton(context, uninstallFailedNotification);
                         } else {
                             addManageUsersButton(context, uninstallFailedNotification);
                         }
 
-                        if (blockingUserId == UserHandle.USER_NULL) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(LOG_TAG,
                                     "Uninstall failed for " + appInfo.packageName + " with code "
                                             + returnCode + " no blocking user");
-                        } else if (blockingUserId == UserHandle.USER_SYSTEM) {
+                        } else if (otherBlockingUserHandle == UserHandle.SYSTEM) {
                             setBigText(uninstallFailedNotification,
                                     context.getString(R.string.uninstall_blocked_device_owner));
                         } else {
@@ -200,18 +189,18 @@
      * Is a profile part of a user?
      *
      * @param userManager The user manager
-     * @param userId The id of the user
-     * @param profileId The id of the profile
+     * @param userHandle The handle of the user
+     * @param profileHandle The handle of the profile
      *
      * @return If the profile is part of the user or the profile parent of the user
      */
-    private boolean isProfileOfOrSame(@NonNull UserManager userManager, int userId, int profileId) {
-        if (userId == profileId) {
+    private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+            UserHandle profileHandle) {
+        if (userHandle.equals(profileHandle)) {
             return true;
         }
-
-        UserInfo parentUser = userManager.getProfileParent(profileId);
-        return parentUser != null && parentUser.id == userId;
+        return userManager.getProfileParent(profileHandle) != null
+                && userManager.getProfileParent(profileHandle).equals(userHandle);
     }
 
     /**
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
index 1485352..b60aba8 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallUninstalling.java
@@ -16,9 +16,7 @@
 
 package com.android.packageinstaller;
 
-import android.annotation.Nullable;
 import android.app.Activity;
-import android.app.ActivityThread;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -27,19 +25,18 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
 /**
  * Start an uninstallation, show a dialog while uninstalling and return result to the caller.
  */
@@ -57,7 +54,7 @@
 
     private int mUninstallId;
     private ApplicationInfo mAppInfo;
-    private IBinder mCallback;
+    private PackageManager.UninstallCompleteCallback mCallback;
     private boolean mReturnResult;
     private String mLabel;
 
@@ -68,7 +65,8 @@
         setFinishOnTouchOutside(false);
 
         mAppInfo = getIntent().getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
-        mCallback = getIntent().getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mCallback = getIntent().getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                PackageManager.UninstallCompleteCallback.class);
         mReturnResult = getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false);
         mLabel = getIntent().getStringExtra(EXTRA_APP_LABEL);
 
@@ -119,15 +117,10 @@
                 int flags = allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
-                try {
-                    ActivityThread.getPackageManager().getPackageInstaller().uninstall(
-                            new VersionedPackage(mAppInfo.packageName,
-                                    PackageManager.VERSION_CODE_HIGHEST),
-                            getPackageName(), flags, pendingIntent.getIntentSender(),
-                            user.getIdentifier());
-                } catch (RemoteException e) {
-                    e.rethrowFromSystemServer();
-                }
+                getPackageManager().getPackageInstaller().uninstall(
+                        new VersionedPackage(mAppInfo.packageName,
+                                PackageManager.VERSION_CODE_HIGHEST),
+                        flags, pendingIntent.getIntentSender());
             } else {
                 mUninstallId = savedInstanceState.getInt(UNINSTALL_ID);
                 UninstallEventReceiver.addObserver(this, mUninstallId, this);
@@ -135,7 +128,7 @@
         } catch (EventResultPersister.OutOfIdsException | IllegalArgumentException e) {
             Log.e(LOG_TAG, "Fails to start uninstall", e);
             onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
-                    null);
+                    null, 0);
         }
     }
 
@@ -152,15 +145,10 @@
     }
 
     @Override
-    public void onResult(int status, int legacyStatus, @Nullable String message) {
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
         if (mCallback != null) {
             // The caller will be informed about the result via a callback
-            final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                    .asInterface(mCallback);
-            try {
-                observer.onPackageDeleted(mAppInfo.packageName, legacyStatus, message);
-            } catch (RemoteException ignored) {
-            }
+            mCallback.onUninstallComplete(mAppInfo.packageName, legacyStatus, message);
         } else if (mReturnResult) {
             // The caller will be informed about the result and might decide to display it
             Intent result = new Intent();
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
index 0198168..04496b9 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UninstallerActivity.java
@@ -22,12 +22,7 @@
 import static com.android.packageinstaller.PackageUtil.getMaxTargetSdkVersionForUid;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.StringRes;
 import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.DialogFragment;
 import android.app.Fragment;
@@ -37,12 +32,9 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -50,13 +42,14 @@
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.StringRes;
+
 import com.android.packageinstaller.handheld.ErrorDialogFragment;
 import com.android.packageinstaller.handheld.UninstallAlertDialogFragment;
 import com.android.packageinstaller.television.ErrorFragment;
@@ -80,7 +73,7 @@
         public ActivityInfo activityInfo;
         public boolean allUsers;
         public UserHandle user;
-        public IBinder callback;
+        public PackageManager.UninstallCompleteCallback callback;
     }
 
     private String mPackageName;
@@ -94,44 +87,8 @@
         // be stale, if e.g. the app was uninstalled while the activity was destroyed.
         super.onCreate(null);
 
-        try {
-            int callingUid = ActivityManager.getService().getLaunchedFromUid(getActivityToken());
-
-            String callingPackage = getPackageNameForUid(callingUid);
-            if (callingPackage == null) {
-                Log.e(TAG, "Package not found for originating uid " + callingUid);
-                setResult(Activity.RESULT_FIRST_USER);
-                finish();
-                return;
-            } else {
-                AppOpsManager appOpsManager = (AppOpsManager) getSystemService(
-                        Context.APP_OPS_SERVICE);
-                if (appOpsManager.noteOpNoThrow(
-                        AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
-                        != MODE_ALLOWED) {
-                    Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
-                    setResult(Activity.RESULT_FIRST_USER);
-                    finish();
-                    return;
-                }
-            }
-
-            if (getMaxTargetSdkVersionForUid(this, callingUid)
-                    >= Build.VERSION_CODES.P && AppGlobals.getPackageManager().checkUidPermission(
-                    Manifest.permission.REQUEST_DELETE_PACKAGES, callingUid)
-                    != PackageManager.PERMISSION_GRANTED
-                    && AppGlobals.getPackageManager().checkUidPermission(
-                            Manifest.permission.DELETE_PACKAGES, callingUid)
-                            != PackageManager.PERMISSION_GRANTED) {
-                Log.e(TAG, "Uid " + callingUid + " does not have "
-                        + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
-                        + Manifest.permission.DELETE_PACKAGES);
-
-                setResult(Activity.RESULT_FIRST_USER);
-                finish();
-                return;
-            }
-        } catch (RemoteException ex) {
+        int callingUid = getLaunchedFromUid();
+        if (callingUid == Process.INVALID_UID) {
             // Cannot reach Package/ActivityManager. Aborting uninstall.
             Log.e(TAG, "Could not determine the launching uid.");
 
@@ -140,6 +97,38 @@
             return;
         }
 
+        String callingPackage = getPackageNameForUid(callingUid);
+        if (callingPackage == null) {
+            Log.e(TAG, "Package not found for originating uid " + callingUid);
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        } else {
+            AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
+            if (appOpsManager.noteOpNoThrow(
+                    AppOpsManager.OPSTR_REQUEST_DELETE_PACKAGES, callingUid, callingPackage)
+                    != MODE_ALLOWED) {
+                Log.e(TAG, "Install from uid " + callingUid + " disallowed by AppOps");
+                setResult(Activity.RESULT_FIRST_USER);
+                finish();
+                return;
+            }
+        }
+
+        if (getMaxTargetSdkVersionForUid(this, callingUid) >= Build.VERSION_CODES.P
+                && getBaseContext().checkPermission(Manifest.permission.REQUEST_DELETE_PACKAGES,
+                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED
+                && getBaseContext().checkPermission(Manifest.permission.DELETE_PACKAGES,
+                0 /* random value for pid */, callingUid) != PackageManager.PERMISSION_GRANTED) {
+            Log.e(TAG, "Uid " + callingUid + " does not have "
+                    + Manifest.permission.REQUEST_DELETE_PACKAGES + " or "
+                    + Manifest.permission.DELETE_PACKAGES);
+
+            setResult(Activity.RESULT_FIRST_USER);
+            finish();
+            return;
+        }
+
         // Get intent information.
         // We expect an intent with URI of the form package://<packageName>#<className>
         // className is optional; if specified, it is the activity the user chose to uninstall
@@ -157,37 +146,37 @@
             return;
         }
 
-        final IPackageManager pm = IPackageManager.Stub.asInterface(
-                ServiceManager.getService("package"));
+        PackageManager pm = getPackageManager();
+        UserManager userManager = getBaseContext().getSystemService(UserManager.class);
 
         mDialogInfo = new DialogInfo();
 
         mDialogInfo.allUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
-        if (mDialogInfo.allUsers && !UserManager.get(this).isAdminUser()) {
+        if (mDialogInfo.allUsers && !userManager.isAdminUser()) {
             Log.e(TAG, "Only admin user can request uninstall for all users");
             showUserIsNotAllowed();
             return;
         }
         mDialogInfo.user = intent.getParcelableExtra(Intent.EXTRA_USER);
         if (mDialogInfo.user == null) {
-            mDialogInfo.user = android.os.Process.myUserHandle();
+            mDialogInfo.user = Process.myUserHandle();
         } else {
-            UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
             List<UserHandle> profiles = userManager.getUserProfiles();
             if (!profiles.contains(mDialogInfo.user)) {
-                Log.e(TAG, "User " + android.os.Process.myUserHandle() + " can't request uninstall "
+                Log.e(TAG, "User " + Process.myUserHandle() + " can't request uninstall "
                         + "for user " + mDialogInfo.user);
                 showUserIsNotAllowed();
                 return;
             }
         }
 
-        mDialogInfo.callback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mDialogInfo.callback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                                            PackageManager.UninstallCompleteCallback.class);
 
         try {
             mDialogInfo.appInfo = pm.getApplicationInfo(mPackageName,
-                    PackageManager.MATCH_ANY_USER, mDialogInfo.user.getIdentifier());
-        } catch (RemoteException e) {
+                    PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_ANY_USER));
+        } catch (PackageManager.NameNotFoundException e) {
             Log.e(TAG, "Unable to get packageName. Package manager is dead?");
         }
 
@@ -202,9 +191,9 @@
         if (className != null) {
             try {
                 mDialogInfo.activityInfo = pm.getActivityInfo(
-                        new ComponentName(mPackageName, className), 0,
-                        mDialogInfo.user.getIdentifier());
-            } catch (RemoteException e) {
+                        new ComponentName(mPackageName, className),
+                        PackageManager.ComponentInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
                 Log.e(TAG, "Unable to get className. Package manager is dead?");
                 // Continue as the ActivityInfo isn't critical.
             }
@@ -315,7 +304,6 @@
             newIntent.putExtra(UninstallUninstalling.EXTRA_APP_LABEL, label);
             newIntent.putExtra(UninstallUninstalling.EXTRA_KEEP_DATA, keepData);
             newIntent.putExtra(PackageInstaller.EXTRA_CALLBACK, mDialogInfo.callback);
-
             if (returnResult) {
                 newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
             }
@@ -366,11 +354,10 @@
                 int flags = mDialogInfo.allUsers ? PackageManager.DELETE_ALL_USERS : 0;
                 flags |= keepData ? PackageManager.DELETE_KEEP_DATA : 0;
 
-                ActivityThread.getPackageManager().getPackageInstaller().uninstall(
+                getPackageManager().getPackageInstaller().uninstall(
                         new VersionedPackage(mDialogInfo.appInfo.packageName,
                                 PackageManager.VERSION_CODE_HIGHEST),
-                        getPackageName(), flags, pendingIntent.getIntentSender(),
-                        mDialogInfo.user.getIdentifier());
+                        flags, pendingIntent.getIntentSender());
             } catch (Exception e) {
                 notificationManager.cancel(uninstallId);
 
@@ -382,13 +369,8 @@
 
     public void dispatchAborted() {
         if (mDialogInfo != null && mDialogInfo.callback != null) {
-            final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub.asInterface(
-                    mDialogInfo.callback);
-            try {
-                observer.onPackageDeleted(mPackageName,
-                        PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
-            } catch (RemoteException ignored) {
-            }
+            mDialogInfo.callback.onUninstallComplete(mPackageName,
+                    PackageManager.DELETE_FAILED_ABORTED, "Cancelled by user");
         }
     }
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index a1bc992..21f4be0 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -16,10 +16,10 @@
 
 package com.android.packageinstaller.handheld;
 
+import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
 import static android.text.format.Formatter.formatFileSize;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
@@ -29,7 +29,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -41,6 +40,9 @@
 import android.widget.CheckBox;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.R;
 import com.android.packageinstaller.UninstallerActivity;
 
@@ -91,11 +93,11 @@
         long appDataSize = 0;
 
         if (user == null) {
-            List<UserInfo> users = userManager.getUsers();
+            List<UserHandle> userHandles = userManager.getUserHandles(true);
 
-            int numUsers = users.size();
+            int numUsers = userHandles.size();
             for (int i = 0; i < numUsers; i++) {
-                appDataSize += getAppDataSizeForUser(pkg, UserHandle.of(users.get(i).id));
+                appDataSize += getAppDataSizeForUser(pkg, userHandles.get(i));
             }
         } else {
             appDataSize = getAppDataSizeForUser(pkg, user);
@@ -128,7 +130,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
-        UserManager userManager = UserManager.get(getActivity());
+        UserManager userManager = getContext().getSystemService(UserManager.class);
         if (isUpdate) {
             if (isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -139,20 +141,25 @@
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
             } else if (!dialogInfo.user.equals(myUserHandle)) {
-                UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                if (userInfo.isManagedProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                int userId = dialogInfo.user.getIdentifier();
+                UserManager customUserManager = getContext()
+                        .createContextAsUser(UserHandle.of(userId), 0)
+                        .getSystemService(UserManager.class);
+                String userName = customUserManager.getUserName();
+
+                if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
-                                    userInfo.name));
-                } else if (userInfo.isCloneProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                                    userName));
+                } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     isClonedApp = true;
                     messageBuilder.append(getString(
                             R.string.uninstall_application_text_current_user_clone_profile));
                 } else {
                     messageBuilder.append(
-                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                            getString(R.string.uninstall_application_text_user, userName));
                 }
             } else if (isCloneProfile(myUserHandle)) {
                 isClonedApp = true;
@@ -238,8 +245,8 @@
         // Check if another instance of given package exists in clone user profile.
         if (cloneUser != null) {
             try {
-                if (getContext().getPackageManager()
-                        .getPackageUidAsUser(packageName, cloneUser.getIdentifier()) > 0) {
+                if (getContext().getPackageManager().getPackageUidAsUser(packageName,
+                        PackageManager.PackageInfoFlags.of(0), cloneUser.getIdentifier()) > 0) {
                     return true;
                 }
             } catch (PackageManager.NameNotFoundException e) {
@@ -273,7 +280,6 @@
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
-        return userCount == 1
-                || (UserManager.isSplitSystemUser() && userCount == 2);
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
index 2d241ca..5c5720a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAlertFragment.java
@@ -16,10 +16,11 @@
 
 package com.android.packageinstaller.television;
 
+import static android.os.UserManager.USER_TYPE_PROFILE_MANAGED;
+
 import android.app.Activity;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -63,7 +64,7 @@
         final boolean isUpdate =
                 ((dialogInfo.appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
         final UserHandle myUserHandle = Process.myUserHandle();
-        UserManager userManager = UserManager.get(getActivity());
+        UserManager userManager = getContext().getSystemService(UserManager.class);
         if (isUpdate) {
             if (isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_update_text));
@@ -74,15 +75,21 @@
             if (dialogInfo.allUsers && !isSingleUser(userManager)) {
                 messageBuilder.append(getString(R.string.uninstall_application_text_all_users));
             } else if (!dialogInfo.user.equals(myUserHandle)) {
-                UserInfo userInfo = userManager.getUserInfo(dialogInfo.user.getIdentifier());
-                if (userInfo.isManagedProfile()
-                        && userInfo.profileGroupId == myUserHandle.getIdentifier()) {
+                int userId = dialogInfo.user.getIdentifier();
+                UserManager customUserManager = getContext()
+                        .createContextAsUser(UserHandle.of(userId), 0)
+                        .getSystemService(UserManager.class);
+                String userName = customUserManager.getUserName();
+
+                if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
+                        && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
+
                     messageBuilder.append(
                             getString(R.string.uninstall_application_text_current_user_work_profile,
-                                    userInfo.name));
+                                    userName));
                 } else {
                     messageBuilder.append(
-                            getString(R.string.uninstall_application_text_user, userInfo.name));
+                            getString(R.string.uninstall_application_text_user, userName));
                 }
             } else {
                 messageBuilder.append(getString(R.string.uninstall_application_text));
@@ -126,7 +133,6 @@
      */
     private boolean isSingleUser(UserManager userManager) {
         final int userCount = userManager.getUserCount();
-        return userCount == 1
-                || (UserManager.isSplitSystemUser() && userCount == 2);
+        return userCount == 1 || (UserManager.isHeadlessSystemUserMode() && userCount == 2);
     }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
index a4f217c..0c59d44 100755
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgress.java
@@ -17,24 +17,20 @@
 package com.android.packageinstaller.television;
 
 import android.app.Activity;
-import android.app.admin.IDevicePolicyManager;
+import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageDeleteObserver2;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
+import android.content.pm.VersionedPackage;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
-import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.Log;
@@ -42,8 +38,12 @@
 import android.view.KeyEvent;
 import android.widget.Toast;
 
+import androidx.annotation.Nullable;
+
+import com.android.packageinstaller.EventResultPersister;
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
@@ -55,14 +55,17 @@
  * by an intent with the intent's class name explicitly set to UninstallAppProgress and expects
  * the application object of the application to uninstall.
  */
-public class UninstallAppProgress extends Activity {
+public class UninstallAppProgress extends Activity implements
+        EventResultPersister.EventResultObserver {
     private static final String TAG = "UninstallAppProgress";
 
     private static final String FRAGMENT_TAG = "progress_fragment";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
 
     private ApplicationInfo mAppInfo;
     private boolean mAllUsers;
-    private IBinder mCallback;
+    private PackageManager.UninstallCompleteCallback mCallback;
 
     private volatile int mResultCode = -1;
 
@@ -116,13 +119,7 @@
                 final String packageName = (String) msg.obj;
 
                 if (mCallback != null) {
-                    final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                            .asInterface(mCallback);
-                    try {
-                        observer.onPackageDeleted(mAppInfo.packageName, mResultCode,
-                                packageName);
-                    } catch (RemoteException ignored) {
-                    }
+                    mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, packageName);
                     finish();
                     return;
                 }
@@ -139,37 +136,34 @@
 
                 // Update the status text
                 final String statusText;
+                Context ctx = getBaseContext();
                 switch (msg.arg1) {
                     case PackageManager.DELETE_SUCCEEDED:
                         statusText = getString(R.string.uninstall_done);
                         // Show a Toast and finish the activity
-                        Context ctx = getBaseContext();
                         Toast.makeText(ctx, statusText, Toast.LENGTH_LONG).show();
                         setResultAndFinish();
                         return;
                     case PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER: {
                         UserManager userManager =
                                 (UserManager) getSystemService(Context.USER_SERVICE);
-                        IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface(
-                                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
                         // Find out if the package is an active admin for some non-current user.
-                        int myUserId = UserHandle.myUserId();
-                        UserInfo otherBlockingUser = null;
-                        for (UserInfo user : userManager.getUsers()) {
+                        UserHandle myUserHandle =  Process.myUserHandle();
+                        UserHandle otherBlockingUserHandle = null;
+                        for (UserHandle otherUserHandle : userManager.getUserHandles(true)) {
                             // We only catch the case when the user in question is neither the
                             // current user nor its profile.
-                            if (isProfileOfOrSame(userManager, myUserId, user.id)) continue;
-
-                            try {
-                                if (dpm.packageHasActiveAdmins(packageName, user.id)) {
-                                    otherBlockingUser = user;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                Log.e(TAG, "Failed to talk to package manager", e);
+                            if (isProfileOfOrSame(userManager, myUserHandle, otherUserHandle)) {
+                                continue;
+                            }
+                            DevicePolicyManager dpm = ctx.createContextAsUser(otherUserHandle, 0)
+                                    .getSystemService(DevicePolicyManager.class);
+                            if (dpm.packageHasActiveAdmins(packageName)) {
+                                otherBlockingUserHandle = otherUserHandle;
+                                break;
                             }
                         }
-                        if (otherBlockingUser == null) {
+                        if (otherBlockingUserHandle == null) {
                             Log.d(TAG, "Uninstall failed because " + packageName
                                     + " is a device admin");
                             getProgressFragment().setDeviceManagerButtonVisible(true);
@@ -177,45 +171,40 @@
                                     R.string.uninstall_failed_device_policy_manager);
                         } else {
                             Log.d(TAG, "Uninstall failed because " + packageName
-                                    + " is a device admin of user " + otherBlockingUser);
+                                    + " is a device admin of user " + otherBlockingUserHandle);
                             getProgressFragment().setDeviceManagerButtonVisible(false);
+                            String userName = ctx.createContextAsUser(otherBlockingUserHandle, 0)
+                                    .getSystemService(UserManager.class).getUserName();
                             statusText = String.format(
                                     getString(R.string.uninstall_failed_device_policy_manager_of_user),
-                                    otherBlockingUser.name);
+                                    userName);
                         }
                         break;
                     }
                     case PackageManager.DELETE_FAILED_OWNER_BLOCKED: {
                         UserManager userManager =
                                 (UserManager) getSystemService(Context.USER_SERVICE);
-                        IPackageManager packageManager = IPackageManager.Stub.asInterface(
-                                ServiceManager.getService("package"));
-                        List<UserInfo> users = userManager.getUsers();
-                        int blockingUserId = UserHandle.USER_NULL;
-                        for (int i = 0; i < users.size(); ++i) {
-                            final UserInfo user = users.get(i);
-                            try {
-                                if (packageManager.getBlockUninstallForUser(packageName,
-                                        user.id)) {
-                                    blockingUserId = user.id;
-                                    break;
-                                }
-                            } catch (RemoteException e) {
-                                // Shouldn't happen.
-                                Log.e(TAG, "Failed to talk to package manager", e);
+                        PackageManager packageManager = ctx.getPackageManager();
+                        List<UserHandle> userHandles = userManager.getUserHandles(true);
+                        UserHandle otherBlockingUserHandle = null;
+                        for (int i = 0; i < userHandles.size(); ++i) {
+                            final UserHandle handle = userHandles.get(i);
+                            if (packageManager.canUserUninstall(packageName, handle)) {
+                                otherBlockingUserHandle = handle;
+                                break;
                             }
                         }
-                        int myUserId = UserHandle.myUserId();
-                        if (isProfileOfOrSame(userManager, myUserId, blockingUserId)) {
+                        UserHandle myUserHandle = Process.myUserHandle();
+                        if (isProfileOfOrSame(userManager, myUserHandle, otherBlockingUserHandle)) {
                             getProgressFragment().setDeviceManagerButtonVisible(true);
                         } else {
                             getProgressFragment().setDeviceManagerButtonVisible(false);
                             getProgressFragment().setUsersButtonVisible(true);
                         }
                         // TODO: b/25442806
-                        if (blockingUserId == UserHandle.USER_SYSTEM) {
+                        if (otherBlockingUserHandle == UserHandle.SYSTEM) {
                             statusText = getString(R.string.uninstall_blocked_device_owner);
-                        } else if (blockingUserId == UserHandle.USER_NULL) {
+                        } else if (otherBlockingUserHandle == null) {
                             Log.d(TAG, "Uninstall failed for " + packageName + " with code "
                                     + msg.arg1 + " no blocking user");
                             statusText = getString(R.string.uninstall_failed);
@@ -239,12 +228,13 @@
         }
     }
 
-    private boolean isProfileOfOrSame(UserManager userManager, int userId, int profileId) {
-        if (userId == profileId) {
+    private boolean isProfileOfOrSame(UserManager userManager, UserHandle userHandle,
+            UserHandle profileHandle) {
+        if (userHandle.equals(profileHandle)) {
             return true;
         }
-        UserInfo parentUser = userManager.getProfileParent(profileId);
-        return parentUser != null && parentUser.id == userId;
+        return userManager.getProfileParent(profileHandle) != null
+                && userManager.getProfileParent(profileHandle).equals(userHandle);
     }
 
     @Override
@@ -253,7 +243,8 @@
 
         Intent intent = getIntent();
         mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
-        mCallback = intent.getIBinderExtra(PackageInstaller.EXTRA_CALLBACK);
+        mCallback = intent.getParcelableExtra(PackageInstaller.EXTRA_CALLBACK,
+                PackageManager.UninstallCompleteCallback.class);
 
         // This currently does not support going through a onDestroy->onCreate cycle. Hence if that
         // happened, just fail the operation for mysterious reasons.
@@ -261,12 +252,7 @@
             mResultCode = PackageManager.DELETE_FAILED_INTERNAL_ERROR;
 
             if (mCallback != null) {
-                final IPackageDeleteObserver2 observer = IPackageDeleteObserver2.Stub
-                        .asInterface(mCallback);
-                try {
-                    observer.onPackageDeleted(mAppInfo.packageName, mResultCode, null);
-                } catch (RemoteException ignored) {
-                }
+                mCallback.onUninstallComplete(mAppInfo.packageName, mResultCode, null);
                 finish();
             } else {
                 setResultAndFinish();
@@ -278,10 +264,9 @@
         mAllUsers = intent.getBooleanExtra(Intent.EXTRA_UNINSTALL_ALL_USERS, false);
         UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
         if (user == null) {
-            user = android.os.Process.myUserHandle();
+            user = Process.myUserHandle();
         }
 
-        PackageDeleteObserver observer = new PackageDeleteObserver();
 
         // Make window transparent until initView is called. In many cases we can avoid showing the
         // UI at all as the app is uninstalled very quickly. If we show the UI and instantly remove
@@ -291,11 +276,29 @@
         getWindow().setNavigationBarColor(Color.TRANSPARENT);
 
         try {
-            getPackageManager().deletePackageAsUser(mAppInfo.packageName, observer,
-                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0, user.getIdentifier());
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
+            createContextAsUser(user, 0).getPackageManager().getPackageInstaller().uninstall(
+                    new VersionedPackage(mAppInfo.packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    mAllUsers ? PackageManager.DELETE_ALL_USERS : 0,
+                    pendingIntent.getIntentSender());
         } catch (IllegalArgumentException e) {
             // Couldn't find the package, no need to call uninstall.
             Log.w(TAG, "Could not find package, not deleting " + mAppInfo.packageName, e);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            onResult(PackageInstaller.STATUS_FAILURE, PackageManager.DELETE_FAILED_INTERNAL_ERROR,
+                    null, 0);
         }
 
         mHandler.sendMessageDelayed(mHandler.obtainMessage(UNINSTALL_IS_SLOW),
@@ -306,13 +309,12 @@
         return mAppInfo;
     }
 
-    private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
-        public void packageDeleted(String packageName, int returnCode) {
-            Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
-            msg.arg1 = returnCode;
-            msg.obj = packageName;
-            mHandler.sendMessage(msg);
-        }
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        Message msg = mHandler.obtainMessage(UNINSTALL_COMPLETE);
+        msg.arg1 = legacyStatus;
+        msg.obj = mAppInfo.packageName;
+        mHandler.sendMessage(msg);
     }
 
     public void setResultAndFinish() {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
index af6d9c5..c2d95b2 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/television/UninstallAppProgressFragment.java
@@ -16,7 +16,6 @@
 
 package com.android.packageinstaller.television;
 
-import android.annotation.Nullable;
 import android.app.Fragment;
 import android.content.Intent;
 import android.os.Bundle;
@@ -28,6 +27,8 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
index 063d789..8dd691d 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/PackageInstallerImpl.java
@@ -265,7 +265,7 @@
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(action);
         mContext.registerReceiver(broadcastReceiver, intentFilter,
-                Context.RECEIVER_EXPORTED_UNAUDITED);
+                Context.RECEIVER_EXPORTED);
 
         // Create a matching PendingIntent and use it to generate the IntentSender
         Intent broadcastIntent = new Intent(action);
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
index 06b1c16..959257f 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/wear/WearPackageInstallerService.java
@@ -19,14 +19,16 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
@@ -43,13 +45,18 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.annotation.Nullable;
+
 import com.android.packageinstaller.DeviceUtils;
+import com.android.packageinstaller.EventResultPersister;
 import com.android.packageinstaller.PackageUtil;
 import com.android.packageinstaller.R;
+import com.android.packageinstaller.UninstallEventReceiver;
 
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -80,16 +87,30 @@
  *  adb shell am startservice -a com.android.packageinstaller.wear.RETRY_GMS \
  *     com.android.packageinstaller/com.android.packageinstaller.wear.WearPackageInstallerService
  */
-public class WearPackageInstallerService extends Service {
+public class WearPackageInstallerService extends Service
+        implements EventResultPersister.EventResultObserver {
     private static final String TAG = "WearPkgInstallerService";
 
     private static final String WEAR_APPS_CHANNEL = "wear_app_install_uninstall";
+    private static final String BROADCAST_ACTION =
+            "com.android.packageinstaller.ACTION_UNINSTALL_COMMIT";
 
     private final int START_INSTALL = 1;
     private final int START_UNINSTALL = 2;
 
     private int mInstallNotificationId = 1;
     private final Map<String, Integer> mNotifIdMap = new ArrayMap<>();
+    private final Map<Integer, UninstallParams> mServiceIdToParams = new HashMap<>();
+
+    private class UninstallParams {
+        public String mPackageName;
+        public PowerManager.WakeLock mLock;
+
+        UninstallParams(String packageName, PowerManager.WakeLock lock) {
+            mPackageName = packageName;
+            mLock = lock;
+        }
+    }
 
     private final class ServiceHandler extends Handler {
         public ServiceHandler(Looper looper) {
@@ -211,7 +232,6 @@
         }
         final PackageManager pm = getPackageManager();
         File tempFile = null;
-        int installFlags = 0;
         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
         boolean messageSent = false;
         try {
@@ -220,17 +240,14 @@
                 existingPkgInfo = pm.getPackageInfo(packageName,
                         PackageManager.MATCH_ANY_USER | PackageManager.GET_PERMISSIONS);
                 if (existingPkgInfo != null) {
-                    installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
+                    if (Log.isLoggable(TAG, Log.DEBUG)) {
+                        Log.d(TAG, "Replacing package:" + packageName);
+                    }
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Ignore this exception. We could not find the package, will treat as a new
                 // installation.
             }
-            if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
-                if (Log.isLoggable(TAG, Log.DEBUG)) {
-                    Log.d(TAG, "Replacing package:" + packageName);
-                }
-            }
             // TODO(28021618): This was left as a temp file due to the fact that this code is being
             //       deprecated and that we need the bare minimum to continue working moving forward
             //       If this code is used as reference, this permission logic might want to be
@@ -366,21 +383,60 @@
         final String packageName = WearPackageArgs.getPackageName(argsBundle);
 
         PowerManager.WakeLock lock = getLock(this.getApplicationContext());
+
+        UninstallParams params = new UninstallParams(packageName, lock);
+        mServiceIdToParams.put(startId, params);
+
         final PackageManager pm = getPackageManager();
         try {
             PackageInfo pkgInfo = pm.getPackageInfo(packageName, 0);
             getLabelAndUpdateNotification(packageName,
                     getString(R.string.uninstalling_app, pkgInfo.applicationInfo.loadLabel(pm)));
 
+            int uninstallId = UninstallEventReceiver.addObserver(this,
+                    EventResultPersister.GENERATE_NEW_ID, this);
+
+            Intent broadcastIntent = new Intent(BROADCAST_ACTION);
+            broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, uninstallId);
+            broadcastIntent.putExtra(EventResultPersister.EXTRA_SERVICE_ID, startId);
+            broadcastIntent.setPackage(getPackageName());
+
+            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, uninstallId,
+                    broadcastIntent, PendingIntent.FLAG_UPDATE_CURRENT
+                            | PendingIntent.FLAG_MUTABLE);
+
             // Found package, send uninstall request.
-            pm.deletePackage(packageName, new PackageDeleteObserver(lock, startId),
-                    PackageManager.DELETE_ALL_USERS);
+            pm.getPackageInstaller().uninstall(
+                    new VersionedPackage(packageName, PackageManager.VERSION_CODE_HIGHEST),
+                    PackageManager.DELETE_ALL_USERS,
+                    pendingIntent.getIntentSender());
 
             Log.i(TAG, "Sent delete request for " + packageName);
         } catch (IllegalArgumentException | PackageManager.NameNotFoundException e) {
             // Couldn't find the package, no need to call uninstall.
             Log.w(TAG, "Could not find package, not deleting " + packageName, e);
             finishService(lock, startId);
+        } catch (EventResultPersister.OutOfIdsException e) {
+            Log.e(TAG, "Fails to start uninstall", e);
+            finishService(lock, startId);
+        }
+    }
+
+    @Override
+    public void onResult(int status, int legacyStatus, @Nullable String message, int serviceId) {
+        if (mServiceIdToParams.containsKey(serviceId)) {
+            UninstallParams params = mServiceIdToParams.get(serviceId);
+            try {
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Log.i(TAG, "Package " + params.mPackageName + " was uninstalled.");
+                } else {
+                    Log.e(TAG, "Package uninstall failed " + params.mPackageName
+                            + ", returnCode " + legacyStatus);
+                }
+            } finally {
+                finishService(params.mLock, serviceId);
+            }
         }
     }
 
@@ -537,29 +593,6 @@
         }
     }
 
-    private class PackageDeleteObserver extends IPackageDeleteObserver.Stub {
-        private PowerManager.WakeLock mWakeLock;
-        private int mStartId;
-
-        private PackageDeleteObserver(PowerManager.WakeLock wakeLock, int startId) {
-            mWakeLock = wakeLock;
-            mStartId = startId;
-        }
-
-        public void packageDeleted(String packageName, int returnCode) {
-            try {
-                if (returnCode >= 0) {
-                    Log.i(TAG, "Package " + packageName + " was uninstalled.");
-                } else {
-                    Log.e(TAG, "Package uninstall failed " + packageName + ", returnCode " +
-                            returnCode);
-                }
-            } finally {
-                finishService(mWakeLock, mStartId);
-            }
-        }
-    }
-
     private synchronized Pair<Integer, Notification> buildNotification(final String packageName,
             final String title) {
         int notifId;
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.kt
new file mode 100644
index 0000000..b8db63c
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreference.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.widget.preference
+
+import com.android.settingslib.spa.framework.util.EntryHighlight
+import androidx.compose.material3.IconButton
+import androidx.compose.runtime.State
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.material3.Icon
+
+@Composable
+fun TwoTargetButtonPreference(
+        title: String,
+        summary: State<String>,
+        icon: @Composable (() -> Unit)? = null,
+        onClick: () -> Unit,
+        buttonIcon: ImageVector,
+        buttonIconDescription: String,
+        onButtonClick: () -> Unit
+) {
+    EntryHighlight {
+        TwoTargetPreference(
+                title = title,
+                summary = summary,
+                onClick = onClick,
+                icon = icon) {
+            IconButton(onClick = onButtonClick) {
+                Icon(imageVector = buttonIcon, contentDescription = buttonIconDescription)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
new file mode 100644
index 0000000..3a2b445
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/preference/TwoTargetButtonPreferenceTest.kt
@@ -0,0 +1,73 @@
+package com.android.settingslib.spa.widget.preference
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.toState
+import com.google.common.truth.Truth
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val TEST_MODEL_TITLE = "TwoTargetButtonPreference"
+private const val TEST_MODEL_SUMMARY = "TestSummary"
+private const val TEST_BUTTON_ICON_DESCRIPTION = "TestButtonIconDescription"
+private val TEST_BUTTON_ICON = Icons.Outlined.Delete
+
+@RunWith(AndroidJUnit4::class)
+class TwoTargetButtonPreferenceTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun title_displayed() {
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference()
+        }
+
+        composeTestRule.onNodeWithText(TEST_MODEL_TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun clickable_label_canBeClicked() {
+        var clicked = false
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference(onClick = { clicked = true })
+        }
+
+        composeTestRule.onNodeWithText(TEST_MODEL_TITLE).performClick()
+        Truth.assertThat(clicked).isTrue()
+    }
+
+    @Test
+    fun clickable_button_label_canBeClicked() {
+        var clicked = false
+        composeTestRule.setContent {
+            testTwoTargetButtonPreference(onButtonClick = { clicked = true })
+        }
+
+        composeTestRule.onNodeWithContentDescription(TEST_BUTTON_ICON_DESCRIPTION).performClick()
+        Truth.assertThat(clicked).isTrue()
+    }
+}
+
+@Composable
+private fun testTwoTargetButtonPreference(
+    onClick: () -> Unit = {},
+    onButtonClick: () -> Unit = {},
+) {
+    TwoTargetButtonPreference(
+        title = TEST_MODEL_TITLE,
+        summary = TEST_MODEL_SUMMARY.toState(),
+        onClick = onClick,
+        buttonIcon = TEST_BUTTON_ICON,
+        buttonIconDescription = TEST_BUTTON_ICON_DESCRIPTION,
+        onButtonClick = onButtonClick
+    )
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
index c08169e..e878804 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppOpsController.kt
@@ -41,6 +41,7 @@
     context: Context,
     private val app: ApplicationInfo,
     private val op: Int,
+    private val setModeByUid: Boolean = false,
 ) : IAppOpsController {
     private val appOpsManager = context.appOpsManager
 
@@ -49,7 +50,11 @@
 
     override fun setAllowed(allowed: Boolean) {
         val mode = if (allowed) MODE_ALLOWED else MODE_ERRORED
-        appOpsManager.setMode(op, app.uid, app.packageName, mode)
+        if (setModeByUid) {
+            appOpsManager.setUidMode(op, app.uid, mode)
+        } else {
+            appOpsManager.setMode(op, app.uid, app.packageName, mode)
+        }
         _mode.postValue(mode)
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index deec267..3ff1d89 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -60,6 +60,7 @@
     val listModel: AppListModel<T>,
     val state: AppListState,
     val header: @Composable () -> Unit,
+    val noItemMessage: String? = null,
     val bottomPadding: Dp,
 )
 
@@ -79,7 +80,7 @@
 ) {
     LogCompositions(TAG, config.userId.toString())
     val appListData = appListDataSupplier()
-    listModel.AppListWidget(appListData, header, bottomPadding)
+    listModel.AppListWidget(appListData, header, bottomPadding, noItemMessage)
 }
 
 @Composable
@@ -87,12 +88,14 @@
     appListData: State<AppListData<T>?>,
     header: @Composable () -> Unit,
     bottomPadding: Dp,
+    noItemMessage: String?
 ) {
     val timeMeasurer = rememberTimeMeasurer(TAG)
     appListData.value?.let { (list, option) ->
         timeMeasurer.logFirst("app list first loaded")
         if (list.isEmpty()) {
-            PlaceholderTitle(stringResource(R.string.no_applications))
+            header()
+            PlaceholderTitle(noItemMessage ?: stringResource(R.string.no_applications))
             return
         }
         LazyColumn(
@@ -151,4 +154,4 @@
     ) { viewModel.reloadApps() }
 
     return viewModel.appListDataFlow.collectAsState(null, Dispatchers.IO)
-}
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt
new file mode 100644
index 0000000..919793a
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListButtonItem.kt
@@ -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 com.android.settingslib.spaprivileged.template.app
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.graphics.vector.ImageVector
+import com.android.settingslib.spa.framework.theme.SettingsDimension
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import com.android.settingslib.spa.widget.preference.TwoTargetButtonPreference
+
+@Composable
+fun <T : AppRecord> AppListItemModel<T>.AppListButtonItem (
+    onClick: () -> Unit,
+    onButtonClick: () -> Unit,
+    buttonIcon: ImageVector,
+    buttonIconDescription: String,
+) {
+        TwoTargetButtonPreference(
+                title = label,
+                summary = this@AppListButtonItem.summary,
+                icon = { AppIcon(record.app, SettingsDimension.appIconItemSize) },
+                onClick = onClick,
+                buttonIcon = buttonIcon,
+                buttonIconDescription = buttonIconDescription,
+                onButtonClick = onButtonClick
+        )
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 318bcd9..7d21d98 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -45,6 +45,7 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
+    noItemMessage: String? = null,
     moreOptions: @Composable MoreOptionsScope.() -> Unit = {},
     header: @Composable () -> Unit = {},
     appList: @Composable AppListInput<T>.() -> Unit = { AppList() },
@@ -77,6 +78,7 @@
                     ),
                     header = header,
                     bottomPadding = bottomPadding,
+                    noItemMessage = noItemMessage,
                 )
                 appList(appListInput)
             }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
index a357832..ee21b81 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt
@@ -49,6 +49,13 @@
     abstract val appOp: Int
     abstract val permission: String
 
+    /**
+     * Use AppOpsManager#setUidMode() instead of AppOpsManager#setMode() when set allowed.
+     *
+     * Security related app-ops should be set with setUidMode() instead of setMode().
+     */
+    open val setModeByUid = false
+
     /** These not changeable packages will also be hidden from app list. */
     private val notChangeablePackages =
         setOf("android", "com.android.systemui", context.packageName)
@@ -61,7 +68,7 @@
                 AppOpPermissionRecord(
                     app = app,
                     hasRequestPermission = app.packageName in packageNames,
-                    appOpsController = AppOpsController(context = context, app = app, op = appOp),
+                    appOpsController = createAppOpsController(app),
                 )
             }
         }
@@ -69,7 +76,14 @@
     override fun transformItem(app: ApplicationInfo) = AppOpPermissionRecord(
         app = app,
         hasRequestPermission = with(packageManagers) { app.hasRequestPermission(permission) },
-        appOpsController = AppOpsController(context = context, app = app, op = appOp),
+        appOpsController = createAppOpsController(app),
+    )
+
+    private fun createAppOpsController(app: ApplicationInfo) = AppOpsController(
+        context = context,
+        app = app,
+        op = appOp,
+        setModeByUid = setModeByUid,
     )
 
     override fun filter(userIdFlow: Flow<Int>, recordListFlow: Flow<List<AppOpPermissionRecord>>) =
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
new file mode 100644
index 0000000..668bfdf
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppOpsControllerTest.kt
@@ -0,0 +1,112 @@
+/*
+ * 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.model.app
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_ERRORED
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
+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.verify
+import org.mockito.Spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class AppOpsControllerTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Spy
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Mock
+    private lateinit var appOpsManager: AppOpsManager
+
+    @Before
+    fun setUp() {
+        whenever(context.appOpsManager).thenReturn(appOpsManager)
+    }
+
+    @Test
+    fun setAllowed_setToTrue() {
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ALLOWED)
+    }
+
+    @Test
+    fun setAllowed_setToFalse() {
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        controller.setAllowed(false)
+
+        verify(appOpsManager).setMode(OP, APP.uid, APP.packageName, MODE_ERRORED)
+    }
+
+    @Test
+    fun setAllowed_setToTrueByUid() {
+        val controller =
+            AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+        controller.setAllowed(true)
+
+        verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ALLOWED)
+    }
+
+    @Test
+    fun setAllowed_setToFalseByUid() {
+        val controller =
+            AppOpsController(context = context, app = APP, op = OP, setModeByUid = true)
+
+        controller.setAllowed(false)
+
+        verify(appOpsManager).setUidMode(OP, APP.uid, MODE_ERRORED)
+    }
+
+    @Test
+    fun getMode() {
+        whenever(
+            appOpsManager.checkOpNoThrow(OP, APP.uid, APP.packageName)
+        ).thenReturn(MODE_ALLOWED)
+        val controller = AppOpsController(context = context, app = APP, op = OP)
+
+        val mode = controller.getMode()
+
+        assertThat(mode).isEqualTo(MODE_ALLOWED)
+    }
+
+    private companion object {
+        const val OP = 1
+        val APP = ApplicationInfo().apply {
+            packageName = "package.name"
+            uid = 123
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index cd9c048..966b869 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
+import com.android.settingslib.spaprivileged.framework.common.appOpsManager
 import com.android.settingslib.spaprivileged.model.app.IAppOpsController
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
@@ -37,6 +38,8 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
@@ -50,17 +53,20 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    @Spy
     private val context: Context = ApplicationProvider.getApplicationContext()
 
     @Mock
     private lateinit var packageManagers: IPackageManagers
 
+    @Mock
+    private lateinit var appOpsManager: AppOpsManager
+
     private lateinit var listModel: TestAppOpPermissionAppListModel
 
     @Before
-    fun setUp() = runTest {
-        whenever(packageManagers.getAppOpPermissionPackages(USER_ID, PERMISSION))
-            .thenReturn(emptySet())
+    fun setUp() {
+        whenever(context.appOpsManager).thenReturn(appOpsManager)
         listModel = TestAppOpPermissionAppListModel()
     }
 
@@ -221,6 +227,16 @@
         assertThat(appOpsController.setAllowedCalledWith).isTrue()
     }
 
+    @Test
+    fun setAllowed_setModeByUid() {
+        listModel.setModeByUid = true
+        val record = listModel.transformItem(APP)
+
+        listModel.setAllowed(record = record, newAllowed = true)
+
+        verify(appOpsManager).setUidMode(listModel.appOp, APP.uid, AppOpsManager.MODE_ALLOWED)
+    }
+
     private fun getIsAllowed(record: AppOpPermissionRecord): Boolean? {
         lateinit var isAllowedState: State<Boolean?>
         composeTestRule.setContent {
@@ -236,6 +252,7 @@
         override val footerResId = R.string.test_app_op_permission_footer
         override val appOp = AppOpsManager.OP_MANAGE_MEDIA
         override val permission = PERMISSION
+        override var setModeByUid = false
     }
 
     private companion object {
diff --git a/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml
new file mode 100644
index 0000000..d6acac2
--- /dev/null
+++ b/packages/SettingsLib/res/layout/grant_admin_dialog_content.xml
@@ -0,0 +1,37 @@
+<!--
+  ~ 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"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:padding="@dimen/grant_admin_dialog_padding">
+    <RadioGroup
+        android:id="@+id/choose_admin"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+        <RadioButton
+            android:id="@+id/grant_admin_yes"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/grant_admin"/>
+        <RadioButton
+            android:id="@+id/grant_admin_no"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/not_grant_admin"/>
+    </RadioGroup>
+</LinearLayout>
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index 3ef3d36..dbfd1c2 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -109,4 +109,7 @@
     <dimen name="broadcast_dialog_btn_text_size">16sp</dimen>
     <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
     <dimen name="broadcast_dialog_margin">16dp</dimen>
+
+    <!-- Size of grant admin privileges dialog padding -->
+    <dimen name="grant_admin_dialog_padding">16dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index e675db4..2845916 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1368,6 +1368,10 @@
     <string name="user_add_user_message_long">You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi\u2011Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user.</string>
     <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
     <string name="user_add_user_message_short">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
+    <!-- Title for grant user admin privileges dialog [CHAR LIMIT=30] -->
+    <string name="user_grant_admin_title">Give this user admin privileges?</string>
+    <!-- Message for grant admin privileges dialog. [CHAR LIMIT=none] -->
+    <string name="user_grant_admin_message">As an admin, they will be able to manage other users, modify device settings and factory reset the device.</string>
     <!-- Title of dialog to setup a new user [CHAR LIMIT=30] -->
     <string name="user_setup_dialog_title">Set up user now?</string>
     <!-- Message in dialog to setup a new user after creation [CHAR LIMIT=none] -->
@@ -1434,6 +1438,10 @@
     <!-- Dialog message on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
     <string name="guest_exit_dialog_message">This will delete
         apps and data from the current guest session</string>
+    <!-- Dialog message on action grant admin privileges [CHAR LIMIT=60] -->
+    <string name="grant_admin">Give this user admin privileges</string>
+    <!-- Dialog message on action not grant admin privileges [CHAR LIMIT=60] -->
+    <string name="not_grant_admin">Do not give user admin privileges</string>
     <!-- Dialog button on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
     <string name="guest_exit_dialog_button">Exit</string>
     <!-- Dialog title on action exit guest (non-ephemeral guest) [CHAR LIMIT=32] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8a30a3b..888b09f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -653,6 +653,9 @@
         }
         for (UsbPort usbPort : usbPortList) {
             Log.d(tag, "usbPort: " + usbPort);
+            if (!usbPort.supportsComplianceWarnings()) {
+                continue;
+            }
             final UsbPortStatus usbStatus = usbPort.getStatus();
             if (usbStatus == null || !usbStatus.isConnected()) {
                 continue;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4da47fd..db224be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -66,6 +66,8 @@
             "com.android.settings.category.ia.battery_saver_settings";
     public static final String CATEGORY_SMART_BATTERY_SETTINGS =
             "com.android.settings.category.ia.smart_battery_settings";
+    public static final String CATEGORY_COMMUNAL_SETTINGS =
+            "com.android.settings.category.ia.communal";
 
     public static final Map<String, String> KEY_COMPAT_MAP;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 3e710e4..28353ab 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.util.KeyValueListParser;
@@ -55,6 +56,10 @@
     public static final String EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL =
             "extra_power_save_mode_trigger_level";
 
+    /** Battery saver schedule keys. */
+    public static final String KEY_NO_SCHEDULE = "key_battery_saver_no_schedule";
+    public static final String KEY_PERCENTAGE = "key_battery_saver_percentage";
+
     private BatterySaverUtils() {
     }
 
@@ -108,7 +113,6 @@
      * - If it's 4th time through 8th time, show the schedule suggestion notification.
      *
      * @param enable true to enable battery saver.
-     *
      * @return true if the request succeeded.
      */
     public static synchronized boolean setPowerSaveMode(Context context,
@@ -154,10 +158,10 @@
      * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in
      * the past before. Various extras can be provided that will change the behavior of this
      * notification as well as the ui for it.
-     * @param context A valid context
-     * @param extras Any extras to include in the intent to trigger this confirmation that will
-     * help the system disambiguate what to show/do
      *
+     * @param context A valid context
+     * @param extras  Any extras to include in the intent to trigger this confirmation that will
+     *                help the system disambiguate what to show/do
      * @return True if it showed the notification because it has not been previously acknowledged.
      * @see #EXTRA_CONFIRM_TEXT_ONLY
      * @see #EXTRA_POWER_SAVE_MODE_TRIGGER
@@ -221,6 +225,7 @@
 
     /**
      * Reverts battery saver schedule mode to none if routine mode is selected.
+     *
      * @param context a valid context
      */
     public static void revertScheduleToNoneIfNeeded(Context context) {
@@ -233,4 +238,50 @@
                     PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
         }
     }
+
+    /**
+     * Gets battery saver schedule mode.
+     *
+     * @param context a valid context
+     * @return battery saver schedule key
+     */
+    public static String getBatterySaverScheduleKey(Context context) {
+        final ContentResolver resolver = context.getContentResolver();
+        final int mode = Settings.Global.getInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        if (mode == PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE) {
+            final int threshold =
+                    Settings.Global.getInt(resolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+            return threshold <= 0 ? KEY_NO_SCHEDULE : KEY_PERCENTAGE;
+        }
+        revertScheduleToNoneIfNeeded(context);
+        return KEY_NO_SCHEDULE;
+    }
+
+    /**
+     * Sets battery saver schedule mode.
+     *
+     * @param context      a valid context
+     * @param scheduleKey  {@link #KEY_NO_SCHEDULE} and {@link #KEY_PERCENTAGE}
+     * @param triggerLevel for automatic battery saver trigger level
+     */
+    public static void setBatterySaverScheduleMode(Context context, String scheduleKey,
+            int triggerLevel) {
+        final ContentResolver resolver = context.getContentResolver();
+        switch (scheduleKey) {
+            case KEY_NO_SCHEDULE:
+                Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                        PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+                Settings.Global.putInt(resolver, Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+                break;
+            case KEY_PERCENTAGE:
+                Settings.Global.putInt(resolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                        PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+                Settings.Global.putInt(resolver,
+                        Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, triggerLevel);
+                break;
+            default:
+                throw new IllegalStateException("Not a valid schedule key");
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java b/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java
new file mode 100644
index 0000000..5cc8d5e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/users/GrantAdminDialogController.java
@@ -0,0 +1,71 @@
+/*
+ * 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.users;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import com.android.settingslib.R;
+
+import java.util.function.Consumer;
+
+/**
+ * This class encapsulates a Dialog for choosing whether to grant admin privileges.
+ */
+public class GrantAdminDialogController {
+
+    /**
+     * Creates a dialog with option to grant user admin privileges.
+     */
+    public Dialog createDialog(Activity activity,
+            Consumer<Boolean> successCallback, Runnable cancelCallback) {
+        LayoutInflater inflater = LayoutInflater.from(activity);
+        View content = inflater.inflate(R.layout.grant_admin_dialog_content, null);
+        RadioGroup radioGroup = content.findViewById(R.id.choose_admin);
+        RadioButton radioButton = radioGroup.findViewById(R.id.grant_admin_yes);
+        radioButton.setChecked(true);
+        Dialog dlg = new AlertDialog.Builder(activity)
+                .setView(content)
+                .setTitle(R.string.user_grant_admin_title)
+                .setMessage(R.string.user_grant_admin_message)
+                .setPositiveButton(android.R.string.ok,
+                        (dialog, which) ->  {
+                            if (successCallback != null) {
+                                successCallback.accept(radioButton.isChecked());
+                            }
+                        })
+                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+                    if (cancelCallback != null) {
+                        cancelCallback.run();
+                    }
+                })
+                .setOnCancelListener(dialog -> {
+                    if (cancelCallback != null) {
+                        cancelCallback.run();
+                    }
+                })
+                .create();
+
+        return dlg;
+    }
+
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 68a1e19..dce1e20 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -448,25 +448,41 @@
 
     @Test
     public void containsIncompatibleChargers_returnTrue() {
-        final List<UsbPort> usbPorts = new ArrayList<>();
-        usbPorts.add(mUsbPort);
-        when(mUsbManager.getPorts()).thenReturn(usbPorts);
-        when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
-        when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
-
+        setupIncompatibleCharging();
         assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isTrue();
     }
 
     @Test
     public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_notSupportComplianceWarnings_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(false);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    @Test
+    public void containsIncompatibleChargers_usbNotConnected_returnFalse() {
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.isConnected()).thenReturn(false);
+
+        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+    }
+
+    private void setupIncompatibleCharging() {
         final List<UsbPort> usbPorts = new ArrayList<>();
         usbPorts.add(mUsbPort);
         when(mUsbManager.getPorts()).thenReturn(usbPorts);
         when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
         when(mUsbPortStatus.isConnected()).thenReturn(true);
-        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
-
-        assertThat(Utils.containsIncompatibleChargers(mContext, "tag")).isFalse();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 340a6c7..c9dc1ba 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -60,8 +60,9 @@
         allKeys.add(CategoryKey.CATEGORY_GESTURES);
         allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
         allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
+        allKeys.add(CategoryKey.CATEGORY_COMMUNAL_SETTINGS);
         // DO NOT REMOVE ANYTHING ABOVE
 
-        assertThat(allKeys.size()).isEqualTo(19);
+        assertThat(allKeys.size()).isEqualTo(20);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
index 2bb3c2a..a15fe9f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.fuelgauge;
 
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE;
+import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertEquals;
@@ -186,4 +189,46 @@
         assertThat(Secure.getInt(mMockResolver, Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, -1))
                 .isEqualTo(1);
     }
+
+    @Test
+    public void testGetBatterySaverScheduleKey_returnExpectedKey() {
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_NO_SCHEDULE);
+
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 20);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_PERCENTAGE);
+
+        Global.putInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 20);
+        Global.putInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE,
+                PowerManager.POWER_SAVE_MODE_TRIGGER_DYNAMIC);
+
+        assertThat(BatterySaverUtils.getBatterySaverScheduleKey(mMockContext)).isEqualTo(
+                KEY_NO_SCHEDULE);
+    }
+
+    @Test
+    public void testSetBatterySaverScheduleMode_setSchedule() {
+        BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_NO_SCHEDULE, -1);
+
+        assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
+                .isEqualTo(PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1))
+                .isEqualTo(0);
+
+        BatterySaverUtils.setBatterySaverScheduleMode(mMockContext, KEY_PERCENTAGE, 20);
+
+        assertThat(Global.getInt(mMockResolver, Global.AUTOMATIC_POWER_SAVE_MODE, -1))
+                .isEqualTo(PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        assertThat(Global.getInt(mMockResolver, Global.LOW_POWER_MODE_TRIGGER_LEVEL, -1))
+                .isEqualTo(20);
+
+    }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed8a457..1356e1d 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -96,6 +96,7 @@
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SetAllResult;
+import android.provider.UpdatableDeviceConfigServiceReadiness;
 import android.provider.settings.validators.SystemSettingsValidators;
 import android.provider.settings.validators.Validator;
 import android.text.TextUtils;
@@ -416,10 +417,16 @@
             startWatchingUserRestrictionChanges();
         });
         ServiceManager.addService("settings", new SettingsService(this));
-        ServiceManager.addService("device_config", new DeviceConfigService(this));
+        addDeviceConfigServiceIfNeeded();
         return true;
     }
 
+    private void addDeviceConfigServiceIfNeeded() {
+        if (!UpdatableDeviceConfigServiceReadiness.shouldStartUpdatableService()) {
+            ServiceManager.addService("device_config", new DeviceConfigService(this));
+        }
+    }
+
     @Override
     public Bundle call(String method, String name, Bundle args) {
         final int requestingUserId = getRequestingUserId(args);
@@ -564,6 +571,11 @@
                 break;
             }
 
+            case Settings.CALL_METHOD_UNREGISTER_MONITOR_CALLBACK_CONFIG: {
+                clearMonitorCallback();
+                break;
+            }
+
             case Settings.CALL_METHOD_LIST_GLOBAL: {
                 Bundle result = new Bundle();
                 result.putStringArrayList(RESULT_SETTINGS_LIST,
@@ -2352,6 +2364,16 @@
         }
     }
 
+    private void clearMonitorCallback() {
+        getContext().enforceCallingOrSelfPermission(
+                Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS,
+                "Permission denial: registering for config access requires: "
+                        + Manifest.permission.MONITOR_DEVICE_CONFIG_ACCESS);
+        synchronized (mLock) {
+            mConfigMonitorCallback = null;
+        }
+    }
+
     private void reportDeviceConfigAccess(@Nullable String prefix) {
         if (prefix == null) {
             return;
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index b8ac384..af4d7d4 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -145,6 +145,7 @@
     <uses-permission android:name="android.permission.LOCATION_BYPASS" />
     <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
     <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" />
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
     <!-- Development tool permissions granted to the shell. -->
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
index f55fb97..9058510 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleShader.kt
@@ -169,11 +169,9 @@
             setFloatUniform("in_progress", value)
             val curvedProg = 1 - (1 - value) * (1 - value) * (1 - value)
 
-            setFloatUniform(
-                "in_size",
-                /* width= */ maxSize.x * curvedProg,
-                /* height= */ maxSize.y * curvedProg
-            )
+            currentWidth = maxSize.x * curvedProg
+            currentHeight = maxSize.y * curvedProg
+            setFloatUniform("in_size", /* width= */ currentWidth, /* height= */ currentHeight)
             setFloatUniform("in_thickness", maxSize.y * curvedProg * 0.5f)
             // radius should not exceed width and height values.
             setFloatUniform("in_cornerRadius", Math.min(maxSize.x, maxSize.y) * curvedProg)
@@ -237,4 +235,10 @@
      * False for a ring effect.
      */
     var rippleFill: Boolean = false
+
+    var currentWidth: Float = 0f
+        private set
+
+    var currentHeight: Float = 0f
+        private set
 }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
index ae28a8b..b37c734 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/ripple/RippleView.kt
@@ -36,7 +36,7 @@
  */
 open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
 
-    private lateinit var rippleShader: RippleShader
+    protected lateinit var rippleShader: RippleShader
     lateinit var rippleShape: RippleShape
         private set
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 894bb5f..cf7d2c5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -179,8 +179,9 @@
 
     @ProvidesInterface(version = Callbacks.VERSION)
     public interface Callbacks {
-        int VERSION = 1;
+        int VERSION = 2;
 
+        // requires version 1
         void onShowRequested(int reason, boolean keyguardLocked, int lockTaskModeState);
         void onDismissRequested(int reason);
         void onStateChanged(State state);
@@ -192,5 +193,7 @@
         void onShowSafetyWarning(int flags);
         void onAccessibilityModeChanged(Boolean showA11yStream);
         void onCaptionComponentStateChanged(Boolean isComponentEnabled, Boolean fromTooltip);
+        // requires version 2
+        void onShowCsdWarning(@AudioManager.CsdWarning int csdWarning, int durationMs);
     }
 }
diff --git a/packages/SystemUI/res/layout/screenshot_static.xml b/packages/SystemUI/res/layout/screenshot_static.xml
index efd683f..e4e0bd4 100644
--- a/packages/SystemUI/res/layout/screenshot_static.xml
+++ b/packages/SystemUI/res/layout/screenshot_static.xml
@@ -190,7 +190,6 @@
             app:layout_constraintBottom_toBottomOf="parent"
             android:contentDescription="@string/screenshot_dismiss_work_profile">
             <ImageView
-                android:id="@+id/screenshot_dismiss_image"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:layout_margin="@dimen/overlay_dismiss_button_margin"
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 561f7f1..1ef523b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -746,27 +746,6 @@
     <!-- How long in milliseconds before full burn-in protection is achieved. -->
     <integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
 
-    <!-- The duration in milliseconds of the y-translation animation when waking up from
-         the dream -->
-    <integer name="config_dreamOverlayOutTranslationYDurationMs">333</integer>
-    <!-- The delay in milliseconds of the y-translation animation when waking up from
-         the dream for the complications at the bottom of the screen -->
-    <integer name="config_dreamOverlayOutTranslationYDelayBottomMs">33</integer>
-    <!-- The delay in milliseconds of the y-translation animation when waking up from
-         the dream for the complications at the top of the screen -->
-    <integer name="config_dreamOverlayOutTranslationYDelayTopMs">117</integer>
-    <!-- The duration in milliseconds of the alpha animation when waking up from the dream -->
-    <integer name="config_dreamOverlayOutAlphaDurationMs">200</integer>
-    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
-         complications at the top of the screen -->
-    <integer name="config_dreamOverlayOutAlphaDelayTopMs">217</integer>
-    <!-- The delay in milliseconds of the alpha animation when waking up from the dream for the
-         complications at the bottom of the screen -->
-    <integer name="config_dreamOverlayOutAlphaDelayBottomMs">133</integer>
-    <!-- The duration in milliseconds of the blur animation when waking up from
-         the dream -->
-    <integer name="config_dreamOverlayOutBlurDurationMs">250</integer>
-
     <integer name="complicationFadeOutMs">500</integer>
 
     <integer name="complicationFadeInMs">500</integer>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 6a9149e..ea1095d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1286,6 +1286,9 @@
          translate into their final position. -->
     <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen>
 
+    <!-- DREAMING -> LOCKSCREEN transition: Amount to shift lockscreen content on entering -->
+    <dimen name="dreaming_to_lockscreen_transition_lockscreen_translation_y">40dp</dimen>
+
     <!-- The amount of vertical offset for the keyguard during the full shade transition. -->
     <dimen name="lockscreen_shade_keyguard_transition_vertical_offset">0dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ce3084c..2eb58b9 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2870,7 +2870,7 @@
     Error message shown when a button should be pressed and held to activate it, usually shown when
     the user attempted to tap the button or held it for too short a time. [CHAR LIMIT=32].
     -->
-    <string name="keyguard_affordance_press_too_short">Press and hold to activate</string>
+    <string name="keyguard_affordance_press_too_short">Touch &amp; hold to open</string>
 
     <!-- Text for education page of cancel button to hide the page. [CHAR_LIMIT=NONE] -->
     <string name="rear_display_bottom_sheet_cancel">Cancel</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
index baaef19..7da27b1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java
@@ -103,7 +103,6 @@
 
     @Override
     public void reset() {
-        super.reset();
         // start fresh
         mDismissing = false;
         mView.resetPasswordText(false /* animate */, false /* announce */);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index b143c5b..d1c9a30 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -121,7 +121,6 @@
 
     @Override
     public void reset() {
-        mMessageAreaController.setMessage("", false);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 8b9823b..b8e196f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static java.util.Collections.emptySet;
+
 import android.content.Context;
 import android.os.Trace;
 import android.util.AttributeSet;
@@ -88,8 +90,9 @@
     }
 
     /** Sets a translationY value on every child view except for the media view. */
-    public void setChildrenTranslationYExcludingMediaView(float translationY) {
-        setChildrenTranslationYExcluding(translationY, Set.of(mMediaHostContainer));
+    public void setChildrenTranslationY(float translationY, boolean excludeMedia) {
+        setChildrenTranslationYExcluding(translationY,
+                excludeMedia ? Set.of(mMediaHostContainer) : emptySet());
     }
 
     /** Sets a translationY value on every view except for the views in the provided set. */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 7849747..aec3063 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -20,6 +20,8 @@
 import android.util.Slog;
 
 import com.android.keyguard.KeyguardClockSwitch.ClockSize;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
@@ -59,6 +61,7 @@
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             ConfigurationController configurationController,
             DozeParameters dozeParameters,
+            FeatureFlags featureFlags,
             ScreenOffAnimationController screenOffAnimationController) {
         super(keyguardStatusView);
         mKeyguardSliceViewController = keyguardSliceViewController;
@@ -67,6 +70,8 @@
         mConfigurationController = configurationController;
         mKeyguardVisibilityHelper = new KeyguardVisibilityHelper(mView, keyguardStateController,
                 dozeParameters, screenOffAnimationController, /* animateYPos= */ true);
+        mKeyguardVisibilityHelper.setOcclusionTransitionFlagEnabled(
+                featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION));
     }
 
     @Override
@@ -115,8 +120,8 @@
     /**
      * Sets a translationY on the views on the keyguard, except on the media view.
      */
-    public void setTranslationYExcludingMedia(float translationY) {
-        mView.setChildrenTranslationYExcludingMediaView(translationY);
+    public void setTranslationY(float translationY, boolean excludeMedia) {
+        mView.setChildrenTranslationY(translationY, excludeMedia);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
index f974e27..edd150c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt
@@ -19,6 +19,8 @@
 import android.content.Context
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -36,27 +38,29 @@
 @Inject
 constructor(
     private val context: Context,
-    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider
+    statusBarStateController: StatusBarStateController,
+    unfoldProgressProvider: NaturalRotationUnfoldProgressProvider,
 ) {
 
     /** Certain views only need to move if they are not currently centered */
     var statusViewCentered = false
 
-    private val filterSplitShadeOnly = { !statusViewCentered }
-    private val filterNever = { true }
+    private val filterKeyguardAndSplitShadeOnly: () -> Boolean = {
+        statusBarStateController.getState() == KEYGUARD && !statusViewCentered }
+    private val filterKeyguard: () -> Boolean = { statusBarStateController.getState() == KEYGUARD }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.keyguard_status_area, START, filterNever),
+                    ViewIdToTranslate(R.id.keyguard_status_area, START, filterKeyguard),
                     ViewIdToTranslate(
-                        R.id.lockscreen_clock_view_large, START, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterNever),
+                        R.id.lockscreen_clock_view_large, START, filterKeyguardAndSplitShadeOnly),
+                    ViewIdToTranslate(R.id.lockscreen_clock_view, START, filterKeyguard),
                     ViewIdToTranslate(
-                        R.id.notification_stack_scroller, END, filterSplitShadeOnly),
-                    ViewIdToTranslate(R.id.start_button, START, filterNever),
-                    ViewIdToTranslate(R.id.end_button, END, filterNever)),
+                        R.id.notification_stack_scroller, END, filterKeyguardAndSplitShadeOnly),
+                    ViewIdToTranslate(R.id.start_button, START, filterKeyguard),
+                    ViewIdToTranslate(R.id.end_button, END, filterKeyguard)),
             progressProvider = unfoldProgressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index e3c58ce..271fc7b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -875,7 +875,7 @@
         Assert.isMainThread();
         if (mWakeOnFingerprintAcquiredStart && acquireInfo == FINGERPRINT_ACQUIRED_START) {
             mPowerManager.wakeUp(
-                    SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                    SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
                     "com.android.systemui.keyguard:FINGERPRINT_ACQUIRED_START");
         }
         for (int i = 0; i < mCallbacks.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
index 498304b..bde0692 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java
@@ -44,6 +44,7 @@
     private boolean mAnimateYPos;
     private boolean mKeyguardViewVisibilityAnimating;
     private boolean mLastOccludedState = false;
+    private boolean mIsUnoccludeTransitionFlagEnabled = false;
     private final AnimationProperties mAnimationProperties = new AnimationProperties();
 
     public KeyguardVisibilityHelper(View view,
@@ -62,6 +63,10 @@
         return mKeyguardViewVisibilityAnimating;
     }
 
+    public void setOcclusionTransitionFlagEnabled(boolean enabled) {
+        mIsUnoccludeTransitionFlagEnabled = enabled;
+    }
+
     /**
      * Set the visibility of a keyguard view based on some new state.
      */
@@ -129,7 +134,7 @@
                 // since it may need to be cancelled due to keyguard lifecycle events.
                 mScreenOffAnimationController.animateInKeyguard(
                         mView, mAnimateKeyguardStatusViewVisibleEndRunnable);
-            } else if (mLastOccludedState && !isOccluded) {
+            } else if (!mIsUnoccludeTransitionFlagEnabled && mLastOccludedState && !isOccluded) {
                 // An activity was displayed over the lock screen, and has now gone away
                 mView.setVisibility(View.VISIBLE);
                 mView.setAlpha(0f);
@@ -142,7 +147,9 @@
                         .start();
             } else {
                 mView.setVisibility(View.VISIBLE);
-                mView.setAlpha(1f);
+                if (!mIsUnoccludeTransitionFlagEnabled) {
+                    mView.setAlpha(1f);
+                }
             }
         } else {
             mView.setVisibility(View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 815ac68..e42f051 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -120,6 +120,7 @@
     private final Interpolator mLinearOutSlowIn;
     private final LockPatternUtils mLockPatternUtils;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private final InteractionJankMonitor mInteractionJankMonitor;
 
     // TODO: these should be migrated out once ready
@@ -141,7 +142,6 @@
     private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     private final @Background DelayableExecutor mBackgroundExecutor;
-    private boolean mIsOrientationChanged = false;
 
     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
     @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason;
@@ -235,6 +235,7 @@
                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
                 @Nullable List<FaceSensorPropertiesInternal> faceProps,
                 @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+                @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
                 @NonNull UserManager userManager,
                 @NonNull LockPatternUtils lockPatternUtils,
                 @NonNull InteractionJankMonitor jankMonitor,
@@ -242,8 +243,9 @@
                 @NonNull Provider<CredentialViewModel> credentialViewModelProvider) {
             mConfig.mSensorIds = sensorIds;
             return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle,
-                    userManager, lockPatternUtils, jankMonitor, biometricPromptInteractor,
-                    credentialViewModelProvider, new Handler(Looper.getMainLooper()), bgExecutor);
+                    panelInteractionDetector, userManager, lockPatternUtils, jankMonitor,
+                    biometricPromptInteractor, credentialViewModelProvider,
+                    new Handler(Looper.getMainLooper()), bgExecutor);
         }
     }
 
@@ -331,6 +333,7 @@
             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
             @Nullable List<FaceSensorPropertiesInternal> faceProps,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull InteractionJankMonitor jankMonitor,
@@ -346,6 +349,7 @@
         mHandler = mainHandler;
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPanelInteractionDetector = panelInteractionDetector;
 
         mTranslationY = getResources()
                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
@@ -490,22 +494,6 @@
     @Override
     public void onOrientationChanged() {
         maybeUpdatePositionForUdfps(true /* invalidate */);
-        mIsOrientationChanged = true;
-    }
-
-    @Override
-    public void onWindowFocusChanged(boolean hasWindowFocus) {
-        super.onWindowFocusChanged(hasWindowFocus);
-        if (!hasWindowFocus) {
-            //it's a workaround to avoid closing BP incorrectly
-            //BP gets a onWindowFocusChanged(false) and then gets a onWindowFocusChanged(true)
-            if (mIsOrientationChanged) {
-                mIsOrientationChanged = false;
-                return;
-            }
-            Log.v(TAG, "Lost window focus, dismissing the dialog");
-            animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
-        }
     }
 
     @Override
@@ -513,6 +501,8 @@
         super.onAttachedToWindow();
 
         mWakefulnessLifecycle.addObserver(this);
+        mPanelInteractionDetector.enable(
+                () -> animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED));
 
         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
             mBiometricScrollView.addView(mBiometricView);
@@ -666,11 +656,6 @@
             mBiometricView.restoreState(savedState);
         }
 
-        if (savedState != null) {
-            mIsOrientationChanged = savedState.getBoolean(
-                    AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED);
-        }
-
         wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
     }
 
@@ -689,6 +674,7 @@
 
     @Override
     public void dismissWithoutCallback(boolean animate) {
+        mPanelInteractionDetector.disable();
         if (animate) {
             animateAway(false /* sendReason */, 0 /* reason */);
         } else {
@@ -699,6 +685,7 @@
 
     @Override
     public void dismissFromSystemServer() {
+        mPanelInteractionDetector.disable();
         animateAway(false /* sendReason */, 0 /* reason */);
     }
 
@@ -761,8 +748,6 @@
                 mBiometricView != null && mCredentialView == null);
         outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
 
-        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_ORIENTATION_CHANGED, mIsOrientationChanged);
-
         if (mBiometricView != null) {
             mBiometricView.onSaveState(outState);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a0f3ecb0..dad6ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -164,6 +164,7 @@
     @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
+    private final AuthDialogPanelInteractionDetector mPanelInteractionDetector;
     private boolean mAllFingerprintAuthenticatorsRegistered;
     @NonNull private final UserManager mUserManager;
     @NonNull private final LockPatternUtils mLockPatternUtils;
@@ -721,6 +722,7 @@
             Provider<SideFpsController> sidefpsControllerFactory,
             @NonNull DisplayManager displayManager,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils,
             @NonNull UdfpsLogger udfpsLogger,
@@ -767,6 +769,8 @@
                 });
 
         mWakefulnessLifecycle = wakefulnessLifecycle;
+        mPanelInteractionDetector = panelInteractionDetector;
+
 
         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
         int[] faceAuthLocation = context.getResources().getIntArray(
@@ -1149,6 +1153,7 @@
                 requestId,
                 multiSensorConfig,
                 mWakefulnessLifecycle,
+                mPanelInteractionDetector,
                 mUserManager,
                 mLockPatternUtils);
 
@@ -1239,6 +1244,7 @@
             String opPackageName, boolean skipIntro, long operationId, long requestId,
             @BiometricMultiSensorMode int multiSensorConfig,
             @NonNull WakefulnessLifecycle wakefulnessLifecycle,
+            @NonNull AuthDialogPanelInteractionDetector panelInteractionDetector,
             @NonNull UserManager userManager,
             @NonNull LockPatternUtils lockPatternUtils) {
         return new AuthContainerView.Builder(mContext)
@@ -1253,8 +1259,9 @@
                 .setMultiSensorConfig(multiSensorConfig)
                 .setScaleFactorProvider(() -> getScaleFactor())
                 .build(bgExecutor, sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle,
-                        userManager, lockPatternUtils, mInteractionJankMonitor,
-                        mBiometricPromptInteractor, mCredentialViewModelProvider);
+                        panelInteractionDetector, userManager, lockPatternUtils,
+                        mInteractionJankMonitor, mBiometricPromptInteractor,
+                        mCredentialViewModelProvider);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index cd0fc37..51f39b3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -48,8 +48,6 @@
     String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
     String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
 
-    String KEY_BIOMETRIC_ORIENTATION_CHANGED = "orientation_changed";
-
     int SIZE_UNKNOWN = 0;
     /**
      * Minimal UI, showing only biometric icon.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
new file mode 100644
index 0000000..64211b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt
@@ -0,0 +1,53 @@
+package com.android.systemui.biometrics
+
+import android.annotation.AnyThread
+import android.annotation.MainThread
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.shade.ShadeExpansionChangeEvent
+import com.android.systemui.shade.ShadeExpansionStateManager
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class AuthDialogPanelInteractionDetector
+@Inject
+constructor(
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
+    @Main private val mainExecutor: Executor,
+) {
+    private var action: Action? = null
+
+    @MainThread
+    fun enable(onPanelInteraction: Runnable) {
+        if (action == null) {
+            action = Action(onPanelInteraction)
+            shadeExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
+        } else {
+            Log.e(TAG, "Already enabled")
+        }
+    }
+
+    @MainThread
+    fun disable() {
+        if (action != null) {
+            action = null
+            shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged)
+        }
+    }
+
+    @AnyThread
+    private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) =
+        mainExecutor.execute {
+            action?.let {
+                if (event.tracking) {
+                    Log.v(TAG, "Detected panel interaction, event: $event")
+                    it.onPanelInteraction.run()
+                    disable()
+                }
+            }
+        }
+}
+
+private data class Action(val onPanelInteraction: Runnable)
+
+private const val TAG = "AuthDialogPanelInteractionDetector"
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
index 82e5704..805a20a 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardListener.java
@@ -16,15 +16,21 @@
 
 package com.android.systemui.clipboardoverlay;
 
+import static android.content.ClipDescription.CLASSIFICATION_COMPLETE;
+
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED;
+import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN;
+
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.os.SystemProperties;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -56,6 +62,7 @@
     private final DeviceConfigProxy mDeviceConfig;
     private final Provider<ClipboardOverlayController> mOverlayProvider;
     private final ClipboardOverlayControllerLegacyFactory mOverlayFactory;
+    private final ClipboardToast mClipboardToast;
     private final ClipboardManager mClipboardManager;
     private final UiEventLogger mUiEventLogger;
     private final FeatureFlags mFeatureFlags;
@@ -66,6 +73,7 @@
     public ClipboardListener(Context context, DeviceConfigProxy deviceConfigProxy,
             Provider<ClipboardOverlayController> clipboardOverlayControllerProvider,
             ClipboardOverlayControllerLegacyFactory overlayFactory,
+            ClipboardToast clipboardToast,
             ClipboardManager clipboardManager,
             UiEventLogger uiEventLogger,
             FeatureFlags featureFlags) {
@@ -73,6 +81,7 @@
         mDeviceConfig = deviceConfigProxy;
         mOverlayProvider = clipboardOverlayControllerProvider;
         mOverlayFactory = overlayFactory;
+        mClipboardToast = clipboardToast;
         mClipboardManager = clipboardManager;
         mUiEventLogger = uiEventLogger;
         mFeatureFlags = featureFlags;
@@ -102,6 +111,15 @@
             return;
         }
 
+        if (!isUserSetupComplete()) {
+            // just show a toast, user should not access intents from this state
+            if (shouldShowToast(clipData)) {
+                mUiEventLogger.log(CLIPBOARD_TOAST_SHOWN, 0, clipSource);
+                mClipboardToast.showCopiedToast();
+            }
+            return;
+        }
+
         boolean enabled = mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR);
         if (mClipboardOverlay == null || enabled != mUsingNewOverlay) {
             mUsingNewOverlay = enabled;
@@ -136,10 +154,26 @@
         return clipData.getDescription().getExtras().getBoolean(EXTRA_SUPPRESS_OVERLAY, false);
     }
 
+    boolean shouldShowToast(ClipData clipData) {
+        if (clipData == null) {
+            return false;
+        } else if (clipData.getDescription().getClassificationStatus() == CLASSIFICATION_COMPLETE) {
+            // only show for classification complete if we aren't already showing a toast, to ignore
+            // the duplicate ClipData with classification
+            return !mClipboardToast.isShowing();
+        }
+        return true;
+    }
+
     private static boolean isEmulator() {
         return SystemProperties.getBoolean("ro.boot.qemu", false);
     }
 
+    private boolean isUserSetupComplete() {
+        return Settings.Secure.getInt(mContext.getContentResolver(),
+                SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+    }
+
     interface ClipboardOverlay {
         void setClipData(ClipData clipData, String clipSource);
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
index 9917507..4b5f876 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayEvent.java
@@ -43,7 +43,9 @@
     @UiEvent(doc = "clipboard overlay tapped outside")
     CLIPBOARD_OVERLAY_TAP_OUTSIDE(1077),
     @UiEvent(doc = "clipboard overlay dismissed, miscellaneous reason")
-    CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078);
+    CLIPBOARD_OVERLAY_DISMISSED_OTHER(1078),
+    @UiEvent(doc = "clipboard toast shown")
+    CLIPBOARD_TOAST_SHOWN(1270);
 
     private final int mId;
 
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
new file mode 100644
index 0000000..0ed7d27
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardToast.java
@@ -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.clipboardoverlay;
+
+import android.content.Context;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+/**
+ * Utility class for showing a simple clipboard toast on copy.
+ */
+class ClipboardToast extends Toast.Callback {
+    private final Context mContext;
+    private Toast mCopiedToast;
+
+    @Inject
+    ClipboardToast(Context context) {
+        mContext = context;
+    }
+
+    void showCopiedToast() {
+        if (mCopiedToast != null) {
+            mCopiedToast.cancel();
+        }
+        mCopiedToast = Toast.makeText(mContext,
+                R.string.clipboard_overlay_text_copied, Toast.LENGTH_SHORT);
+        mCopiedToast.show();
+    }
+
+    boolean isShowing() {
+        return mCopiedToast != null;
+    }
+
+    @Override // Toast.Callback
+    public void onToastHidden() {
+        super.onToastHidden();
+        mCopiedToast = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
index 16f4150..c746efd 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt
@@ -20,7 +20,7 @@
 import android.database.ContentObserver
 import android.os.Handler
 import android.os.Looper
-import android.provider.Settings
+import com.android.systemui.util.settings.GlobalSettings
 
 /**
  * Class to track the availability of [DemoMode]. Use this class to track the availability and
@@ -29,7 +29,10 @@
  * This class works by wrapping a content observer for the relevant keys related to DemoMode
  * availability and current on/off state, and triggering callbacks.
  */
-abstract class DemoModeAvailabilityTracker(val context: Context) {
+abstract class DemoModeAvailabilityTracker(
+    val context: Context,
+    val globalSettings: GlobalSettings,
+) {
     var isInDemoMode = false
     var isDemoModeAvailable = false
 
@@ -41,9 +44,9 @@
     fun startTracking() {
         val resolver = context.contentResolver
         resolver.registerContentObserver(
-                Settings.Global.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
+                globalSettings.getUriFor(DEMO_MODE_ALLOWED), false, allowedObserver)
         resolver.registerContentObserver(
-                Settings.Global.getUriFor(DEMO_MODE_ON), false, onObserver)
+                globalSettings.getUriFor(DEMO_MODE_ON), false, onObserver)
     }
 
     fun stopTracking() {
@@ -57,12 +60,11 @@
     abstract fun onDemoModeFinished()
 
     private fun checkIsDemoModeAllowed(): Boolean {
-        return Settings.Global
-                .getInt(context.contentResolver, DEMO_MODE_ALLOWED, 0) != 0
+        return globalSettings.getInt(DEMO_MODE_ALLOWED, 0) != 0
     }
 
     private fun checkIsDemoModeOn(): Boolean {
-        return Settings.Global.getInt(context.contentResolver, DEMO_MODE_ON, 0) != 0
+        return globalSettings.getInt(DEMO_MODE_ON, 0) != 0
     }
 
     private val allowedObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
index 000bbe6..84f83f1 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
+++ b/packages/SystemUI/src/com/android/systemui/demomode/DemoModeController.kt
@@ -24,22 +24,28 @@
 import android.os.UserHandle
 import android.util.Log
 import com.android.systemui.Dumpable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.demomode.DemoMode.ACTION_DEMO
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.CallbackController
 import com.android.systemui.util.Assert
 import com.android.systemui.util.settings.GlobalSettings
 import java.io.PrintWriter
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
 
 /**
  * Handles system broadcasts for [DemoMode]
  *
  * Injected via [DemoModeModule]
  */
-class DemoModeController constructor(
+class DemoModeController
+constructor(
     private val context: Context,
     private val dumpManager: DumpManager,
-    private val globalSettings: GlobalSettings
+    private val globalSettings: GlobalSettings,
+    private val broadcastDispatcher: BroadcastDispatcher,
 ) : CallbackController<DemoMode>, Dumpable {
 
     // Var updated when the availability tracker changes, or when we enter/exit demo mode in-process
@@ -58,9 +64,7 @@
         requestFinishDemoMode()
 
         val m = mutableMapOf<String, MutableList<DemoMode>>()
-        DemoMode.COMMANDS.map { command ->
-            m.put(command, mutableListOf())
-        }
+        DemoMode.COMMANDS.map { command -> m.put(command, mutableListOf()) }
         receiverMap = m
     }
 
@@ -71,7 +75,7 @@
 
         initialized = true
 
-        dumpManager.registerDumpable(TAG, this)
+        dumpManager.registerNormalDumpable(TAG, this)
 
         // Due to DemoModeFragment running in systemui:tuner process, we have to observe for
         // content changes to know if the setting turned on or off
@@ -81,8 +85,13 @@
 
         val demoFilter = IntentFilter()
         demoFilter.addAction(ACTION_DEMO)
-        context.registerReceiverAsUser(broadcastReceiver, UserHandle.ALL, demoFilter,
-                android.Manifest.permission.DUMP, null, Context.RECEIVER_EXPORTED)
+
+        broadcastDispatcher.registerReceiver(
+            receiver = broadcastReceiver,
+            filter = demoFilter,
+            user = UserHandle.ALL,
+            permission = android.Manifest.permission.DUMP,
+        )
     }
 
     override fun addCallback(listener: DemoMode) {
@@ -91,16 +100,15 @@
 
         commands.forEach { command ->
             if (!receiverMap.containsKey(command)) {
-                throw IllegalStateException("Command ($command) not recognized. " +
-                        "See DemoMode.java for valid commands")
+                throw IllegalStateException(
+                    "Command ($command) not recognized. " + "See DemoMode.java for valid commands"
+                )
             }
 
             receiverMap[command]!!.add(listener)
         }
 
-        synchronized(this) {
-            receivers.add(listener)
-        }
+        synchronized(this) { receivers.add(listener) }
 
         if (isInDemoMode) {
             listener.onDemoModeStarted()
@@ -109,14 +117,46 @@
 
     override fun removeCallback(listener: DemoMode) {
         synchronized(this) {
-            listener.demoCommands().forEach { command ->
-                receiverMap[command]!!.remove(listener)
-            }
+            listener.demoCommands().forEach { command -> receiverMap[command]!!.remove(listener) }
 
             receivers.remove(listener)
         }
     }
 
+    /**
+     * Create a [Flow] for the stream of demo mode arguments that come in for the given [command]
+     *
+     * This is equivalent of creating a listener manually and adding an event handler for the given
+     * command, like so:
+     *
+     * ```
+     * class Demoable {
+     *   private val demoHandler = object : DemoMode {
+     *     override fun demoCommands() = listOf(<command>)
+     *
+     *     override fun dispatchDemoCommand(command: String, args: Bundle) {
+     *       handleDemoCommand(args)
+     *     }
+     *   }
+     * }
+     * ```
+     *
+     * @param command The top-level demo mode command you want a stream for
+     */
+    fun demoFlowForCommand(command: String): Flow<Bundle> = conflatedCallbackFlow {
+        val callback =
+            object : DemoMode {
+                override fun demoCommands(): List<String> = listOf(command)
+
+                override fun dispatchDemoCommand(command: String, args: Bundle) {
+                    trySend(args)
+                }
+            }
+
+        addCallback(callback)
+        awaitClose { removeCallback(callback) }
+    }
+
     private fun setIsDemoModeAllowed(enabled: Boolean) {
         // Turn off demo mode if it was on
         if (isInDemoMode && !enabled) {
@@ -129,13 +169,9 @@
         Assert.isMainThread()
 
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
+        synchronized(this) { copy = receivers.toList() }
 
-        copy.forEach { r ->
-            r.onDemoModeStarted()
-        }
+        copy.forEach { r -> r.onDemoModeStarted() }
     }
 
     private fun exitDemoMode() {
@@ -143,18 +179,13 @@
         Assert.isMainThread()
 
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
+        synchronized(this) { copy = receivers.toList() }
 
-        copy.forEach { r ->
-            r.onDemoModeFinished()
-        }
+        copy.forEach { r -> r.onDemoModeFinished() }
     }
 
     fun dispatchDemoCommand(command: String, args: Bundle) {
         Assert.isMainThread()
-
         if (DEBUG) {
             Log.d(TAG, "dispatchDemoCommand: $command, args=$args")
         }
@@ -172,9 +203,7 @@
         }
 
         // See? demo mode is easy now, you just notify the listeners when their command is called
-        receiverMap[command]!!.forEach { receiver ->
-            receiver.dispatchDemoCommand(command, args)
-        }
+        receiverMap[command]!!.forEach { receiver -> receiver.dispatchDemoCommand(command, args) }
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
@@ -183,65 +212,64 @@
         pw.println("  isDemoModeAllowed=$isAvailable")
         pw.print("  receivers=[")
         val copy: List<DemoModeCommandReceiver>
-        synchronized(this) {
-            copy = receivers.toList()
-        }
-        copy.forEach { recv ->
-            pw.print(" ${recv.javaClass.simpleName}")
-        }
+        synchronized(this) { copy = receivers.toList() }
+        copy.forEach { recv -> pw.print(" ${recv.javaClass.simpleName}") }
         pw.println(" ]")
         pw.println("  receiverMap= [")
         receiverMap.keys.forEach { command ->
             pw.print("    $command : [")
-            val recvs = receiverMap[command]!!.map { receiver ->
-                receiver.javaClass.simpleName
-            }.joinToString(",")
+            val recvs =
+                receiverMap[command]!!
+                    .map { receiver -> receiver.javaClass.simpleName }
+                    .joinToString(",")
             pw.println("$recvs ]")
         }
     }
 
-    private val tracker = object : DemoModeAvailabilityTracker(context) {
-        override fun onDemoModeAvailabilityChanged() {
-            setIsDemoModeAllowed(isDemoModeAvailable)
-        }
+    private val tracker =
+        object : DemoModeAvailabilityTracker(context, globalSettings) {
+            override fun onDemoModeAvailabilityChanged() {
+                setIsDemoModeAllowed(isDemoModeAvailable)
+            }
 
-        override fun onDemoModeStarted() {
-            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
-                enterDemoMode()
+            override fun onDemoModeStarted() {
+                if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                    enterDemoMode()
+                }
+            }
+
+            override fun onDemoModeFinished() {
+                if (this@DemoModeController.isInDemoMode != isInDemoMode) {
+                    exitDemoMode()
+                }
             }
         }
 
-        override fun onDemoModeFinished() {
-            if (this@DemoModeController.isInDemoMode != isInDemoMode) {
-                exitDemoMode()
+    private val broadcastReceiver =
+        object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                if (DEBUG) {
+                    Log.v(TAG, "onReceive: $intent")
+                }
+
+                val action = intent.action
+                if (!ACTION_DEMO.equals(action)) {
+                    return
+                }
+
+                val bundle = intent.extras ?: return
+                val command = bundle.getString("command", "").trim().lowercase()
+                if (command.isEmpty()) {
+                    return
+                }
+
+                try {
+                    dispatchDemoCommand(command, bundle)
+                } catch (t: Throwable) {
+                    Log.w(TAG, "Error running demo command, intent=$intent $t")
+                }
             }
         }
-    }
-
-    private val broadcastReceiver = object : BroadcastReceiver() {
-        override fun onReceive(context: Context, intent: Intent) {
-            if (DEBUG) {
-                Log.v(TAG, "onReceive: $intent")
-            }
-
-            val action = intent.action
-            if (!ACTION_DEMO.equals(action)) {
-                return
-            }
-
-            val bundle = intent.extras ?: return
-            val command = bundle.getString("command", "").trim().toLowerCase()
-            if (command.length == 0) {
-                return
-            }
-
-            try {
-                dispatchDemoCommand(command, bundle)
-            } catch (t: Throwable) {
-                Log.w(TAG, "Error running demo command, intent=$intent $t")
-            }
-        }
-    }
 
     fun requestSetDemoModeAllowed(allowed: Boolean) {
         setGlobal(DEMO_MODE_ALLOWED, if (allowed) 1 else 0)
@@ -258,10 +286,12 @@
     private fun setGlobal(key: String, value: Int) {
         globalSettings.putInt(key, value)
     }
+
+    companion object {
+        const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
+        const val DEMO_MODE_ON = "sysui_tuner_demo_on"
+    }
 }
 
 private const val TAG = "DemoModeController"
-private const val DEMO_MODE_ALLOWED = "sysui_demo_allowed"
-private const val DEMO_MODE_ON = "sysui_tuner_demo_on"
-
 private const val DEBUG = false
diff --git a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
index de9affb..b84fa5a 100644
--- a/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/demomode/dagger/DemoModeModule.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.dump.DumpManager;
@@ -37,8 +38,14 @@
     static DemoModeController provideDemoModeController(
             Context context,
             DumpManager dumpManager,
-            GlobalSettings globalSettings) {
-        DemoModeController dmc = new DemoModeController(context, dumpManager, globalSettings);
+            GlobalSettings globalSettings,
+            BroadcastDispatcher broadcastDispatcher
+    ) {
+        DemoModeController dmc = new DemoModeController(
+                context,
+                dumpManager,
+                globalSettings,
+                broadcastDispatcher);
         dmc.initialize();
         return /*run*/dmc;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 5d21349..5b90ef2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -16,7 +16,14 @@
 
 package com.android.systemui.doze;
 
+import static android.os.PowerManager.WAKE_REASON_BIOMETRIC;
+import static android.os.PowerManager.WAKE_REASON_GESTURE;
+import static android.os.PowerManager.WAKE_REASON_LIFT;
+import static android.os.PowerManager.WAKE_REASON_PLUGGED_IN;
+import static android.os.PowerManager.WAKE_REASON_TAP;
+
 import android.annotation.IntDef;
+import android.os.PowerManager;
 import android.util.TimeUtils;
 
 import androidx.annotation.NonNull;
@@ -511,6 +518,25 @@
         }
     }
 
+    /**
+     * Converts {@link Reason} to {@link PowerManager.WakeReason}.
+     */
+    public static @PowerManager.WakeReason int getPowerManagerWakeReason(@Reason int wakeReason) {
+        switch (wakeReason) {
+            case REASON_SENSOR_DOUBLE_TAP:
+            case REASON_SENSOR_TAP:
+                return WAKE_REASON_TAP;
+            case REASON_SENSOR_PICKUP:
+                return WAKE_REASON_LIFT;
+            case REASON_SENSOR_UDFPS_LONG_PRESS:
+                return WAKE_REASON_BIOMETRIC;
+            case PULSE_REASON_DOCKING:
+                return WAKE_REASON_PLUGGED_IN;
+            default:
+                return WAKE_REASON_GESTURE;
+        }
+    }
+
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({PULSE_REASON_NONE, PULSE_REASON_INTENT, PULSE_REASON_NOTIFICATION,
             PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index f64d918..c73387b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -24,6 +24,7 @@
 import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
 
 import android.annotation.AnyThread;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
@@ -43,6 +44,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.internal.R;
 import com.android.internal.logging.UiEvent;
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.UiEventLoggerImpl;
@@ -143,6 +145,7 @@
     }
 
     DozeSensors(
+            Resources resources,
             AsyncSensorManager sensorManager,
             DozeParameters dozeParameters,
             AmbientDisplayConfiguration config,
@@ -188,7 +191,8 @@
                 new TriggerSensor(
                         mSensorManager.getDefaultSensor(Sensor.TYPE_PICK_UP_GESTURE),
                         Settings.Secure.DOZE_PICK_UP_GESTURE,
-                        true /* settingDef */,
+                        resources.getBoolean(
+                                R.bool.config_dozePickupGestureEnabled) /* settingDef */,
                         config.dozePickupSensorAvailable(),
                         DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
                         false /* touchscreen */,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
index f8bd1e7..ba38ab0 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeService.java
@@ -116,8 +116,8 @@
 
     @Override
     public void requestWakeUp(@DozeLog.Reason int reason) {
-        PowerManager pm = getSystemService(PowerManager.class);
-        pm.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+        final PowerManager pm = getSystemService(PowerManager.class);
+        pm.wakeUp(SystemClock.uptimeMillis(), DozeLog.getPowerManagerWakeReason(reason),
                 "com.android.systemui:NODOZE " + DozeLog.reasonToString(reason));
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 3f9f14c..b95c3f3 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -210,7 +210,7 @@
         mAllowPulseTriggers = true;
         mSessionTracker = sessionTracker;
 
-        mDozeSensors = new DozeSensors(mSensorManager, dozeParameters,
+        mDozeSensors = new DozeSensors(mContext.getResources(), mSensorManager, dozeParameters,
                 config, wakeLock, this::onSensor, this::onProximityFar, dozeLog, proximitySensor,
                 secureSettings, authController, devicePostureController, userTracker);
         mDockManager = dockManager;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 9b8ef71..c882f8a 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -22,6 +22,9 @@
 import android.view.View
 import android.view.animation.Interpolator
 import androidx.core.animation.doOnEnd
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
 import com.android.systemui.animation.Interpolators
 import com.android.systemui.dreams.complication.ComplicationHostViewController
 import com.android.systemui.dreams.complication.ComplicationLayoutParams
@@ -29,10 +32,20 @@
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.POSITION_TOP
 import com.android.systemui.dreams.complication.ComplicationLayoutParams.Position
 import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_ANIMATION_DURATION
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.statusbar.BlurUtils
 import com.android.systemui.statusbar.CrossFadeHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
+import com.android.systemui.util.concurrency.DelayableExecutor
 import javax.inject.Inject
 import javax.inject.Named
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.launch
 
 /** Controller for dream overlay animations. */
 class DreamOverlayAnimationsController
@@ -43,6 +56,8 @@
     private val mStatusBarViewController: DreamOverlayStatusBarViewController,
     private val mOverlayStateController: DreamOverlayStateController,
     @Named(DreamOverlayModule.DREAM_BLUR_RADIUS) private val mDreamBlurRadius: Int,
+    private val transitionViewModel: DreamingToLockscreenTransitionViewModel,
+    private val configController: ConfigurationController,
     @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
     private val mDreamInBlurAnimDurationMs: Long,
     @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
@@ -51,22 +66,10 @@
     private val mDreamInTranslationYDistance: Int,
     @Named(DreamOverlayModule.DREAM_IN_TRANSLATION_Y_DURATION)
     private val mDreamInTranslationYDurationMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DISTANCE)
-    private val mDreamOutTranslationYDistance: Int,
-    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DURATION)
-    private val mDreamOutTranslationYDurationMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
-    private val mDreamOutTranslationYDelayBottomMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
-    private val mDreamOutTranslationYDelayTopMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DURATION) private val mDreamOutAlphaDurationMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_BOTTOM)
-    private val mDreamOutAlphaDelayBottomMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_ALPHA_DELAY_TOP) private val mDreamOutAlphaDelayTopMs: Long,
-    @Named(DreamOverlayModule.DREAM_OUT_BLUR_DURATION) private val mDreamOutBlurDurationMs: Long
 ) {
 
     private var mAnimator: Animator? = null
+    private lateinit var view: View
 
     /**
      * Store the current alphas at the various positions. This is so that we may resume an animation
@@ -76,9 +79,61 @@
 
     private var mCurrentBlurRadius: Float = 0f
 
+    fun init(view: View) {
+        this.view = view
+
+        view.repeatWhenAttached {
+            val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
+            val configCallback =
+                object : ConfigurationListener {
+                    override fun onDensityOrFontScaleChanged() {
+                        configurationBasedDimensions.value = loadFromResources(view)
+                    }
+                }
+
+            configController.addCallback(configCallback)
+
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                /* Translation animations, when moving from DREAMING->LOCKSCREEN state */
+                launch {
+                    configurationBasedDimensions
+                        .flatMapLatest {
+                            transitionViewModel.dreamOverlayTranslationY(it.translationYPx)
+                        }
+                        .collect { px ->
+                            ComplicationLayoutParams.iteratePositions(
+                                { position: Int ->
+                                    setElementsTranslationYAtPosition(px, position)
+                                },
+                                POSITION_TOP or POSITION_BOTTOM
+                            )
+                        }
+                }
+
+                /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
+                launch {
+                    transitionViewModel.dreamOverlayAlpha.collect { alpha ->
+                        ComplicationLayoutParams.iteratePositions(
+                            { position: Int ->
+                                setElementsAlphaAtPosition(
+                                    alpha = alpha,
+                                    position = position,
+                                    fadingOut = true,
+                                )
+                            },
+                            POSITION_TOP or POSITION_BOTTOM
+                        )
+                    }
+                }
+            }
+
+            configController.removeCallback(configCallback)
+        }
+    }
+
     /** Starts the dream content and dream overlay entry animations. */
     @JvmOverloads
-    fun startEntryAnimations(view: View, animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
+    fun startEntryAnimations(animatorBuilder: () -> AnimatorSet = { AnimatorSet() }) {
         cancelAnimations()
 
         mAnimator =
@@ -113,73 +168,9 @@
     }
 
     /** Starts the dream content and dream overlay exit animations. */
-    @JvmOverloads
-    fun startExitAnimations(
-        view: View,
-        doneCallback: () -> Unit,
-        animatorBuilder: () -> AnimatorSet = { AnimatorSet() }
-    ) {
+    fun wakeUp(doneCallback: Runnable, executor: DelayableExecutor) {
         cancelAnimations()
-
-        mAnimator =
-            animatorBuilder().apply {
-                playTogether(
-                    blurAnimator(
-                        view = view,
-                        // Start the blurring wherever the entry animation ended, in
-                        // case it was cancelled early.
-                        fromBlurRadius = mCurrentBlurRadius,
-                        toBlurRadius = mDreamBlurRadius.toFloat(),
-                        durationMs = mDreamOutBlurDurationMs,
-                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
-                    ),
-                    translationYAnimator(
-                        from = 0f,
-                        to = mDreamOutTranslationYDistance.toFloat(),
-                        durationMs = mDreamOutTranslationYDurationMs,
-                        delayMs = mDreamOutTranslationYDelayBottomMs,
-                        positions = POSITION_BOTTOM,
-                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
-                    ),
-                    translationYAnimator(
-                        from = 0f,
-                        to = mDreamOutTranslationYDistance.toFloat(),
-                        durationMs = mDreamOutTranslationYDurationMs,
-                        delayMs = mDreamOutTranslationYDelayTopMs,
-                        positions = POSITION_TOP,
-                        interpolator = Interpolators.EMPHASIZED_ACCELERATE
-                    ),
-                    alphaAnimator(
-                        from =
-                            mCurrentAlphaAtPosition.getOrDefault(
-                                key = POSITION_BOTTOM,
-                                defaultValue = 1f
-                            ),
-                        to = 0f,
-                        durationMs = mDreamOutAlphaDurationMs,
-                        delayMs = mDreamOutAlphaDelayBottomMs,
-                        positions = POSITION_BOTTOM
-                    ),
-                    alphaAnimator(
-                        from =
-                            mCurrentAlphaAtPosition.getOrDefault(
-                                key = POSITION_TOP,
-                                defaultValue = 1f
-                            ),
-                        to = 0f,
-                        durationMs = mDreamOutAlphaDurationMs,
-                        delayMs = mDreamOutAlphaDelayTopMs,
-                        positions = POSITION_TOP
-                    )
-                )
-                doOnEnd {
-                    mAnimator = null
-                    mOverlayStateController.setExitAnimationsRunning(false)
-                    doneCallback()
-                }
-                start()
-            }
-        mOverlayStateController.setExitAnimationsRunning(true)
+        executor.executeDelayed(doneCallback, DREAM_ANIMATION_DURATION.inWholeMilliseconds)
     }
 
     /** Cancels the dream content and dream overlay animations, if they're currently running. */
@@ -288,4 +279,15 @@
             mStatusBarViewController.setTranslationY(translationY)
         }
     }
+
+    private fun loadFromResources(view: View): ConfigurationBasedDimensions {
+        return ConfigurationBasedDimensions(
+            translationYPx =
+                view.resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset),
+        )
+    }
+
+    private data class ConfigurationBasedDimensions(
+        val translationYPx: Int,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
new file mode 100644
index 0000000..d5ff8f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayCallbackController.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.dreams
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.CallbackController
+import javax.inject.Inject
+
+/** Dream overlay-related callback information */
+@SysUISingleton
+class DreamOverlayCallbackController @Inject constructor() :
+    CallbackController<DreamOverlayCallbackController.Callback> {
+
+    private val callbacks = mutableSetOf<DreamOverlayCallbackController.Callback>()
+
+    var isDreaming = false
+        private set
+
+    override fun addCallback(callback: DreamOverlayCallbackController.Callback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DreamOverlayCallbackController.Callback) {
+        callbacks.remove(callback)
+    }
+
+    fun onWakeUp() {
+        isDreaming = false
+        callbacks.forEach { it.onWakeUp() }
+    }
+
+    fun onStartDream() {
+        isDreaming = true
+        callbacks.forEach { it.onStartDream() }
+    }
+
+    interface Callback {
+        /** Dream overlay has ended */
+        fun onWakeUp()
+
+        /** Dream overlay has started */
+        fun onStartDream()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 9d7ad30..3106173 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -42,9 +42,9 @@
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.util.ViewController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Arrays;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -170,6 +170,7 @@
     protected void onInit() {
         mStatusBarViewController.init();
         mComplicationHostViewController.init();
+        mDreamOverlayAnimationsController.init(mView);
     }
 
     @Override
@@ -184,7 +185,7 @@
 
         // Start dream entry animations. Skip animations for low light clock.
         if (!mStateController.isLowLightActive()) {
-            mDreamOverlayAnimationsController.startEntryAnimations(mView);
+            mDreamOverlayAnimationsController.startEntryAnimations();
         }
     }
 
@@ -261,10 +262,8 @@
      * @param onAnimationEnd Callback to trigger once animations are finished.
      * @param callbackExecutor Executor to execute the callback on.
      */
-    public void wakeUp(@NonNull Runnable onAnimationEnd, @NonNull Executor callbackExecutor) {
-        mDreamOverlayAnimationsController.startExitAnimations(mView, () -> {
-            callbackExecutor.execute(onAnimationEnd);
-            return null;
-        });
+    public void wakeUp(@NonNull Runnable onAnimationEnd,
+            @NonNull DelayableExecutor callbackExecutor) {
+        mDreamOverlayAnimationsController.wakeUp(onAnimationEnd, callbackExecutor);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 16b4f99..fdc115b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -46,10 +46,10 @@
 import com.android.systemui.dreams.dagger.DreamOverlayComponent;
 import com.android.systemui.dreams.touch.DreamOverlayTouchMonitor;
 import com.android.systemui.touch.TouchInsetManager;
+import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -66,10 +66,11 @@
     // The Context is used to construct the hosting constraint layout and child overlay views.
     private final Context mContext;
     // The Executor ensures actions and ui updates happen on the same thread.
-    private final Executor mExecutor;
+    private final DelayableExecutor mExecutor;
     // A controller for the dream overlay container view (which contains both the status bar and the
     // content area).
     private DreamOverlayContainerViewController mDreamOverlayContainerViewController;
+    private final DreamOverlayCallbackController mDreamOverlayCallbackController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @Nullable
     private final ComponentName mLowLightDreamComponent;
@@ -137,8 +138,8 @@
     @Inject
     public DreamOverlayService(
             Context context,
-            @Main Executor executor,
             DreamOverlayLifecycleOwner lifecycleOwner,
+            @Main DelayableExecutor executor,
             WindowManager windowManager,
             ComplicationComponent.Factory complicationComponentFactory,
             com.android.systemui.dreams.dreamcomplication.dagger.ComplicationComponent.Factory
@@ -149,7 +150,8 @@
             UiEventLogger uiEventLogger,
             TouchInsetManager touchInsetManager,
             @Nullable @Named(LowLightDreamModule.LOW_LIGHT_DREAM_COMPONENT)
-                    ComponentName lowLightDreamComponent) {
+                    ComponentName lowLightDreamComponent,
+            DreamOverlayCallbackController dreamOverlayCallbackController) {
         mContext = context;
         mExecutor = executor;
         mWindowManager = windowManager;
@@ -158,6 +160,7 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
+        mDreamOverlayCallbackController = dreamOverlayCallbackController;
 
         final ViewModelStore viewModelStore = new ViewModelStore();
         final Complication.Host host =
@@ -226,6 +229,7 @@
                     dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent));
             mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START);
 
+            mDreamOverlayCallbackController.onStartDream();
             mStarted = true;
         });
     }
@@ -242,6 +246,7 @@
     public void onWakeUp(@NonNull Runnable onCompletedCallback) {
         mExecutor.execute(() -> {
             if (mDreamOverlayContainerViewController != null) {
+                mDreamOverlayCallbackController.onWakeUp();
                 mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
             }
         });
@@ -251,6 +256,7 @@
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
      * set by the {@link android.service.dreams.DreamService} on the dream Window.
+     *
      * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting
      *                     into the dream window.
      */
@@ -297,7 +303,11 @@
 
     private void resetCurrentDreamOverlayLocked() {
         if (mStarted && mWindow != null) {
-            mWindowManager.removeView(mWindow.getDecorView());
+            try {
+                mWindowManager.removeView(mWindow.getDecorView());
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Error removing decor view when resetting overlay", e);
+            }
         }
 
         mStateController.setOverlayActive(false);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4aa46d4..51eefd6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -58,22 +58,6 @@
             "dream_in_complications_translation_y";
     public static final String DREAM_IN_TRANSLATION_Y_DURATION =
             "dream_in_complications_translation_y_duration";
-    public static final String DREAM_OUT_TRANSLATION_Y_DISTANCE =
-            "dream_out_complications_translation_y";
-    public static final String DREAM_OUT_TRANSLATION_Y_DURATION =
-            "dream_out_complications_translation_y_duration";
-    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM =
-            "dream_out_complications_translation_y_delay_bottom";
-    public static final String DREAM_OUT_TRANSLATION_Y_DELAY_TOP =
-            "dream_out_complications_translation_y_delay_top";
-    public static final String DREAM_OUT_ALPHA_DURATION =
-            "dream_out_complications_alpha_duration";
-    public static final String DREAM_OUT_ALPHA_DELAY_BOTTOM =
-            "dream_out_complications_alpha_delay_bottom";
-    public static final String DREAM_OUT_ALPHA_DELAY_TOP =
-            "dream_out_complications_alpha_delay_top";
-    public static final String DREAM_OUT_BLUR_DURATION =
-            "dream_out_blur_duration";
 
     /** */
     @Provides
@@ -180,66 +164,6 @@
         return (long) resources.getInteger(R.integer.config_dreamOverlayInTranslationYDurationMs);
     }
 
-    /**
-     * Provides the number of pixels to translate complications when waking up from dream.
-     */
-    @Provides
-    @Named(DREAM_OUT_TRANSLATION_Y_DISTANCE)
-    @DreamOverlayComponent.DreamOverlayScope
-    static int providesDreamOutComplicationsTranslationY(@Main Resources resources) {
-        return resources.getDimensionPixelSize(R.dimen.dream_overlay_exit_y_offset);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_TRANSLATION_Y_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsTranslationYDuration(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDurationMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsTranslationYDelayBottom(@Main Resources resources) {
-        return (long) resources.getInteger(
-                R.integer.config_dreamOverlayOutTranslationYDelayBottomMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_TRANSLATION_Y_DELAY_TOP)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsTranslationYDelayTop(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutTranslationYDelayTopMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_ALPHA_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsAlphaDuration(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDurationMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_ALPHA_DELAY_BOTTOM)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsAlphaDelayBottom(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayBottomMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_ALPHA_DELAY_TOP)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutComplicationsAlphaDelayTop(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutAlphaDelayTopMs);
-    }
-
-    @Provides
-    @Named(DREAM_OUT_BLUR_DURATION)
-    @DreamOverlayComponent.DreamOverlayScope
-    static long providesDreamOutBlurDuration(@Main Resources resources) {
-        return (long) resources.getInteger(R.integer.config_dreamOverlayOutBlurDurationMs);
-    }
-
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
     static Lifecycle providesLifecycle(LifecycleOwner lifecycleOwner) {
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index c982131..276a290 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -51,6 +51,11 @@
         registerDumpable(name, module, DumpPriority.CRITICAL)
     }
 
+    /** See [registerNormalDumpable]. */
+    fun registerNormalDumpable(module: Dumpable) {
+        registerNormalDumpable(module::class.java.simpleName, module)
+    }
+
     /**
      * Registers a dumpable to be called during the NORMAL section of the bug report.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 25fa915..7b876d0 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -188,10 +188,6 @@
     // TODO(b/260619425): Tracking Bug
     @JvmField val MODERN_ALTERNATE_BOUNCER = unreleasedFlag(219, "modern_alternate_bouncer")
 
-    // TODO(b/262780002): Tracking Bug
-    @JvmField
-    val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
-
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
     @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
@@ -200,6 +196,15 @@
     // TODO(b/244313043): Tracking bug
     @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
 
+    // TODO(b/262780002): Tracking Bug
+    @JvmField
+    val REVAMPED_WALLPAPER_UI = unreleasedFlag(222, "revamped_wallpaper_ui", teamfood = false)
+
+    /** A different path for unocclusion transitions back to keyguard */
+    // TODO(b/262859270): Tracking Bug
+    @JvmField
+    val UNOCCLUSION_TRANSITION = unreleasedFlag(223, "unocclusion_transition", teamfood = false)
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
     @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
@@ -315,12 +320,18 @@
     // TODO(b/261734857): Tracking Bug
     @JvmField val UMO_TURBULENCE_NOISE = unreleasedFlag(909, "umo_turbulence_noise")
 
+    // TODO(b/263272731): Tracking Bug
+    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE =
+        unreleasedFlag(910, "media_ttt_receiver_success_ripple", teamfood = true)
+
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
 
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
+    val SHOW_LOWLIGHT_ON_DIRECT_BOOT = unreleasedFlag(1003, "show_lowlight_on_direct_boot")
+
     // 1100 - windowing
     @Keep
     @JvmField
@@ -447,11 +458,11 @@
     @JvmField val NOTE_TASKS = unreleasedFlag(1900, "keycode_flag")
 
     // 2000 - device controls
-    @Keep @JvmField val USE_APP_PANELS = unreleasedFlag(2000, "use_app_panels", teamfood = true)
+    @Keep @JvmField val USE_APP_PANELS = releasedFlag(2000, "use_app_panels", teamfood = true)
 
     @JvmField
     val APP_PANELS_ALL_APPS_ALLOWED =
-        unreleasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
+        releasedFlag(2001, "app_panels_all_apps_allowed", teamfood = true)
 
     // 2100 - Falsing Manager
     @JvmField val FALSING_FOR_LONG_TAPS = releasedFlag(2100, "falsing_for_long_taps")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9dd41f0..3beec36 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -33,6 +33,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
 import static com.android.systemui.DejankUtils.whitelistIpcs;
+import static com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.LOCKSCREEN_ANIMATION_DURATION_MS;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1232,8 +1233,7 @@
 
         mDreamOpenAnimationDuration = context.getResources().getInteger(
                 com.android.internal.R.integer.config_dreamOpenAnimationDuration);
-        mDreamCloseAnimationDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_dreamCloseAnimationDuration);
+        mDreamCloseAnimationDuration = (int) LOCKSCREEN_ANIMATION_DURATION_MS;
     }
 
     public void userActivity() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 148792b..a4fd087 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamOverlayCallbackController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -78,6 +79,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Is an activity showing over the keyguard? */
+    val isKeyguardOccluded: Flow<Boolean>
+
     /** Observable for the signal that keyguard is about to go away. */
     val isKeyguardGoingAway: Flow<Boolean>
 
@@ -104,6 +108,9 @@
      */
     val isDreaming: Flow<Boolean>
 
+    /** Observable for whether the device is dreaming with an overlay, see [DreamOverlayService] */
+    val isDreamingWithOverlay: Flow<Boolean>
+
     /**
      * Observable for the amount of doze we are currently in.
      *
@@ -176,6 +183,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val authController: AuthController,
+    private val dreamOverlayCallbackController: DreamOverlayCallbackController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -187,28 +195,55 @@
     private val _clockPosition = MutableStateFlow(Position(0, 0))
     override val clockPosition = _clockPosition.asStateFlow()
 
-    override val isKeyguardShowing: Flow<Boolean> = conflatedCallbackFlow {
-        val callback =
-            object : KeyguardStateController.Callback {
-                override fun onKeyguardShowingChanged() {
-                    trySendWithFailureLogging(
-                        keyguardStateController.isShowing,
-                        TAG,
-                        "updated isKeyguardShowing"
-                    )
-                }
+    override val isKeyguardShowing: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isShowing,
+                                TAG,
+                                "updated isKeyguardShowing"
+                            )
+                        }
+                    }
+
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isShowing,
+                    TAG,
+                    "initial isKeyguardShowing"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
             }
+            .distinctUntilChanged()
 
-        keyguardStateController.addCallback(callback)
-        // Adding the callback does not send an initial update.
-        trySendWithFailureLogging(
-            keyguardStateController.isShowing,
-            TAG,
-            "initial isKeyguardShowing"
-        )
+    override val isKeyguardOccluded: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : KeyguardStateController.Callback {
+                        override fun onKeyguardShowingChanged() {
+                            trySendWithFailureLogging(
+                                keyguardStateController.isOccluded,
+                                TAG,
+                                "updated isKeyguardOccluded"
+                            )
+                        }
+                    }
 
-        awaitClose { keyguardStateController.removeCallback(callback) }
-    }
+                keyguardStateController.addCallback(callback)
+                // Adding the callback does not send an initial update.
+                trySendWithFailureLogging(
+                    keyguardStateController.isOccluded,
+                    TAG,
+                    "initial isKeyguardOccluded"
+                )
+
+                awaitClose { keyguardStateController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
         val callback =
@@ -275,6 +310,28 @@
             }
             .distinctUntilChanged()
 
+    override val isDreamingWithOverlay: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DreamOverlayCallbackController.Callback {
+                        override fun onStartDream() {
+                            trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay")
+                        }
+                        override fun onWakeUp() {
+                            trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay")
+                        }
+                    }
+                dreamOverlayCallbackController.addCallback(callback)
+                trySendWithFailureLogging(
+                    dreamOverlayCallbackController.isDreaming,
+                    TAG,
+                    "initial isDreamingWithOverlay",
+                )
+
+                awaitClose { dreamOverlayCallbackController.removeCallback(callback) }
+            }
+            .distinctUntilChanged()
+
     override val isDreaming: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 5bb586e..343c2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -66,8 +66,8 @@
     }
 
     /**
-     * Begin a transition from one state to another. Will not start if another transition is in
-     * progress.
+     * Begin a transition from one state to another. Transitions are interruptible, and will issue a
+     * [TransitionStep] with state = [TransitionState.CANCELED] before beginning the next one.
      */
     fun startTransition(info: TransitionInfo): UUID?
 
@@ -131,6 +131,10 @@
     }
 
     override fun startTransition(info: TransitionInfo): UUID? {
+        if (lastStep.from == info.from && lastStep.to == info.to) {
+            Log.i(TAG, "Duplicate call to start the transition, rejecting: $info")
+            return null
+        }
         if (lastStep.transitionState != TransitionState.FINISHED) {
             Log.i(TAG, "Transition still active: $lastStep, canceling")
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
deleted file mode 100644
index dad166f..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
+++ /dev/null
@@ -1,75 +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.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class AodToGoneTransitionInteractor
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodToGoneTransitionInteractor::class.simpleName!!) {
-
-    override fun start() {
-        scope.launch {
-            keyguardInteractor.biometricUnlockState
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
-                .collect { pair ->
-                    val (biometricUnlockState, keyguardState) = pair
-                    if (
-                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                KeyguardState.AOD,
-                                KeyguardState.GONE,
-                                getAnimator(),
-                            )
-                        )
-                    }
-                }
-        }
-    }
-
-    private fun getAnimator(): ValueAnimator {
-        return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
-        }
-    }
-
-    companion object {
-        private const val TRANSITION_DURATION_MS = 500L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index f3d2905..c2d139c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.Companion.isWakeAndUnlock
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
@@ -30,33 +31,33 @@
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromAodTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromAodTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForTransitionToAodFromLockscreen()
-        listenForTransitionToLockscreenFromDozeStates()
+        listenForAodToLockscreen()
+        listenForAodToGone()
     }
 
-    private fun listenForTransitionToAodFromLockscreen() {
+    private fun listenForAodToLockscreen() {
         scope.launch {
             keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+                .dozeTransitionTo(DozeStateModel.FINISH)
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
                     val (dozeToAod, lastStartedStep) = pair
-                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    if (lastStartedStep.to == KeyguardState.AOD) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                KeyguardState.LOCKSCREEN,
                                 KeyguardState.AOD,
+                                KeyguardState.LOCKSCREEN,
                                 getAnimator(),
                             )
                         )
@@ -65,20 +66,20 @@
         }
     }
 
-    private fun listenForTransitionToLockscreenFromDozeStates() {
-        val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+    private fun listenForAodToGone() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.FINISH)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (canGoToLockscreen.contains(lastStartedStep.to)) {
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.AOD && isWakeAndUnlock(biometricUnlockState)
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                lastStartedStep.to,
-                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                KeyguardState.GONE,
                                 getAnimator(),
                             )
                         )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
similarity index 60%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
index 056c44d..0e9c447 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerToGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromBouncerTransitionInteractor.kt
@@ -23,16 +23,18 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class BouncerToGoneTransitionInteractor
+class FromBouncerTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
@@ -40,15 +42,54 @@
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(BouncerToGoneTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromBouncerTransitionInteractor::class.simpleName!!) {
 
     private var transitionId: UUID? = null
 
     override fun start() {
-        listenForKeyguardGoingAway()
+        listenForBouncerToGone()
+        listenForBouncerToLockscreenOrAod()
     }
 
-    private fun listenForKeyguardGoingAway() {
+    private fun listenForBouncerToLockscreenOrAod() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(
+                    combine(
+                        keyguardInteractor.wakefulnessModel,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
+                    if (
+                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
+                    ) {
+                        val to =
+                            if (
+                                wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
+                                    wakefulnessState.state == WakefulnessState.ASLEEP
+                            ) {
+                                KeyguardState.AOD
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = to,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForBouncerToGone() {
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 95d9602..fd2d271 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -21,36 +21,46 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class LockscreenGoneTransitionInteractor
+class FromDozingTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
-) : TransitionInteractor(LockscreenGoneTransitionInteractor::class.simpleName!!) {
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(FromDozingTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForDozingToLockscreen()
+    }
+
+    private fun listenForDozingToLockscreen() {
         scope.launch {
-            keyguardInteractor.isKeyguardGoingAway
+            keyguardInteractor.dozeTransitionModel
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
-                    val (isKeyguardGoingAway, lastStartedStep) = pair
-                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    val (dozeTransitionModel, lastStartedTransition) = pair
+                    if (
+                        isDozeOff(dozeTransitionModel.to) &&
+                            lastStartedTransition.to == KeyguardState.DOZING
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
+                                KeyguardState.DOZING,
                                 KeyguardState.LOCKSCREEN,
-                                KeyguardState.GONE,
                                 getAnimator(),
                             )
                         )
@@ -59,14 +69,14 @@
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            setDuration(duration.inWholeMilliseconds)
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 10L
+        private val DEFAULT_DURATION = 500.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
similarity index 75%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index b73ce9e..3b09ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -28,81 +28,48 @@
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class DreamingTransitionInteractor
+class FromDreamingTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(DreamingTransitionInteractor::class.simpleName!!) {
-
-    private val canDreamFrom =
-        setOf(KeyguardState.LOCKSCREEN, KeyguardState.GONE, KeyguardState.DOZING)
+) : TransitionInteractor(FromDreamingTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForEntryToDreaming()
         listenForDreamingToLockscreen()
+        listenForDreamingToOccluded()
         listenForDreamingToGone()
         listenForDreamingToDozing()
     }
 
-    private fun listenForEntryToDreaming() {
-        scope.launch {
-            keyguardInteractor.isDreaming
-                .sample(
-                    combine(
-                        keyguardInteractor.dozeTransitionModel,
-                        keyguardTransitionInteractor.finishedKeyguardState,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (isDreaming, dozeTransitionModel, keyguardState) = triple
-                    // Dozing/AOD and dreaming have overlapping events. If the state remains in
-                    // FINISH, it means that doze mode is not running and DREAMING is ok to
-                    // commence.
-                    if (
-                        isDozeOff(dozeTransitionModel.to) &&
-                            isDreaming &&
-                            canDreamFrom.contains(keyguardState)
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                name,
-                                keyguardState,
-                                KeyguardState.DREAMING,
-                                getAnimator(),
-                            )
-                        )
-                    }
-                }
-        }
-    }
-
     private fun listenForDreamingToLockscreen() {
         scope.launch {
-            keyguardInteractor.isDreaming
+            // Using isDreamingWithOverlay provides an optimized path to LOCKSCREEN state, which
+            // otherwise would have gone through OCCLUDED first
+            keyguardInteractor.isDreamingWithOverlay
                 .sample(
                     combine(
                         keyguardInteractor.dozeTransitionModel,
                         keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair,
+                        ::Pair
                     ),
                     ::toTriple
                 )
                 .collect { triple ->
                     val (isDreaming, dozeTransitionModel, lastStartedTransition) = triple
                     if (
-                        isDozeOff(dozeTransitionModel.to) &&
-                            !isDreaming &&
+                        !isDreaming &&
+                            isDozeOff(dozeTransitionModel.to) &&
                             lastStartedTransition.to == KeyguardState.DREAMING
                     ) {
                         keyguardTransitionRepository.startTransition(
@@ -110,6 +77,42 @@
                                 name,
                                 KeyguardState.DREAMING,
                                 KeyguardState.LOCKSCREEN,
+                                getAnimator(TO_LOCKSCREEN_DURATION),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForDreamingToOccluded() {
+        scope.launch {
+            keyguardInteractor.isDreaming
+                .sample(
+                    combine(
+                        keyguardInteractor.isKeyguardOccluded,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair,
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isDreaming, isOccluded, lastStartedTransition) = triple
+                    if (
+                        isOccluded &&
+                            !isDreaming &&
+                            (lastStartedTransition.to == KeyguardState.DREAMING ||
+                                lastStartedTransition.to == KeyguardState.LOCKSCREEN)
+                    ) {
+                        // At the moment, checking for LOCKSCREEN state above provides a corrective
+                        // action. There's no great signal to determine when the dream is ending
+                        // and a transition to OCCLUDED is beginning directly. For now, the solution
+                        // is DREAMING->LOCKSCREEN->OCCLUDED
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                lastStartedTransition.to,
+                                KeyguardState.OCCLUDED,
                                 getAnimator(),
                             )
                         )
@@ -167,14 +170,15 @@
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            setDuration(duration.inWholeMilliseconds)
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 500L
+        private val DEFAULT_DURATION = 500.milliseconds
+        val TO_LOCKSCREEN_DURATION = 1183.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
similarity index 72%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index a50e759..553fafe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -30,19 +30,44 @@
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class GoneAodTransitionInteractor
+class FromGoneTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(GoneAodTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromGoneTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
+        listenForGoneToAod()
+        listenForGoneToDreaming()
+    }
+
+    private fun listenForGoneToDreaming() {
+        scope.launch {
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
+                .collect { pair ->
+                    val (isAbleToDream, keyguardState) = pair
+                    if (isAbleToDream && keyguardState == KeyguardState.GONE) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.GONE,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForGoneToAod() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
-                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
                     val (wakefulnessState, keyguardState) = pair
                     if (
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
new file mode 100644
index 0000000..326acc9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -0,0 +1,236 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.shade.data.repository.ShadeRepository
+import com.android.systemui.util.kotlin.sample
+import java.util.UUID
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class FromLockscreenTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val shadeRepository: ShadeRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor(FromLockscreenTransitionInteractor::class.simpleName!!) {
+
+    private var transitionId: UUID? = null
+
+    override fun start() {
+        listenForLockscreenToGone()
+        listenForLockscreenToOccluded()
+        listenForLockscreenToAod()
+        listenForLockscreenToBouncer()
+        listenForLockscreenToDreaming()
+        listenForLockscreenToBouncerDragging()
+    }
+
+    private fun listenForLockscreenToDreaming() {
+        scope.launch {
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isAbleToDream, lastStartedTransition) = pair
+                    if (isAbleToDream && lastStartedTransition.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.DREAMING,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToBouncer() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isBouncerShowing, lastStartedTransitionStep) = pair
+                    if (
+                        isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.LOCKSCREEN,
+                                to = KeyguardState.BOUNCER,
+                                animator = getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+    private fun listenForLockscreenToBouncerDragging() {
+        scope.launch {
+            shadeRepository.shadeModel
+                .sample(
+                    combine(
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.statusBarState,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (shadeModel, keyguardState, statusBarState) = triple
+
+                    val id = transitionId
+                    if (id != null) {
+                        // An existing `id` means a transition is started, and calls to
+                        // `updateTransition` will control it until FINISHED
+                        keyguardTransitionRepository.updateTransition(
+                            id,
+                            1f - shadeModel.expansionAmount,
+                            if (
+                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
+                            ) {
+                                transitionId = null
+                                TransitionState.FINISHED
+                            } else {
+                                TransitionState.RUNNING
+                            }
+                        )
+                    } else {
+                        // TODO (b/251849525): Remove statusbarstate check when that state is
+                        // integrated into KeyguardTransitionRepository
+                        if (
+                            keyguardState == KeyguardState.LOCKSCREEN &&
+                                shadeModel.isUserDragging &&
+                                statusBarState == KEYGUARD
+                        ) {
+                            transitionId =
+                                keyguardTransitionRepository.startTransition(
+                                    TransitionInfo(
+                                        ownerName = name,
+                                        from = KeyguardState.LOCKSCREEN,
+                                        to = KeyguardState.BOUNCER,
+                                        animator = null,
+                                    )
+                                )
+                        }
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGone() {
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToOccluded() {
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded
+                .sample(
+                    combine(
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.isDreaming,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { triple ->
+                    val (isOccluded, keyguardState, isDreaming) = triple
+                    // Occlusion signals come from the framework, and should interrupt any
+                    // existing transition
+                    if (isOccluded && !isDreaming) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                keyguardState,
+                                KeyguardState.OCCLUDED,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToAod() {
+        scope.launch {
+            keyguardInteractor
+                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
+                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (dozeToAod, lastStartedStep) = pair
+                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
copy to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index f3d2905..937e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -21,42 +21,43 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.launch
 
 @SysUISingleton
-class AodLockscreenTransitionInteractor
+class FromOccludedTransitionInteractor
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) : TransitionInteractor(AodLockscreenTransitionInteractor::class.simpleName!!) {
+) : TransitionInteractor(FromOccludedTransitionInteractor::class.simpleName!!) {
 
     override fun start() {
-        listenForTransitionToAodFromLockscreen()
-        listenForTransitionToLockscreenFromDozeStates()
+        listenForOccludedToLockscreen()
+        listenForOccludedToDreaming()
     }
 
-    private fun listenForTransitionToAodFromLockscreen() {
+    private fun listenForOccludedToDreaming() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.DOZE_AOD)
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
+            keyguardInteractor.isAbleToDream
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                    val (isAbleToDream, keyguardState) = pair
+                    if (isAbleToDream && keyguardState == KeyguardState.OCCLUDED) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                KeyguardState.LOCKSCREEN,
-                                KeyguardState.AOD,
+                                KeyguardState.OCCLUDED,
+                                KeyguardState.DREAMING,
                                 getAnimator(),
                             )
                         )
@@ -65,19 +66,19 @@
         }
     }
 
-    private fun listenForTransitionToLockscreenFromDozeStates() {
-        val canGoToLockscreen = setOf(KeyguardState.AOD, KeyguardState.DOZING)
+    private fun listenForOccludedToLockscreen() {
         scope.launch {
-            keyguardInteractor
-                .dozeTransitionTo(DozeStateModel.FINISH)
+            keyguardInteractor.isKeyguardOccluded
                 .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
                 .collect { pair ->
-                    val (dozeToAod, lastStartedStep) = pair
-                    if (canGoToLockscreen.contains(lastStartedStep.to)) {
+                    val (isOccluded, lastStartedKeyguardState) = pair
+                    // Occlusion signals come from the framework, and should interrupt any
+                    // existing transition
+                    if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
-                                lastStartedStep.to,
+                                KeyguardState.OCCLUDED,
                                 KeyguardState.LOCKSCREEN,
                                 getAnimator(),
                             )
@@ -87,14 +88,15 @@
         }
     }
 
-    private fun getAnimator(): ValueAnimator {
+    private fun getAnimator(duration: Duration = DEFAULT_DURATION): ValueAnimator {
         return ValueAnimator().apply {
             setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
+            setDuration(duration.inWholeMilliseconds)
         }
     }
 
     companion object {
-        private const val TRANSITION_DURATION_MS = 500L
+        private val DEFAULT_DURATION = 500.milliseconds
+        val TO_LOCKSCREEN_DURATION = 1183.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6912e1d..402c179 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -22,12 +22,16 @@
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.merge
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -52,8 +56,27 @@
      * but not vice-versa.
      */
     val isDreaming: Flow<Boolean> = repository.isDreaming
+    /** Whether the system is dreaming with an overlay active */
+    val isDreamingWithOverlay: Flow<Boolean> = repository.isDreamingWithOverlay
+
+    /**
+     * Dozing and dreaming have overlapping events. If the doze state remains in FINISH, it means
+     * that doze mode is not running and DREAMING is ok to commence.
+     */
+    val isAbleToDream: Flow<Boolean> =
+        merge(isDreaming, isDreamingWithOverlay)
+            .sample(
+                dozeTransitionModel,
+                { isDreaming, dozeTransitionModel ->
+                    isDreaming && isDozeOff(dozeTransitionModel.to)
+                }
+            )
+            .distinctUntilChanged()
+
     /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the keyguard is occluded (covered by an activity). */
+    val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
     /** Whether the bouncer is showing or not. */
@@ -74,8 +97,8 @@
     /** The approximate location on the screen of the face unlock sensor, if one is available. */
     val faceSensorLocation: Flow<Point?> = repository.faceSensorLocation
 
-    fun dozeTransitionTo(state: DozeStateModel): Flow<DozeTransitionModel> {
-        return dozeTransitionModel.filter { it.to == state }
+    fun dozeTransitionTo(vararg states: DozeStateModel): Flow<DozeTransitionModel> {
+        return dozeTransitionModel.filter { states.contains(it.to) }
     }
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index bb8b79a..fbed446 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -37,13 +37,13 @@
             // exhaustive
             val ret =
                 when (it) {
-                    is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is BouncerToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
-                    is DreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromAodTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 54a4f49..3b9d6f5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,12 +19,15 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.AnimationParams
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
+import kotlin.time.Duration
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -43,6 +46,10 @@
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
+    /** DREAMING->LOCKSCREEN transition information. */
+    val dreamingToLockscreenTransition: Flow<TransitionStep> =
+        repository.transition(DREAMING, LOCKSCREEN)
+
     /** (any)->AOD transition information */
     val anyStateToAodTransition: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.to == KeyguardState.AOD }
@@ -72,4 +79,21 @@
     /* The last completed [KeyguardState] transition */
     val finishedKeyguardState: Flow<KeyguardState> =
         finishedKeyguardTransitionStep.map { step -> step.to }
+
+    /**
+     * Transitions will occur over a [totalDuration] with [TransitionStep]s being emitted in the
+     * range of [0, 1]. View animations should begin and end within a subset of this range. This
+     * function maps the [startTime] and [duration] into [0, 1], when this subset is valid.
+     */
+    fun transitionStepAnimation(
+        flow: Flow<TransitionStep>,
+        params: AnimationParams,
+        totalDuration: Duration,
+    ): Flow<Float> {
+        val start = (params.startTime / totalDuration).toFloat()
+        val chunks = (totalDuration / params.duration).toFloat()
+        return flow
+            .map { step -> (step.value - start) * chunks }
+            .filter { value -> value >= 0f && value <= 1f }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 6e25200..a59c407 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -89,6 +89,7 @@
                 KeyguardState.BOUNCER -> true
                 KeyguardState.LOCKSCREEN -> true
                 KeyguardState.GONE -> true
+                KeyguardState.OCCLUDED -> true
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
deleted file mode 100644
index 5cb7d70..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ /dev/null
@@ -1,170 +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.keyguard.domain.interactor
-
-import android.animation.ValueAnimator
-import com.android.systemui.animation.Interpolators
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
-import com.android.systemui.keyguard.shared.model.TransitionInfo
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.WakefulnessState
-import com.android.systemui.shade.data.repository.ShadeRepository
-import com.android.systemui.util.kotlin.sample
-import java.util.UUID
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-@SysUISingleton
-class LockscreenBouncerTransitionInteractor
-@Inject
-constructor(
-    @Application private val scope: CoroutineScope,
-    private val keyguardInteractor: KeyguardInteractor,
-    private val shadeRepository: ShadeRepository,
-    private val keyguardTransitionRepository: KeyguardTransitionRepository,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
-) : TransitionInteractor(LockscreenBouncerTransitionInteractor::class.simpleName!!) {
-
-    private var transitionId: UUID? = null
-
-    override fun start() {
-        listenForDraggingUpToBouncer()
-        listenForBouncer()
-    }
-
-    private fun listenForBouncer() {
-        scope.launch {
-            keyguardInteractor.isBouncerShowing
-                .sample(
-                    combine(
-                        keyguardInteractor.wakefulnessModel,
-                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (isBouncerShowing, wakefulnessState, lastStartedTransitionStep) = triple
-                    if (
-                        !isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.BOUNCER
-                    ) {
-                        val to =
-                            if (
-                                wakefulnessState.state == WakefulnessState.STARTING_TO_SLEEP ||
-                                    wakefulnessState.state == WakefulnessState.ASLEEP
-                            ) {
-                                KeyguardState.AOD
-                            } else {
-                                KeyguardState.LOCKSCREEN
-                            }
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.BOUNCER,
-                                to = to,
-                                animator = getAnimator(),
-                            )
-                        )
-                    } else if (
-                        isBouncerShowing && lastStartedTransitionStep.to == KeyguardState.LOCKSCREEN
-                    ) {
-                        keyguardTransitionRepository.startTransition(
-                            TransitionInfo(
-                                ownerName = name,
-                                from = KeyguardState.LOCKSCREEN,
-                                to = KeyguardState.BOUNCER,
-                                animator = getAnimator(),
-                            )
-                        )
-                    }
-                    Unit
-                }
-        }
-    }
-
-    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
-    private fun listenForDraggingUpToBouncer() {
-        scope.launch {
-            shadeRepository.shadeModel
-                .sample(
-                    combine(
-                        keyguardTransitionInteractor.finishedKeyguardState,
-                        keyguardInteractor.statusBarState,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect { triple ->
-                    val (shadeModel, keyguardState, statusBarState) = triple
-
-                    val id = transitionId
-                    if (id != null) {
-                        // An existing `id` means a transition is started, and calls to
-                        // `updateTransition` will control it until FINISHED
-                        keyguardTransitionRepository.updateTransition(
-                            id,
-                            1f - shadeModel.expansionAmount,
-                            if (
-                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
-                            ) {
-                                transitionId = null
-                                TransitionState.FINISHED
-                            } else {
-                                TransitionState.RUNNING
-                            }
-                        )
-                    } else {
-                        // TODO (b/251849525): Remove statusbarstate check when that state is
-                        // integrated into KeyguardTransitionRepository
-                        if (
-                            keyguardState == KeyguardState.LOCKSCREEN &&
-                                shadeModel.isUserDragging &&
-                                statusBarState == KEYGUARD
-                        ) {
-                            transitionId =
-                                keyguardTransitionRepository.startTransition(
-                                    TransitionInfo(
-                                        ownerName = name,
-                                        from = KeyguardState.LOCKSCREEN,
-                                        to = KeyguardState.BOUNCER,
-                                        animator = null,
-                                    )
-                                )
-                        }
-                    }
-                }
-        }
-    }
-
-    private fun getAnimator(): ValueAnimator {
-        return ValueAnimator().apply {
-            setInterpolator(Interpolators.LINEAR)
-            setDuration(TRANSITION_DURATION_MS)
-        }
-    }
-
-    companion object {
-        private const val TRANSITION_DURATION_MS = 300L
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 5f63ae7..81fa233 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -32,25 +32,25 @@
 
     @Binds
     @IntoSet
-    abstract fun lockscreenBouncer(
-        impl: LockscreenBouncerTransitionInteractor
-    ): TransitionInteractor
+    abstract fun fromBouncer(impl: FromBouncerTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+    abstract fun fromLockscreen(impl: FromLockscreenTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+    @Binds @IntoSet abstract fun fromAod(impl: FromAodTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+    @Binds @IntoSet abstract fun fromGone(impl: FromGoneTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun bouncerGone(impl: BouncerToGoneTransitionInteractor): TransitionInteractor
+    abstract fun fromDreaming(impl: FromDreamingTransitionInteractor): TransitionInteractor
 
     @Binds
     @IntoSet
-    abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
+    abstract fun fromOccluded(impl: FromOccludedTransitionInteractor): TransitionInteractor
 
-    @Binds @IntoSet abstract fun dreaming(impl: DreamingTransitionInteractor): TransitionInteractor
+    @Binds
+    @IntoSet
+    abstract fun fromDozing(impl: FromDozingTransitionInteractor): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 08ad3d5..4d24c14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -31,4 +31,6 @@
     abstract fun start()
 
     fun <A, B, C> toTriple(a: A, bc: Pair<B, C>) = Triple(a, bc.first, bc.second)
+
+    fun <A, B, C> toTriple(ab: Pair<A, B>, c: C) = Triple(ab.first, ab.second, c)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
new file mode 100644
index 0000000..67733e9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/AnimationParams.kt
@@ -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
+ */
+package com.android.systemui.keyguard.shared.model
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+/** Animation parameters */
+data class AnimationParams(
+    val startTime: Duration = 0.milliseconds,
+    val duration: Duration,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index dd908c4..c757986 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -57,4 +57,8 @@
      * with SWIPE security method or face unlock without bypass.
      */
     GONE,
+    /*
+     * An activity is displaying over the keyguard.
+     */
+    OCCLUDED,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index ae8edfe..b19795c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -18,6 +18,7 @@
 
 import android.annotation.SuppressLint
 import android.graphics.drawable.Animatable2
+import android.os.VibrationEffect
 import android.util.Size
 import android.util.TypedValue
 import android.view.MotionEvent
@@ -43,8 +44,11 @@
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.kotlin.pairwise
 import kotlin.math.pow
 import kotlin.math.sqrt
+import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.combine
@@ -93,6 +97,7 @@
         view: ViewGroup,
         viewModel: KeyguardBottomAreaViewModel,
         falsingManager: FalsingManager?,
+        vibratorHelper: VibratorHelper?,
         messageDisplayer: (Int) -> Unit,
     ): Binding {
         val indicationArea: View = view.requireViewById(R.id.keyguard_indication_area)
@@ -118,22 +123,48 @@
                             viewModel = buttonModel,
                             falsingManager = falsingManager,
                             messageDisplayer = messageDisplayer,
+                            vibratorHelper = vibratorHelper,
                         )
                     }
                 }
 
                 launch {
+                    viewModel.startButton
+                        .map { it.isActivated }
+                        .pairwise()
+                        .collect { (prev, next) ->
+                            when {
+                                !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+                                prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+                            }
+                        }
+                }
+
+                launch {
                     viewModel.endButton.collect { buttonModel ->
                         updateButton(
                             view = endButton,
                             viewModel = buttonModel,
                             falsingManager = falsingManager,
                             messageDisplayer = messageDisplayer,
+                            vibratorHelper = vibratorHelper,
                         )
                     }
                 }
 
                 launch {
+                    viewModel.endButton
+                        .map { it.isActivated }
+                        .pairwise()
+                        .collect { (prev, next) ->
+                            when {
+                                !prev && next -> vibratorHelper?.vibrate(Vibrations.Activated)
+                                prev && !next -> vibratorHelper?.vibrate(Vibrations.Deactivated)
+                            }
+                        }
+                }
+
+                launch {
                     viewModel.isOverlayContainerVisible.collect { isVisible ->
                         overlayContainer.visibility =
                             if (isVisible) {
@@ -239,6 +270,7 @@
         viewModel: KeyguardQuickAffordanceViewModel,
         falsingManager: FalsingManager?,
         messageDisplayer: (Int) -> Unit,
+        vibratorHelper: VibratorHelper?,
     ) {
         if (!viewModel.isVisible) {
             view.isVisible = false
@@ -312,7 +344,9 @@
         view.isClickable = viewModel.isClickable
         if (viewModel.isClickable) {
             if (viewModel.useLongPress) {
-                view.setOnTouchListener(OnTouchListener(view, viewModel, messageDisplayer))
+                view.setOnTouchListener(
+                    OnTouchListener(view, viewModel, messageDisplayer, vibratorHelper)
+                )
             } else {
                 view.setOnClickListener(OnClickListener(viewModel, checkNotNull(falsingManager)))
             }
@@ -328,6 +362,7 @@
         private val view: View,
         private val viewModel: KeyguardQuickAffordanceViewModel,
         private val messageDisplayer: (Int) -> Unit,
+        private val vibratorHelper: VibratorHelper?,
     ) : View.OnTouchListener {
 
         private val longPressDurationMs = ViewConfiguration.getLongPressTimeout().toLong()
@@ -376,25 +411,38 @@
                     true
                 }
                 MotionEvent.ACTION_UP -> {
-                    if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
-                        messageDisplayer.invoke(R.string.keyguard_affordance_press_too_short)
-                        val shakeAnimator =
-                            ObjectAnimator.ofFloat(
-                                view,
-                                "translationX",
-                                0f,
-                                view.context.resources
-                                    .getDimensionPixelSize(
-                                        R.dimen.keyguard_affordance_shake_amplitude
+                    cancel(
+                        onAnimationEnd =
+                            if (System.currentTimeMillis() - downTimestamp < longPressDurationMs) {
+                                Runnable {
+                                    messageDisplayer.invoke(
+                                        R.string.keyguard_affordance_press_too_short
                                     )
-                                    .toFloat(),
-                                0f,
-                            )
-                        shakeAnimator.duration = 300
-                        shakeAnimator.interpolator = CycleInterpolator(5f)
-                        shakeAnimator.start()
-                    }
-                    cancel()
+                                    val amplitude =
+                                        view.context.resources
+                                            .getDimensionPixelSize(
+                                                R.dimen.keyguard_affordance_shake_amplitude
+                                            )
+                                            .toFloat()
+                                    val shakeAnimator =
+                                        ObjectAnimator.ofFloat(
+                                            view,
+                                            "translationX",
+                                            -amplitude / 2,
+                                            amplitude / 2,
+                                        )
+                                    shakeAnimator.duration =
+                                        ShakeAnimationDuration.inWholeMilliseconds
+                                    shakeAnimator.interpolator =
+                                        CycleInterpolator(ShakeAnimationCycles)
+                                    shakeAnimator.start()
+
+                                    vibratorHelper?.vibrate(Vibrations.Shake)
+                                }
+                            } else {
+                                null
+                            }
+                    )
                     true
                 }
                 MotionEvent.ACTION_CANCEL -> {
@@ -405,11 +453,11 @@
             }
         }
 
-        private fun cancel() {
+        private fun cancel(onAnimationEnd: Runnable? = null) {
             downTimestamp = 0L
             longPressAnimator?.cancel()
             longPressAnimator = null
-            view.animate().scaleX(1f).scaleY(1f)
+            view.animate().scaleX(1f).scaleY(1f).withEndAction(onAnimationEnd)
         }
 
         companion object {
@@ -461,4 +509,58 @@
         val indicationTextSizePx: Int,
         val buttonSizePx: Size,
     )
+
+    private val ShakeAnimationDuration = 300.milliseconds
+    private val ShakeAnimationCycles = 5f
+
+    object Vibrations {
+
+        private const val SmallVibrationScale = 0.3f
+        private const val BigVibrationScale = 0.6f
+
+        val Shake =
+            VibrationEffect.startComposition()
+                .apply {
+                    val vibrationDelayMs =
+                        (ShakeAnimationDuration.inWholeMilliseconds / (ShakeAnimationCycles * 2))
+                            .toInt()
+                    val vibrationCount = ShakeAnimationCycles.toInt() * 2
+                    repeat(vibrationCount) {
+                        addPrimitive(
+                            VibrationEffect.Composition.PRIMITIVE_TICK,
+                            SmallVibrationScale,
+                            vibrationDelayMs,
+                        )
+                    }
+                }
+                .compose()
+
+        val Activated =
+            VibrationEffect.startComposition()
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_TICK,
+                    BigVibrationScale,
+                    0,
+                )
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_QUICK_RISE,
+                    0.1f,
+                    0,
+                )
+                .compose()
+
+        val Deactivated =
+            VibrationEffect.startComposition()
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_TICK,
+                    BigVibrationScale,
+                    0,
+                )
+                .addPrimitive(
+                    VibrationEffect.Composition.PRIMITIVE_QUICK_FALL,
+                    0.1f,
+                    0,
+                )
+                .compose()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 3d5985c5..f772b17 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -105,6 +105,14 @@
                     }
 
                     launch {
+                        viewModel.showWithFullExpansion.collect { model ->
+                            hostViewController.resetSecurityContainer()
+                            hostViewController.showPromptReason(model.promptReason)
+                            hostViewController.onResume()
+                        }
+                    }
+
+                    launch {
                         viewModel.hide.collect {
                             hostViewController.cancelDismissAction()
                             hostViewController.cleanUp()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
new file mode 100644
index 0000000..e164f5d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/**
+ * Breaks down DREAMING->LOCKSCREEN transition into discrete steps for corresponding views to
+ * consume.
+ */
+@SysUISingleton
+class DreamingToLockscreenTransitionViewModel
+@Inject
+constructor(
+    private val interactor: KeyguardTransitionInteractor,
+) {
+
+    /** Dream overlay y-translation on exit */
+    fun dreamOverlayTranslationY(translatePx: Int): Flow<Float> {
+        return flowForAnimation(DREAM_OVERLAY_TRANSLATION_Y).map { value ->
+            EMPHASIZED_ACCELERATE.getInterpolation(value) * translatePx
+        }
+    }
+    /** Dream overlay views alpha - fade out */
+    val dreamOverlayAlpha: Flow<Float> = flowForAnimation(DREAM_OVERLAY_ALPHA).map { 1f - it }
+
+    /** Lockscreen views y-translation */
+    fun lockscreenTranslationY(translatePx: Int): Flow<Float> {
+        return flowForAnimation(LOCKSCREEN_TRANSLATION_Y).map { value ->
+            -translatePx + (EMPHASIZED_DECELERATE.getInterpolation(value) * translatePx)
+        }
+    }
+
+    /** Lockscreen views alpha */
+    val lockscreenAlpha: Flow<Float> = flowForAnimation(LOCKSCREEN_ALPHA)
+
+    private fun flowForAnimation(params: AnimationParams): Flow<Float> {
+        return interactor.transitionStepAnimation(
+            interactor.dreamingToLockscreenTransition,
+            params,
+            totalDuration = TO_LOCKSCREEN_DURATION
+        )
+    }
+
+    companion object {
+        /* Length of time before ending the dream activity, in order to start unoccluding */
+        val DREAM_ANIMATION_DURATION = 250.milliseconds
+        @JvmField
+        val LOCKSCREEN_ANIMATION_DURATION_MS =
+            (TO_LOCKSCREEN_DURATION - DREAM_ANIMATION_DURATION).inWholeMilliseconds
+
+        val DREAM_OVERLAY_TRANSLATION_Y = AnimationParams(duration = 600.milliseconds)
+        val DREAM_OVERLAY_ALPHA = AnimationParams(duration = 250.milliseconds)
+        val LOCKSCREEN_TRANSLATION_Y = AnimationParams(duration = TO_LOCKSCREEN_DURATION)
+        val LOCKSCREEN_ALPHA =
+            AnimationParams(startTime = 233.milliseconds, duration = 250.milliseconds)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 737c35d..e5d4e49 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -22,8 +22,10 @@
 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
+import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 
 /** Models UI state for the lock screen bouncer; handles user input. */
@@ -42,6 +44,10 @@
     /** Observe whether bouncer is showing. */
     val show: Flow<KeyguardBouncerModel> = interactor.show
 
+    /** Observe visible expansion when bouncer is showing. */
+    val showWithFullExpansion: Flow<KeyguardBouncerModel> =
+        interactor.show.filter { it.expansionAmount == EXPANSION_VISIBLE }
+
     /** Observe whether bouncer is hiding. */
     val hide: Flow<Unit> = interactor.hide
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index ab93b29..d6f941d 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -58,10 +58,10 @@
 
     // Keep track of the key used for the session tokens. This information is used to know when to
     // dispatch a removed event so that a media object for a local session will be removed.
-    private val keyedTokens: MutableMap<String, MutableSet<MediaSession.Token>> = mutableMapOf()
+    private val keyedTokens: MutableMap<String, MutableSet<TokenId>> = mutableMapOf()
 
     // Keep track of which media session tokens have associated notifications.
-    private val tokensWithNotifications: MutableSet<MediaSession.Token> = mutableSetOf()
+    private val tokensWithNotifications: MutableSet<TokenId> = mutableSetOf()
 
     private val sessionListener =
         object : MediaSessionManager.OnActiveSessionsChangedListener {
@@ -101,15 +101,15 @@
         isSsReactivated: Boolean
     ) {
         backgroundExecutor.execute {
-            data.token?.let { tokensWithNotifications.add(it) }
+            data.token?.let { tokensWithNotifications.add(TokenId(it)) }
             val isMigration = oldKey != null && key != oldKey
             if (isMigration) {
                 keyedTokens.remove(oldKey)?.let { removed -> keyedTokens.put(key, removed) }
             }
             if (data.token != null) {
-                keyedTokens.get(key)?.let { tokens -> tokens.add(data.token) }
+                keyedTokens.get(key)?.let { tokens -> tokens.add(TokenId(data.token)) }
                     ?: run {
-                        val tokens = mutableSetOf(data.token)
+                        val tokens = mutableSetOf(TokenId(data.token))
                         keyedTokens.put(key, tokens)
                     }
             }
@@ -125,7 +125,7 @@
                 isMigration ||
                     remote == null ||
                     remote.sessionToken == data.token ||
-                    !tokensWithNotifications.contains(remote.sessionToken)
+                    !tokensWithNotifications.contains(TokenId(remote.sessionToken))
             ) {
                 // Not filtering in this case. Passing the event along to listeners.
                 dispatchMediaDataLoaded(key, oldKey, data, immediately)
@@ -136,7 +136,7 @@
                 // If the local session uses a different notification key, then lets go a step
                 // farther and dismiss the media data so that media controls for the local session
                 // don't hang around while casting.
-                if (!keyedTokens.get(key)!!.contains(remote.sessionToken)) {
+                if (!keyedTokens.get(key)!!.contains(TokenId(remote.sessionToken))) {
                     dispatchMediaDataRemoved(key)
                 }
             }
@@ -199,6 +199,15 @@
                     packageControllers.put(controller.packageName, tokens)
                 }
         }
-        tokensWithNotifications.retainAll(controllers.map { it.sessionToken })
+        tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+    }
+
+    /**
+     * Represents a unique identifier for a [MediaSession.Token].
+     *
+     * It's used to avoid storing strong binders for media session tokens.
+     */
+    private data class TokenId(val id: Int) {
+        constructor(token: MediaSession.Token) : this(token.hashCode())
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
index 03bc935..8a565fa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt
@@ -26,4 +26,8 @@
 class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags) {
     /** */
     fun isMediaTttEnabled(): Boolean = featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER)
+
+    /** Check whether the flag for the receiver success state is enabled. */
+    fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
+        featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
 }
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 7b9d0b4..889147b 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
@@ -37,6 +37,7 @@
 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.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttIcon
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
@@ -69,6 +70,7 @@
         mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
+        dumpManager: DumpManager,
         powerManager: PowerManager,
         @Main private val mainHandler: Handler,
         private val mediaTttFlags: MediaTttFlags,
@@ -83,6 +85,7 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
+        dumpManager,
         powerManager,
         R.layout.media_ttt_chip_receiver,
         wakeLockBuilder,
@@ -111,6 +114,9 @@
         }
     }
 
+    private var maxRippleWidth: Float = 0f
+    private var maxRippleHeight: Float = 0f
+
     private fun updateMediaTapToTransferReceiverDisplay(
         @StatusBarManager.MediaTransferReceiverState displayState: Int,
         routeInfo: MediaRoute2Info,
@@ -162,6 +168,7 @@
     }
 
     override fun start() {
+        super.start()
         if (mediaTttFlags.isMediaTttEnabled()) {
             commandQueue.addCallback(commandQueueCallbacks)
         }
@@ -212,7 +219,7 @@
         expandRipple(view.requireViewById(R.id.ripple))
     }
 
-    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+    override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         val appIconView = view.getAppIconView()
         appIconView.animate()
                 .translationYBy(getTranslationAmount().toFloat())
@@ -222,7 +229,14 @@
                 .alpha(0f)
                 .setDuration(ICON_ALPHA_ANIM_DURATION)
                 .start()
-        (view.requireViewById(R.id.ripple) as ReceiverChipRippleView).collapseRipple(onAnimationEnd)
+
+        val rippleView: ReceiverChipRippleView = view.requireViewById(R.id.ripple)
+        if (removalReason == ChipStateReceiver.TRANSFER_TO_RECEIVER_SUCCEEDED.name &&
+                mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()) {
+            expandRippleToFull(rippleView, onAnimationEnd)
+        } else {
+            rippleView.collapseRipple(onAnimationEnd)
+        }
     }
 
     override fun getTouchableRegion(view: View, outRect: Rect) {
@@ -267,12 +281,19 @@
         })
     }
 
-    private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+    private fun layoutRipple(rippleView: ReceiverChipRippleView, isFullScreen: Boolean = false) {
         val windowBounds = windowManager.currentWindowMetrics.bounds
         val height = windowBounds.height().toFloat()
         val width = windowBounds.width().toFloat()
 
-        rippleView.setMaxSize(width / 2f, height / 2f)
+        if (isFullScreen) {
+            maxRippleHeight = height * 2f
+            maxRippleWidth = width * 2f
+        } else {
+            maxRippleHeight = height / 2f
+            maxRippleWidth = width / 2f
+        }
+        rippleView.setMaxSize(maxRippleWidth, maxRippleHeight)
         // Center the ripple on the bottom of the screen in the middle.
         rippleView.setCenter(width * 0.5f, height)
         val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
@@ -282,6 +303,11 @@
     private fun View.getAppIconView(): CachingIconView {
         return this.requireViewById(R.id.app_icon)
     }
+
+    private fun expandRippleToFull(rippleView: ReceiverChipRippleView, onAnimationEnd: Runnable?) {
+        layoutRipple(rippleView, true)
+        rippleView.expandToFull(maxRippleHeight, onAnimationEnd)
+    }
 }
 
 val ICON_TRANSLATION_ANIM_DURATION = 30.frames
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 6e9fc5c..87b2528 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -22,6 +22,7 @@
 import android.util.AttributeSet
 import com.android.systemui.surfaceeffects.ripple.RippleShader
 import com.android.systemui.surfaceeffects.ripple.RippleView
+import kotlin.math.pow
 
 /**
  * An expanding ripple effect for the media tap-to-transfer receiver chip.
@@ -59,4 +60,44 @@
         })
         animator.reverse()
     }
+
+    // Expands the ripple to cover full screen.
+    fun expandToFull(newHeight: Float, onAnimationEnd: Runnable? = null) {
+        if (!isStarted) {
+            return
+        }
+        // Reset all listeners to animator.
+        animator.removeAllListeners()
+        animator.removeAllUpdateListeners()
+
+        // Only show the outline as ripple expands and disappears when animation ends.
+        setRippleFill(false)
+
+        val startingPercentage = calculateStartingPercentage(newHeight)
+        animator.addUpdateListener { updateListener ->
+            val now = updateListener.currentPlayTime
+            val progress = updateListener.animatedValue as Float
+            rippleShader.progress = startingPercentage + (progress * (1 - startingPercentage))
+            rippleShader.distortionStrength = 1 - rippleShader.progress
+            rippleShader.pixelDensity = 1 - rippleShader.progress
+            rippleShader.time = now.toFloat()
+            invalidate()
+        }
+        animator.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationEnd(animation: Animator?) {
+                animation?.let { visibility = GONE }
+                onAnimationEnd?.run()
+                isStarted = false
+            }
+        })
+        animator.start()
+    }
+
+    // Calculates the actual starting percentage according to ripple shader progress set method.
+    // Check calculations in [RippleShader.progress]
+    fun calculateStartingPercentage(newHeight: Float): Float {
+        val ratio = rippleShader.currentHeight / newHeight
+        val remainingPercentage = (1 - ratio).toDouble().pow(1 / 3.toDouble()).toFloat()
+        return 1 - remainingPercentage
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index a6447a5..5716a1d72 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -448,6 +448,10 @@
 
     // Any cleanup needed when the service is being destroyed.
     void onDestroy() {
+        if (mSaveInBgTask != null) {
+            // just log success/failure for the pre-existing screenshot
+            mSaveInBgTask.setActionsReadyListener(this::logSuccessOnActionsReady);
+        }
         removeWindow();
         releaseMediaPlayer();
         releaseContext();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index ba779c6..639172f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -19,6 +19,9 @@
 import android.content.Context
 import android.view.ViewGroup
 import com.android.systemui.R
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.END
 import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction.START
@@ -30,17 +33,24 @@
 @SysUIUnfoldScope
 class NotificationPanelUnfoldAnimationController
 @Inject
-constructor(private val context: Context, progressProvider: NaturalRotationUnfoldProgressProvider) {
+constructor(
+    private val context: Context,
+    statusBarStateController: StatusBarStateController,
+    progressProvider: NaturalRotationUnfoldProgressProvider,
+) {
+
+    private val filterShade: () -> Boolean = { statusBarStateController.getState() == SHADE ||
+        statusBarStateController.getState() == SHADE_LOCKED }
 
     private val translateAnimator by lazy {
         UnfoldConstantTranslateAnimator(
             viewsIdToTranslate =
                 setOf(
-                    ViewIdToTranslate(R.id.quick_settings_panel, START),
-                    ViewIdToTranslate(R.id.notification_stack_scroller, END),
-                    ViewIdToTranslate(R.id.rightLayout, END),
-                    ViewIdToTranslate(R.id.clock, START),
-                    ViewIdToTranslate(R.id.date, START)),
+                    ViewIdToTranslate(R.id.quick_settings_panel, START, filterShade),
+                    ViewIdToTranslate(R.id.notification_stack_scroller, END, filterShade),
+                    ViewIdToTranslate(R.id.rightLayout, END, filterShade),
+                    ViewIdToTranslate(R.id.clock, START, filterShade),
+                    ViewIdToTranslate(R.id.date, START, filterShade)),
             progressProvider = progressProvider)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1709043..3e9d89a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -44,6 +44,7 @@
 import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import static java.lang.Float.isNaN;
 
@@ -139,6 +140,10 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.shared.model.TransitionState;
+import com.android.systemui.keyguard.shared.model.TransitionStep;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -679,6 +684,12 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
+    private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+
+    private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    private boolean mIsDreamToLockscreenTransitionRunning = false;
+    private int mDreamingToLockscreenTransitionTranslationY;
+    private boolean mUnocclusionTransitionFlagEnabled = false;
 
     private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
             mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
@@ -694,6 +705,12 @@
         }
     };
 
+    private final Consumer<TransitionStep> mDreamingToLockscreenTransition =
+            (TransitionStep step) -> {
+                mIsDreamToLockscreenTransitionRunning =
+                    step.getTransitionState() == TransitionState.RUNNING;
+            };
+
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
             @Main Handler handler,
@@ -763,6 +780,8 @@
             KeyguardBottomAreaViewModel keyguardBottomAreaViewModel,
             KeyguardBottomAreaInteractor keyguardBottomAreaInteractor,
             AlternateBouncerInteractor alternateBouncerInteractor,
+            DreamingToLockscreenTransitionViewModel dreamingToLockscreenTransitionViewModel,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
             DumpManager dumpManager) {
         keyguardStateController.addCallback(new KeyguardStateController.Callback() {
             @Override
@@ -778,6 +797,8 @@
         mShadeLog = shadeLogger;
         mShadeHeightLogger = shadeHeightLogger;
         mGutsManager = gutsManager;
+        mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -923,6 +944,8 @@
         mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
                 SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
 
+        mUnocclusionTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
+
         mQsFrameTranslateController = qsFrameTranslateController;
         updateUserSwitcherFlags();
         mKeyguardBottomAreaViewModel = keyguardBottomAreaViewModel;
@@ -1076,6 +1099,18 @@
         mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
         mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
                 controller.setup(mNotificationContainerParent));
+
+        if (mUnocclusionTransitionFlagEnabled) {
+            collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
+                    dreamingToLockscreenTransitionAlpha(mNotificationStackScrollLayoutController));
+
+            collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
+                    mDreamingToLockscreenTransitionTranslationY),
+                    dreamingToLockscreenTransitionY(mNotificationStackScrollLayoutController));
+
+            collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
+                    mDreamingToLockscreenTransition);
+        }
     }
 
     @VisibleForTesting
@@ -1110,6 +1145,8 @@
         mUdfpsMaxYBurnInOffset = mResources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y);
         mSplitShadeScrimTransitionDistance = mResources.getDimensionPixelSize(
                 R.dimen.split_shade_scrim_transition_distance);
+        mDreamingToLockscreenTransitionTranslationY = mResources.getDimensionPixelSize(
+                R.dimen.dreaming_to_lockscreen_transition_lockscreen_translation_y);
     }
 
     private void updateViewControllers(KeyguardStatusView keyguardStatusView,
@@ -1337,8 +1374,8 @@
                 mFalsingManager,
                 mLockIconViewController,
                 stringResourceId ->
-                        mKeyguardIndicationController.showTransientIndication(stringResourceId)
-        );
+                        mKeyguardIndicationController.showTransientIndication(stringResourceId),
+                mVibratorHelper);
     }
 
     @VisibleForTesting
@@ -1773,10 +1810,14 @@
     }
 
     private void updateClock() {
+        if (mIsDreamToLockscreenTransitionRunning) {
+            return;
+        }
         float alpha = mClockPositionResult.clockAlpha * mKeyguardOnlyContentAlpha;
         mKeyguardStatusViewController.setAlpha(alpha);
         mKeyguardStatusViewController
-                .setTranslationYExcludingMedia(mKeyguardOnlyTransitionTranslationY);
+            .setTranslationY(mKeyguardOnlyTransitionTranslationY, /* excludeMedia= */true);
+
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setAlpha(alpha);
         }
@@ -2660,7 +2701,9 @@
         } else if (statusBarState == KEYGUARD
                 || statusBarState == StatusBarState.SHADE_LOCKED) {
             mKeyguardBottomArea.setVisibility(View.VISIBLE);
-            mKeyguardBottomArea.setAlpha(1f);
+            if (!mIsDreamToLockscreenTransitionRunning) {
+                mKeyguardBottomArea.setAlpha(1f);
+            }
         } else {
             mKeyguardBottomArea.setVisibility(View.GONE);
         }
@@ -3527,6 +3570,9 @@
     }
 
     private void updateNotificationTranslucency() {
+        if (mIsDreamToLockscreenTransitionRunning) {
+            return;
+        }
         float alpha = 1f;
         if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
                 && !mHeadsUpManager.hasPinnedHeadsUp()) {
@@ -3582,6 +3628,9 @@
     }
 
     private void updateKeyguardBottomAreaAlpha() {
+        if (mIsDreamToLockscreenTransitionRunning) {
+            return;
+        }
         // There are two possible panel expansion behaviors:
         // • User dragging up to unlock: we want to fade out as quick as possible
         //   (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
@@ -3614,7 +3663,9 @@
     }
 
     private void onExpandingFinished() {
-        mScrimController.onExpandingFinished();
+        if (!mUnocclusionTransitionFlagEnabled) {
+            mScrimController.onExpandingFinished();
+        }
         mNotificationStackScrollLayoutController.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
         mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
@@ -5809,6 +5860,32 @@
         mCurrentPanelState = state;
     }
 
+    private Consumer<Float> dreamingToLockscreenTransitionAlpha(
+            NotificationStackScrollLayoutController stackScroller) {
+        return (Float alpha) -> {
+            mKeyguardStatusViewController.setAlpha(alpha);
+            stackScroller.setAlpha(alpha);
+
+            mKeyguardBottomAreaInteractor.setAlpha(alpha);
+            mLockIconViewController.setAlpha(alpha);
+
+            if (mKeyguardQsUserSwitchController != null) {
+                mKeyguardQsUserSwitchController.setAlpha(alpha);
+            }
+            if (mKeyguardUserSwitcherController != null) {
+                mKeyguardUserSwitcherController.setAlpha(alpha);
+            }
+        };
+    }
+
+    private Consumer<Float> dreamingToLockscreenTransitionY(
+                NotificationStackScrollLayoutController stackScroller) {
+        return (Float translationY) -> {
+            mKeyguardStatusViewController.setTranslationY(translationY,  /* excludeMedia= */false);
+            stackScroller.setTranslationY(translationY);
+        };
+    }
+
     @VisibleForTesting
     StatusBarStateController getStatusBarStateController() {
         return mStatusBarStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 3a011c5..64b6e61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -19,6 +19,7 @@
 import android.app.StatusBarManager;
 import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.GestureDetector;
@@ -242,7 +243,9 @@
                         () -> mService.wakeUpIfDozing(
                                 SystemClock.uptimeMillis(),
                                 mView,
-                                "LOCK_ICON_TOUCH"));
+                                "LOCK_ICON_TOUCH",
+                                PowerManager.WAKE_REASON_GESTURE)
+                );
 
                 // In case we start outside of the view bounds (below the status bar), we need to
                 // dispatch
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index bf622c9..db70065 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.shade
 
 import android.hardware.display.AmbientDisplayConfiguration
+import android.os.PowerManager
 import android.os.SystemClock
 import android.os.UserHandle
 import android.provider.Settings
@@ -89,7 +90,8 @@
                 centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_SINGLE_TAP"
+                    "PULSING_SINGLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
                 )
             }
             return true
@@ -114,7 +116,9 @@
             centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_DOUBLE_TAP")
+                    "PULSING_DOUBLE_TAP",
+                    PowerManager.WAKE_REASON_TAP
+            )
             return true
         }
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 8ceb46e..595b39a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -946,8 +946,7 @@
         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
             if (mAlternateBouncerInteractor.isVisibleState()) {
                 return; // udfps affordance is highlighted, no need to show action to unlock
-            } else if (!mKeyguardUpdateMonitor.getIsFaceAuthenticated()
-                    && mKeyguardUpdateMonitor.isFaceEnrolled()) {
+            } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) {
                 String message = mContext.getString(R.string.keyguard_retry);
                 mStatusBarKeyguardViewManager.setKeyguardMessage(message, mInitialTextColorState);
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 2334a4c..9421524 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -90,8 +90,13 @@
 class LinearLightRevealEffect(private val isVertical: Boolean) : LightRevealEffect {
 
     // Interpolator that reveals >80% of the content at 0.5 progress, makes revealing faster
-    private val interpolator = PathInterpolator(/* controlX1= */ 0.4f, /* controlY1= */ 0f,
-            /* controlX2= */ 0.2f, /* controlY2= */ 1f)
+    private val interpolator =
+        PathInterpolator(
+            /* controlX1= */ 0.4f,
+            /* controlY1= */ 0f,
+            /* controlX2= */ 0.2f,
+            /* controlY2= */ 1f
+        )
 
     override fun setRevealAmountOnScrim(amount: Float, scrim: LightRevealScrim) {
         val interpolatedAmount = interpolator.getInterpolation(amount)
@@ -116,17 +121,17 @@
 
         if (isVertical) {
             scrim.setRevealGradientBounds(
-                left = scrim.width / 2 - (scrim.width / 2) * gradientBoundsAmount,
+                left = scrim.viewWidth / 2 - (scrim.viewWidth / 2) * gradientBoundsAmount,
                 top = 0f,
-                right = scrim.width / 2 + (scrim.width / 2) * gradientBoundsAmount,
-                bottom = scrim.height.toFloat()
+                right = scrim.viewWidth / 2 + (scrim.viewWidth / 2) * gradientBoundsAmount,
+                bottom = scrim.viewHeight.toFloat()
             )
         } else {
             scrim.setRevealGradientBounds(
                 left = 0f,
-                top = scrim.height / 2 - (scrim.height / 2) * gradientBoundsAmount,
-                right = scrim.width.toFloat(),
-                bottom = scrim.height / 2 + (scrim.height / 2) * gradientBoundsAmount
+                top = scrim.viewHeight / 2 - (scrim.viewHeight / 2) * gradientBoundsAmount,
+                right = scrim.viewWidth.toFloat(),
+                bottom = scrim.viewHeight / 2 + (scrim.viewHeight / 2) * gradientBoundsAmount
             )
         }
     }
@@ -234,7 +239,14 @@
  * transparent center. The center position, size, and stops of the gradient can be manipulated to
  * reveal views below the scrim as if they are being 'lit up'.
  */
-class LightRevealScrim(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+class LightRevealScrim
+@JvmOverloads
+constructor(
+    context: Context?,
+    attrs: AttributeSet?,
+    initialWidth: Int? = null,
+    initialHeight: Int? = null
+) : View(context, attrs) {
 
     /** Listener that is called if the scrim's opaqueness changes */
     lateinit var isScrimOpaqueChangedListener: Consumer<Boolean>
@@ -278,6 +290,17 @@
     var revealGradientHeight: Float = 0f
 
     /**
+     * Keeps the initial value until the view is measured. See [LightRevealScrim.onMeasure].
+     *
+     * Needed as the view dimensions are used before the onMeasure pass happens, and without preset
+     * width and height some flicker during fold/unfold happens.
+     */
+    internal var viewWidth: Int = initialWidth ?: 0
+        private set
+    internal var viewHeight: Int = initialHeight ?: 0
+        private set
+
+    /**
      * Alpha of the fill that can be used in the beginning of the animation to hide the content.
      * Normally the gradient bounds are animated from small size so the content is not visible, but
      * if the start gradient bounds allow to see some content this could be used to make the reveal
@@ -375,6 +398,11 @@
         invalidate()
     }
 
+    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+        viewWidth = measuredWidth
+        viewHeight = measuredHeight
+    }
     /**
      * Sets bounds for the transparent oval gradient that reveals the views below the scrim. This is
      * simply a helper method that sets [revealGradientCenter], [revealGradientWidth], and
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index b8302d7..905cc3f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -5,6 +5,7 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.content.res.Configuration
+import android.os.PowerManager
 import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.util.MathUtils
@@ -272,7 +273,12 @@
         // Bind the click listener of the shelf to go to the full shade
         notificationShelfController.setOnClickListener {
             if (statusBarStateController.state == StatusBarState.KEYGUARD) {
-                centralSurfaces.wakeUpIfDozing(SystemClock.uptimeMillis(), it, "SHADE_CLICK")
+                centralSurfaces.wakeUpIfDozing(
+                        SystemClock.uptimeMillis(),
+                        it,
+                        "SHADE_CLICK",
+                        PowerManager.WAKE_REASON_GESTURE,
+                )
                 goToLockedShade(it)
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 3516037..8f9365c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.UserInfo;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -64,6 +65,8 @@
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.ListenerSet;
 
+import dagger.Lazy;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -71,8 +74,6 @@
 import java.util.Optional;
 import java.util.function.Consumer;
 
-import dagger.Lazy;
-
 /**
  * Class for handling remote input state over a set of notifications. This class handles things
  * like keeping notifications temporarily that were cancelled as a response to a remote input
@@ -120,7 +121,8 @@
                 View view, PendingIntent pendingIntent, RemoteViews.RemoteResponse response) {
             mCentralSurfacesOptionalLazy.get().ifPresent(
                     centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                            SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK"));
+                            SystemClock.uptimeMillis(), view, "NOTIFICATION_CLICK",
+                            PowerManager.WAKE_REASON_GESTURE));
 
             final NotificationEntry entry = getNotificationForParent(view.getParent());
             mLogger.logInitialClick(entry, pendingIntent);
@@ -464,9 +466,6 @@
         riv.getController().setRemoteInputs(inputs);
         riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
         ViewGroup parent = view.getParent() != null ? (ViewGroup) view.getParent() : null;
-        if (parent != null) {
-            riv.setDefocusTargetHeight(parent.getHeight());
-        }
         riv.focusAnimated(parent);
         if (userMessageContent != null) {
             riv.setEditTextContent(userMessageContent);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
index c630feb..976924a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt
@@ -22,7 +22,6 @@
 import android.content.Context
 import android.content.res.Configuration
 import android.os.PowerManager
-import android.os.PowerManager.WAKE_REASON_GESTURE
 import android.os.SystemClock
 import android.util.IndentingPrintWriter
 import android.view.MotionEvent
@@ -249,7 +248,7 @@
         }
         if (statusBarStateController.isDozing) {
             wakeUpCoordinator.willWakeUp = true
-            mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), WAKE_REASON_GESTURE,
+            mPowerManager!!.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                     "com.android.systemui:PULSEDRAG")
         }
         lockscreenShadeTransitionController.goToLockedShade(startingChild,
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 b9074f0..6e74542 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -1301,7 +1301,7 @@
             }
         }
         String wifi = args.getString("wifi");
-        if (wifi != null) {
+        if (wifi != null && !mStatusBarPipelineFlags.runNewWifiIconBackend()) {
             boolean show = wifi.equals("show");
             String level = args.getString("level");
             if (level != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index c3ce593..705cf92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -16,6 +16,7 @@
 package com.android.systemui.statusbar.notification;
 
 import android.app.Notification;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -70,7 +71,8 @@
         }
 
         mCentralSurfacesOptional.ifPresent(centralSurfaces -> centralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
+                SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK",
+                PowerManager.WAKE_REASON_GESTURE));
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
         final NotificationEntry entry = row.getEntry();
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 4bcc0b6..c2c38a7 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
@@ -916,6 +916,11 @@
         return mView.getTranslationX();
     }
 
+    /** Set view y-translation */
+    public void setTranslationY(float translationY) {
+        mView.setTranslationY(translationY);
+    }
+
     public int indexOfChild(View view) {
         return mView.indexOfChild(view);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index d240d5a..9a8c5d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -420,7 +420,7 @@
         Runnable wakeUp = ()-> {
             if (!wasDeviceInteractive || mUpdateMonitor.isDreaming()) {
                 mLogger.i("bio wakelock: Authenticated, waking up...");
-                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
+                mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_BIOMETRIC,
                         "android.policy:BIOMETRIC");
             }
             Trace.beginSection("release wake-and-unlock");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index c7c6441..cf2f7742 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
+import android.os.PowerManager;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.view.KeyEvent;
@@ -205,7 +206,10 @@
     @Override
     Lifecycle getLifecycle();
 
-    void wakeUpIfDozing(long time, View where, String why);
+    /**
+     * Wakes up the device if the device was dozing.
+     */
+    void wakeUpIfDozing(long time, View where, String why, @PowerManager.WakeReason int wakeReason);
 
     NotificationShadeWindowView getNotificationShadeWindowView();
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c1ed10c..22ebcab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -887,8 +887,6 @@
         mKeyguardIndicationController.init();
 
         mColorExtractor.addOnColorsChangedListener(mOnColorsChangedListener);
-        mStatusBarStateController.addCallback(mStateListener,
-                SysuiStatusBarStateController.RANK_STATUS_BAR);
 
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
@@ -1507,10 +1505,11 @@
      * @param why the reason for the wake up
      */
     @Override
-    public void wakeUpIfDozing(long time, View where, String why) {
+    public void wakeUpIfDozing(long time, View where, String why,
+            @PowerManager.WakeReason int wakeReason) {
         if (mDozing && mScreenOffAnimationController.allowWakeUpIfDozing()) {
             mPowerManager.wakeUp(
-                    time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
+                    time, wakeReason, "com.android.systemui:" + why);
             mWakeUpComingFromTouch = true;
             mFalsingCollector.onScreenOnFromTouch();
         }
@@ -1587,6 +1586,8 @@
 
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
+        mStatusBarStateController.addCallback(mStateListener,
+                SysuiStatusBarStateController.RANK_STATUS_BAR);
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
         mBiometricUnlockController.addBiometricModeListener(
                 new BiometricUnlockController.BiometricModeListener() {
@@ -3364,7 +3365,8 @@
         mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
         mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
         if (mBouncerShowing) {
-            wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE");
+            wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE",
+                    PowerManager.WAKE_REASON_GESTURE);
         }
         updateScrimController();
         if (!mBouncerShowing) {
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 c7be219..c72eb05 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DemoStatusIcons.java
@@ -42,6 +42,8 @@
 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 com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -56,14 +58,17 @@
     private final int mIconSize;
 
     private StatusBarWifiView mWifiView;
+    private ModernStatusBarWifiView mModernWifiView;
     private boolean mDemoMode;
     private int mColor;
 
     private final MobileIconsViewModel mMobileIconsViewModel;
+    private final StatusBarLocation mLocation;
 
     public DemoStatusIcons(
             LinearLayout statusIcons,
             MobileIconsViewModel mobileIconsViewModel,
+            StatusBarLocation location,
             int iconSize
     ) {
         super(statusIcons.getContext());
@@ -71,6 +76,7 @@
         mIconSize = iconSize;
         mColor = DarkIconDispatcher.DEFAULT_ICON_TINT;
         mMobileIconsViewModel = mobileIconsViewModel;
+        mLocation = location;
 
         if (statusIcons instanceof StatusIconContainer) {
             setShouldRestrictIcons(((StatusIconContainer) statusIcons).isRestrictingIcons());
@@ -233,14 +239,14 @@
 
     public void addDemoWifiView(WifiIconState state) {
         Log.d(TAG, "addDemoWifiView: ");
-        // TODO(b/238425913): Migrate this view to {@code ModernStatusBarWifiView}.
         StatusBarWifiView view = StatusBarWifiView.fromContext(mContext, state.slot);
 
         int viewIndex = getChildCount();
         // If we have mobile views, put wifi before them
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            if (child instanceof StatusBarMobileView) {
+            if (child instanceof StatusBarMobileView
+                    || child instanceof ModernStatusBarMobileView) {
                 viewIndex = i;
                 break;
             }
@@ -287,7 +293,7 @@
         ModernStatusBarMobileView view = ModernStatusBarMobileView.constructAndBind(
                 mobileContext,
                 "mobile",
-                mMobileIconsViewModel.viewModelForSub(subId)
+                mMobileIconsViewModel.viewModelForSub(subId, mLocation)
         );
 
         // mobile always goes at the end
@@ -296,6 +302,30 @@
     }
 
     /**
+     * Add a {@link ModernStatusBarWifiView}
+     */
+    public void addModernWifiView(LocationBasedWifiViewModel viewModel) {
+        Log.d(TAG, "addModernDemoWifiView: ");
+        ModernStatusBarWifiView view = ModernStatusBarWifiView
+                .constructAndBind(mContext, "wifi", viewModel);
+
+        int viewIndex = getChildCount();
+        // If we have mobile views, put wifi before them
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            if (child instanceof StatusBarMobileView
+                    || child instanceof ModernStatusBarMobileView) {
+                viewIndex = i;
+                break;
+            }
+        }
+
+        mModernWifiView = view;
+        mModernWifiView.setStaticDrawableColor(mColor);
+        addView(view, viewIndex, 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
@@ -317,8 +347,14 @@
 
     public void onRemoveIcon(StatusIconDisplayable view) {
         if (view.getSlot().equals("wifi")) {
-            removeView(mWifiView);
-            mWifiView = null;
+            if (view instanceof StatusBarWifiView) {
+                removeView(mWifiView);
+                mWifiView = null;
+            } else if (view instanceof ModernStatusBarWifiView) {
+                Log.d(TAG, "onRemoveIcon: removing modern wifi view");
+                removeView(mModernWifiView);
+                mModernWifiView = null;
+            }
         } else if (view instanceof StatusBarMobileView) {
             StatusBarMobileView mobileView = matchingMobileView(view);
             if (mobileView != null) {
@@ -371,8 +407,14 @@
         if (mWifiView != null) {
             mWifiView.onDarkChanged(areas, darkIntensity, tint);
         }
+        if (mModernWifiView != null) {
+            mModernWifiView.onDarkChanged(areas, darkIntensity, tint);
+        }
         for (StatusBarMobileView view : mMobileViews) {
             view.onDarkChanged(areas, darkIntensity, tint);
         }
+        for (ModernStatusBarMobileView view : mModernMobileViews) {
+            view.onDarkChanged(areas, darkIntensity, tint);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 2ce1163..e4227dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardBottomAreaViewBinder.bind
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.statusbar.VibratorHelper
 
 /**
  * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -65,12 +66,14 @@
         falsingManager: FalsingManager? = null,
         lockIconViewController: LockIconViewController? = null,
         messageDisplayer: MessageDisplayer? = null,
+        vibratorHelper: VibratorHelper? = null,
     ) {
         binding =
             bind(
                 this,
                 viewModel,
                 falsingManager,
+                vibratorHelper,
             ) {
                 messageDisplayer?.display(it)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index d500f99..ee8b861 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -232,7 +232,6 @@
     private boolean mExpansionAffectsAlpha = true;
     private boolean mAnimateChange;
     private boolean mUpdatePending;
-    private boolean mTracking;
     private long mAnimationDuration = -1;
     private long mAnimationDelay;
     private Animator.AnimatorListener mAnimatorListener;
@@ -526,7 +525,6 @@
     }
 
     public void onTrackingStarted() {
-        mTracking = true;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
         if (!mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
             mAnimatingPanelExpansionOnUnlock = false;
@@ -534,7 +532,6 @@
     }
 
     public void onExpandingFinished() {
-        mTracking = false;
         setUnocclusionAnimationRunning(false);
     }
 
@@ -1450,8 +1447,6 @@
         pw.print(" expansionProgress=");
         pw.println(mTransitionToLockScreenFullShadeNotificationsProgress);
 
-        pw.print("  mTracking=");
-        pw.println(mTracking);
         pw.print("  mDefaultScrimAlpha=");
         pw.println(mDefaultScrimAlpha);
         pw.print("  mPanelExpansionFraction=");
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 df3ab49..1a14a036 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -359,6 +359,7 @@
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
+        private final StatusBarLocation mLocation;
 
         // Enables SystemUI demo mode to take effect in this group
         protected boolean mDemoable = true;
@@ -381,11 +382,12 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+            mLocation = location;
 
             if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
-                mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
+                mMobileIconsViewModel = mobileUiAdapter.getMobileIconsViewModel();
                 MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
             } else {
                 mMobileIconsViewModel = null;
@@ -394,7 +396,7 @@
             if (statusBarPipelineFlags.runNewWifiIconBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
-                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, mLocation);
             } else {
                 mWifiViewModel = null;
             }
@@ -495,6 +497,11 @@
 
             ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
             mGroup.addView(view, index, onCreateLayoutParams());
+
+            if (mIsInDemoMode) {
+                mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+            }
+
             return view;
         }
 
@@ -569,7 +576,7 @@
                     .constructAndBind(
                             mobileContext,
                             slot,
-                            mMobileIconsViewModel.viewModelForSub(subId)
+                            mMobileIconsViewModel.viewModelForSub(subId, mLocation)
                         );
         }
 
@@ -686,6 +693,9 @@
             mIsInDemoMode = true;
             if (mDemoStatusIcons == null) {
                 mDemoStatusIcons = createDemoStatusIcons();
+                if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+                    mDemoStatusIcons.addModernWifiView(mWifiViewModel);
+                }
             }
             mDemoStatusIcons.onDemoModeStarted();
         }
@@ -705,7 +715,12 @@
         }
 
         protected DemoStatusIcons createDemoStatusIcons() {
-            return new DemoStatusIcons((LinearLayout) mGroup, mMobileIconsViewModel, mIconSize);
+            return new DemoStatusIcons(
+                    (LinearLayout) mGroup,
+                    mMobileIconsViewModel,
+                    mLocation,
+                    mIconSize
+            );
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3e3ce77..61ddf8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -137,7 +137,7 @@
     private final PrimaryBouncerInteractor mPrimaryBouncerInteractor;
     private final AlternateBouncerInteractor mAlternateBouncerInteractor;
     private final BouncerView mPrimaryBouncerView;
-    private final Lazy<com.android.systemui.shade.ShadeController> mShadeController;
+    private final Lazy<ShadeController> mShadeController;
 
     // Local cache of expansion events, to avoid duplicates
     private float mFraction = -1f;
@@ -255,6 +255,7 @@
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernBouncerEnabled;
     private boolean mIsModernAlternateBouncerEnabled;
+    private boolean mIsUnoccludeTransitionFlagEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private Runnable mKeyguardGoneCancelAction;
@@ -335,6 +336,7 @@
         mIsModernBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_BOUNCER);
         mIsModernAlternateBouncerEnabled = featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER);
         mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mIsUnoccludeTransitionFlagEnabled = featureFlags.isEnabled(Flags.UNOCCLUSION_TRANSITION);
     }
 
     @Override
@@ -891,8 +893,10 @@
             // by a FLAG_DISMISS_KEYGUARD_ACTIVITY.
             reset(isOccluding /* hideBouncerWhenShowing*/);
         }
-        if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
-            mCentralSurfaces.animateKeyguardUnoccluding();
+        if (!mIsUnoccludeTransitionFlagEnabled) {
+            if (animate && !isOccluded && isShowing && !primaryBouncerIsShowing()) {
+                mCentralSurfaces.animateKeyguardUnoccluding();
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index a1e0c50..da1c361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -20,6 +20,7 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
@@ -270,7 +271,8 @@
             boolean nowExpanded) {
         mHeadsUpManager.setExpanded(clickedEntry, nowExpanded);
         mCentralSurfaces.wakeUpIfDozing(
-                SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK");
+                SystemClock.uptimeMillis(), clickedView, "NOTIFICATION_CLICK",
+                PowerManager.WAKE_REASON_GESTURE);
         if (nowExpanded) {
             if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                 mShadeTransitionController.goToLockedShade(clickedEntry.getRow());
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 c350c78..0d01715 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
@@ -36,7 +36,7 @@
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositorySwitcher
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl
 import dagger.Binds
@@ -56,7 +56,7 @@
     @Binds
     abstract fun connectivityRepository(impl: ConnectivityRepositoryImpl): ConnectivityRepository
 
-    @Binds abstract fun wifiRepository(impl: WifiRepositoryImpl): WifiRepository
+    @Binds abstract fun wifiRepository(impl: WifiRepositorySwitcher): WifiRepository
 
     @Binds
     abstract fun wifiInteractor(impl: WifiInteractorImpl): WifiInteractor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 1d00c33..1aa954f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.model
 
 import android.annotation.IntRange
-import android.telephony.Annotation.DataActivityType
 import android.telephony.CellSignalStrength
 import android.telephony.TelephonyCallback.CarrierNetworkListener
 import android.telephony.TelephonyCallback.DataActivityListener
@@ -27,7 +26,10 @@
 import android.telephony.TelephonyCallback.SignalStrengthsListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 
 /**
  * Data class containing all of the relevant information for a particular line of service, known as
@@ -39,30 +41,112 @@
  * threading complex system objects through the pipeline.
  */
 data class MobileConnectionModel(
-    /** From [ServiceStateListener.onServiceStateChanged] */
+    /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
     val isEmergencyOnly: Boolean = false,
+    val isRoaming: Boolean = false,
+    /**
+     * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+     * current registered operator name in short alphanumeric format. In some cases this name might
+     * be preferred over other methods of calculating the network name
+     */
+    val operatorAlphaShort: String? = null,
 
-    /** From [SignalStrengthsListener.onSignalStrengthsChanged] */
+    /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
     val isGsm: Boolean = false,
     @IntRange(from = 0, to = 4)
     val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
     @IntRange(from = 0, to = 4)
     val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
 
-    /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */
+    /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
     val dataConnectionState: DataConnectionState = Disconnected,
 
-    /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
-    @DataActivityType val dataActivityDirection: Int? = null,
+    /**
+     * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
+     * values
+     */
+    val dataActivityDirection: DataActivityModel =
+        DataActivityModel(
+            hasActivityIn = false,
+            hasActivityOut = false,
+        ),
 
-    /** From [CarrierNetworkListener.onCarrierNetworkChange] */
+    /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
     val carrierNetworkChangeActive: Boolean = false,
 
+    /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
+
     /**
-     * From [DisplayInfoListener.onDisplayInfoChanged].
-     *
      * [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 = ResolvedNetworkType.UnknownNetworkType,
-)
+) : Diffable<MobileConnectionModel> {
+    override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
+        if (prevVal.dataConnectionState != dataConnectionState) {
+            row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+        }
+
+        if (prevVal.isEmergencyOnly != isEmergencyOnly) {
+            row.logChange(COL_EMERGENCY, isEmergencyOnly)
+        }
+
+        if (prevVal.isRoaming != isRoaming) {
+            row.logChange(COL_ROAMING, isRoaming)
+        }
+
+        if (prevVal.operatorAlphaShort != operatorAlphaShort) {
+            row.logChange(COL_OPERATOR, operatorAlphaShort)
+        }
+
+        if (prevVal.isGsm != isGsm) {
+            row.logChange(COL_IS_GSM, isGsm)
+        }
+
+        if (prevVal.cdmaLevel != cdmaLevel) {
+            row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+        }
+
+        if (prevVal.primaryLevel != primaryLevel) {
+            row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+        }
+
+        if (prevVal.dataActivityDirection != dataActivityDirection) {
+            row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+        }
+
+        if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
+            row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+        }
+
+        if (prevVal.resolvedNetworkType != resolvedNetworkType) {
+            row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_CONNECTION_STATE, dataConnectionState.toString())
+        row.logChange(COL_EMERGENCY, isEmergencyOnly)
+        row.logChange(COL_ROAMING, isRoaming)
+        row.logChange(COL_OPERATOR, operatorAlphaShort)
+        row.logChange(COL_IS_GSM, isGsm)
+        row.logChange(COL_CDMA_LEVEL, cdmaLevel)
+        row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
+        row.logChange(COL_ACTIVITY_DIRECTION, dataActivityDirection.toString())
+        row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
+        row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
+    }
+
+    companion object {
+        const val COL_EMERGENCY = "EmergencyOnly"
+        const val COL_ROAMING = "Roaming"
+        const val COL_OPERATOR = "OperatorName"
+        const val COL_IS_GSM = "IsGsm"
+        const val COL_CDMA_LEVEL = "CdmaLevel"
+        const val COL_PRIMARY_LEVEL = "PrimaryLevel"
+        const val COL_CONNECTION_STATE = "ConnectionState"
+        const val COL_ACTIVITY_DIRECTION = "DataActivity"
+        const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
+        const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
new file mode 100644
index 0000000..c50d82a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.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.pipeline.mobile.data.model
+
+import android.content.Intent
+import android.telephony.TelephonyManager.EXTRA_DATA_SPN
+import android.telephony.TelephonyManager.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/**
+ * Encapsulates the data needed to show a network name for a mobile network. The data is parsed from
+ * the intent sent by [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED].
+ */
+sealed interface NetworkNameModel : Diffable<NetworkNameModel> {
+    val name: String
+
+    /** The default name is read from [com.android.internal.R.string.lockscreen_carrier_default] */
+    data class Default(override val name: String) : NetworkNameModel {
+        override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+            if (prevVal !is Default || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "Default($name)")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_NAME, "Default($name)")
+        }
+    }
+
+    /**
+     * This name has been derived from telephony intents. see
+     * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
+     */
+    data class Derived(override val name: String) : NetworkNameModel {
+        override fun logDiffs(prevVal: NetworkNameModel, row: TableRowLogger) {
+            if (prevVal !is Derived || prevVal.name != name) {
+                row.logChange(COL_NETWORK_NAME, "Derived($name)")
+            }
+        }
+
+        override fun logFull(row: TableRowLogger) {
+            row.logChange(COL_NETWORK_NAME, "Derived($name)")
+        }
+    }
+
+    companion object {
+        const val COL_NETWORK_NAME = "networkName"
+    }
+}
+
+fun Intent.toNetworkNameModel(separator: String): NetworkNameModel? {
+    val showSpn = getBooleanExtra(EXTRA_SHOW_SPN, false)
+    val spn = getStringExtra(EXTRA_DATA_SPN)
+    val showPlmn = getBooleanExtra(EXTRA_SHOW_PLMN, false)
+    val plmn = getStringExtra(EXTRA_PLMN)
+
+    val str = StringBuilder()
+    val strData = StringBuilder()
+    if (showPlmn && plmn != null) {
+        str.append(plmn)
+        strData.append(plmn)
+    }
+    if (showSpn && spn != null) {
+        if (str.isNotEmpty()) {
+            str.append(separator)
+        }
+        str.append(spn)
+    }
+
+    return if (str.isNotEmpty()) NetworkNameModel.Derived(str.toString()) else null
+}
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 2621f997..40e9ba1 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
@@ -20,7 +20,9 @@
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -38,6 +40,13 @@
 interface MobileConnectionRepository {
     /** The subscriptionId that this connection represents */
     val subId: Int
+
+    /**
+     * The table log buffer created for this connection. Will have the name "MobileConnectionLog
+     * [subId]"
+     */
+    val tableLogBuffer: TableLogBuffer
+
     /**
      * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
      * listener + model.
@@ -50,4 +59,15 @@
      * [SubscriptionManager.getDefaultDataSubscriptionId]
      */
     val isDefaultDataSubscription: StateFlow<Boolean>
+
+    /**
+     * See [TelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber]. This bit only matters if
+     * the connection type is CDMA.
+     *
+     * True if the Enhanced Roaming Indicator (ERI) display number is not [TelephonyManager.ERI_OFF]
+     */
+    val cdmaRoaming: StateFlow<Boolean>
+
+    /** The service provider name for this network connection, or the default name */
+    val networkName: StateFlow<NetworkNameModel>
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index 1c08525..b252de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -18,14 +18,18 @@
 
 import android.content.Context
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
 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.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 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.NetworkNameModel
 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
@@ -34,6 +38,7 @@
 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 com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -57,6 +62,7 @@
     private val dataSource: DemoModeMobileConnectionDataSource,
     @Application private val scope: CoroutineScope,
     context: Context,
+    private val logFactory: TableLogBufferFactory,
 ) : MobileConnectionsRepository {
 
     private var demoCommandJob: Job? = null
@@ -146,7 +152,16 @@
 
     override fun getRepoForSubId(subId: Int): DemoMobileConnectionRepository {
         return connectionRepoCache[subId]
-            ?: DemoMobileConnectionRepository(subId).also { connectionRepoCache[subId] = it }
+            ?: createDemoMobileConnectionRepo(subId).also { connectionRepoCache[subId] = it }
+    }
+
+    private fun createDemoMobileConnectionRepo(subId: Int): DemoMobileConnectionRepository {
+        val tableLogBuffer = logFactory.create("DemoMobileConnectionLog [$subId]", 100)
+
+        return DemoMobileConnectionRepository(
+            subId,
+            tableLogBuffer,
+        )
     }
 
     override val globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
@@ -185,7 +200,9 @@
         // 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.networkName.value = NetworkNameModel.Derived(state.name)
 
+        connection.cdmaRoaming.value = state.roaming
         connection.connectionInfo.value = state.toMobileConnectionModel()
     }
 
@@ -229,12 +246,13 @@
     private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
         return MobileConnectionModel(
             isEmergencyOnly = false, // TODO(b/261029387): not yet supported
+            isRoaming = roaming,
             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,
+            dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
             carrierNetworkChangeActive = carrierNetworkChange,
             resolvedNetworkType = dataType.toResolvedNetworkType()
         )
@@ -254,10 +272,17 @@
     }
 }
 
-class DemoMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class DemoMobileConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
     override val connectionInfo = MutableStateFlow(MobileConnectionModel())
 
     override val dataEnabled = MutableStateFlow(true)
 
     override val isDefaultDataSubscription = MutableStateFlow(true)
+
+    override val cdmaRoaming = MutableStateFlow(false)
+
+    override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo network"))
 }
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
index da55787..d4ddb85 100644
--- 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
@@ -24,10 +24,8 @@
 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
@@ -35,8 +33,6 @@
 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
@@ -52,27 +48,7 @@
     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) }
-    }
+    private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
 
     // 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
@@ -98,6 +74,8 @@
         val inflateStrength = getString("inflate")?.toBoolean()
         val activity = getString("activity")?.toActivity()
         val carrierNetworkChange = getString("carriernetworkchange") == "show"
+        val roaming = getString("roam") == "show"
+        val name = getString("networkname") ?: "demo mode"
 
         return Mobile(
             level = level,
@@ -107,6 +85,8 @@
             inflateStrength = inflateStrength,
             activity = activity,
             carrierNetworkChange = carrierNetworkChange,
+            roaming = roaming,
+            name = name,
         )
     }
 }
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
index 3f3acaf..8b03f71 100644
--- 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
@@ -34,6 +34,8 @@
         val inflateStrength: Boolean?,
         @DataActivityType val activity: Int?,
         val carrierNetworkChange: Boolean,
+        val roaming: Boolean,
+        val name: String,
     ) : FakeNetworkEventModel
 
     data class MobileDisabled(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index 15505fd..0b9e158 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -17,29 +17,39 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
 import android.content.Context
+import android.content.IntentFilter
 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.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
 import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.ERI_OFF
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import com.android.systemui.broadcast.BroadcastDispatcher
 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.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 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.model.toNetworkNameModel
 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -51,6 +61,7 @@
 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
@@ -61,13 +72,17 @@
 class MobileConnectionRepositoryImpl(
     private val context: Context,
     override val subId: Int,
+    defaultNetworkName: NetworkNameModel,
+    networkNameSeparator: String,
     private val telephonyManager: TelephonyManager,
     private val globalSettings: GlobalSettings,
+    broadcastDispatcher: BroadcastDispatcher,
     defaultDataSubId: StateFlow<Int>,
     globalMobileDataSettingChangedEvent: Flow<Unit>,
     mobileMappingsProxy: MobileMappingsProxy,
     bgDispatcher: CoroutineDispatcher,
     logger: ConnectivityPipelineLogger,
+    mobileLogger: TableLogBuffer,
     scope: CoroutineScope,
 ) : MobileConnectionRepository {
     init {
@@ -81,10 +96,11 @@
 
     private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
 
+    override val tableLogBuffer: TableLogBuffer = mobileLogger
+
     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(),
@@ -95,11 +111,18 @@
                         TelephonyCallback.CarrierNetworkListener,
                         TelephonyCallback.DisplayInfoListener {
                         override fun onServiceStateChanged(serviceState: ServiceState) {
-                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            logger.logOnServiceStateChanged(serviceState, subId)
+                            state =
+                                state.copy(
+                                    isEmergencyOnly = serviceState.isEmergencyOnly,
+                                    isRoaming = serviceState.roaming,
+                                    operatorAlphaShort = serviceState.operatorAlphaShort,
+                                )
                             trySend(state)
                         }
 
                         override fun onSignalStrengthsChanged(signalStrength: SignalStrength) {
+                            logger.logOnSignalStrengthsChanged(signalStrength, subId)
                             val cdmaLevel =
                                 signalStrength
                                     .getCellSignalStrengths(CellSignalStrengthCdma::class.java)
@@ -126,17 +149,23 @@
                             dataState: Int,
                             networkType: Int
                         ) {
+                            logger.logOnDataConnectionStateChanged(dataState, networkType, subId)
                             state =
                                 state.copy(dataConnectionState = dataState.toDataConnectionType())
                             trySend(state)
                         }
 
                         override fun onDataActivity(direction: Int) {
-                            state = state.copy(dataActivityDirection = direction)
+                            logger.logOnDataActivity(direction, subId)
+                            state =
+                                state.copy(
+                                    dataActivityDirection = direction.toMobileDataActivityModel()
+                                )
                             trySend(state)
                         }
 
                         override fun onCarrierNetworkChange(active: Boolean) {
+                            logger.logOnCarrierNetworkChange(active, subId)
                             state = state.copy(carrierNetworkChangeActive = active)
                             trySend(state)
                         }
@@ -144,6 +173,7 @@
                         override fun onDisplayInfoChanged(
                             telephonyDisplayInfo: TelephonyDisplayInfo
                         ) {
+                            logger.logOnDisplayInfoChanged(telephonyDisplayInfo, subId)
 
                             val networkType =
                                 if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
@@ -174,7 +204,11 @@
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
             .onEach { telephonyCallbackEvent.tryEmit(Unit) }
-            .logOutputChange(logger, "MobileSubscriptionModel")
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "MobileConnection ($subId)",
+                initialValue = state,
+            )
             .stateIn(scope, SharingStarted.WhileSubscribed(), state)
     }
 
@@ -208,46 +242,102 @@
             globalMobileDataSettingChangedEvent,
         )
 
-    override val dataEnabled: StateFlow<Boolean> =
+    override val cdmaRoaming: StateFlow<Boolean> =
+        telephonyPollingEvent
+            .mapLatest { telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber != ERI_OFF }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val networkName: StateFlow<NetworkNameModel> =
+        broadcastDispatcher
+            .broadcastFlow(IntentFilter(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED)) {
+                intent,
+                _ ->
+                if (intent.getIntExtra(EXTRA_SUBSCRIPTION_ID, INVALID_SUBSCRIPTION_ID) != subId) {
+                    defaultNetworkName
+                } else {
+                    intent.toNetworkNameModel(networkNameSeparator) ?: defaultNetworkName
+                }
+            }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                initialValue = defaultNetworkName,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+
+    override val dataEnabled: StateFlow<Boolean> = run {
+        val initial = dataConnectionAllowed()
         telephonyPollingEvent
             .mapLatest { dataConnectionAllowed() }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                columnName = "dataEnabled",
+                initialValue = initial,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
 
     private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
 
-    override val isDefaultDataSubscription: StateFlow<Boolean> =
+    override val isDefaultDataSubscription: StateFlow<Boolean> = run {
+        val initialValue = defaultDataSubId.value == subId
         defaultDataSubId
             .mapLatest { it == subId }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                mobileLogger,
+                columnPrefix = "",
+                columnName = "isDefaultDataSub",
+                initialValue = initialValue,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue)
+    }
 
     class Factory
     @Inject
     constructor(
+        private val broadcastDispatcher: BroadcastDispatcher,
         private val context: Context,
         private val telephonyManager: TelephonyManager,
         private val logger: ConnectivityPipelineLogger,
         private val globalSettings: GlobalSettings,
         private val mobileMappingsProxy: MobileMappingsProxy,
+        private val logFactory: TableLogBufferFactory,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
         fun build(
             subId: Int,
+            defaultNetworkName: NetworkNameModel,
+            networkNameSeparator: String,
             defaultDataSubId: StateFlow<Int>,
             globalMobileDataSettingChangedEvent: Flow<Unit>,
         ): MobileConnectionRepository {
+            val mobileLogger = logFactory.create(tableBufferLogName(subId), 100)
+
             return MobileConnectionRepositoryImpl(
                 context,
                 subId,
+                defaultNetworkName,
+                networkNameSeparator,
                 telephonyManager.createForSubscriptionId(subId),
                 globalSettings,
+                broadcastDispatcher,
                 defaultDataSubId,
                 globalMobileDataSettingChangedEvent,
                 mobileMappingsProxy,
                 bgDispatcher,
                 logger,
+                mobileLogger,
                 scope,
             )
         }
     }
+
+    companion object {
+        fun tableBufferLogName(subId: Int): String = "MobileConnectionLog [$subId]"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index 483df47..d407abe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -38,17 +38,20 @@
 import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.R
 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.NetworkNameModel
 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.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
 import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -88,6 +91,14 @@
 ) : MobileConnectionsRepository {
     private var subIdRepositoryCache: MutableMap<Int, MobileConnectionRepository> = mutableMapOf()
 
+    private val defaultNetworkName =
+        NetworkNameModel.Default(
+            context.getString(com.android.internal.R.string.lockscreen_carrier_default)
+        )
+
+    private val networkNameSeparator: String =
+        context.getString(R.string.status_bar_network_name_separator)
+
     /**
      * 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
@@ -110,6 +121,7 @@
                 awaitClose { subscriptionManager.removeOnSubscriptionsChangedListener(callback) }
             }
             .mapLatest { fetchSubscriptionsList().map { it.toSubscriptionModel() } }
+            .logInputChange(logger, "onSubscriptionsChanged")
             .onEach { infos -> dropUnusedReposFromCache(infos) }
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), listOf())
 
@@ -126,6 +138,8 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .distinctUntilChanged()
+            .logInputChange(logger, "onActiveDataSubscriptionIdChanged")
             .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
 
     private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
@@ -139,6 +153,7 @@
                 intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
             }
             .distinctUntilChanged()
+            .logInputChange(logger, "ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED")
             .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
             .stateIn(
                 scope,
@@ -147,13 +162,15 @@
             )
 
     private val carrierConfigChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
-        )
+        broadcastDispatcher
+            .broadcastFlow(IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED))
+            .logInputChange(logger, "ACTION_CARRIER_CONFIG_CHANGED")
 
     override val defaultDataSubRatConfig: StateFlow<Config> =
         merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
             .mapLatest { Config.readConfig(context) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultDataSubRatConfig")
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
@@ -161,10 +178,16 @@
             )
 
     override val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>> =
-        defaultDataSubRatConfig.map { mobileMappingsProxy.mapIconSets(it) }
+        defaultDataSubRatConfig
+            .map { mobileMappingsProxy.mapIconSets(it) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileIconMapping")
 
     override val defaultMobileIconGroup: Flow<MobileIconGroup> =
-        defaultDataSubRatConfig.map { mobileMappingsProxy.getDefaultIcons(it) }
+        defaultDataSubRatConfig
+            .map { mobileMappingsProxy.getDefaultIcons(it) }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileIconGroup")
 
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         if (!isValidSubId(subId)) {
@@ -181,22 +204,24 @@
      * 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)
-                }
+    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) }
             }
-
-        globalSettings.registerContentObserver(
-            globalSettings.getUriFor(MOBILE_DATA),
-            true,
-            observer
-        )
-
-        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
-    }
+            .logInputChange(logger, "globalMobileDataSettingChangedEvent")
 
     @SuppressLint("MissingPermission")
     override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
@@ -226,6 +251,8 @@
 
                 awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
             }
+            .distinctUntilChanged()
+            .logInputChange(logger, "defaultMobileNetworkConnectivity")
             .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
 
     private fun isValidSubId(subId: Int): Boolean {
@@ -243,6 +270,8 @@
     private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
         return mobileConnectionRepositoryFactory.build(
             subId,
+            defaultNetworkName,
+            networkNameSeparator,
             defaultDataSubId,
             globalMobileDataSettingChangedEvent,
         )
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 a26f28a..e6686dc 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
@@ -19,11 +19,15 @@
 import android.telephony.CarrierConfigManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.util.CarrierConfigTracker
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -32,6 +36,12 @@
 import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
+    /** The table log created for this connection */
+    val tableLogBuffer: TableLogBuffer
+
+    /** The current mobile data activity */
+    val activity: Flow<DataActivityModel>
+
     /** Only true if mobile is the default transport but is not validated, otherwise false */
     val isDefaultConnectionFailed: StateFlow<Boolean>
 
@@ -51,9 +61,25 @@
     /** Observable for RAT type (network type) indicator */
     val networkTypeIconGroup: StateFlow<MobileIconGroup>
 
+    /**
+     * Provider name for this network connection. The name can be one of 3 values:
+     * 1. The default network name, if one is configured
+     * 2. A derived name based off of the intent [ACTION_SERVICE_PROVIDERS_UPDATED]
+     * 3. Or, in the case where the repository sends us the default network name, we check for an
+     * override in [connectionInfo.operatorAlphaShort], a value that is derived from [ServiceState]
+     */
+    val networkName: StateFlow<NetworkNameModel>
+
     /** True if this line of service is emergency-only */
     val isEmergencyOnly: StateFlow<Boolean>
 
+    /**
+     * True if this connection is considered roaming. The roaming bit can come from [ServiceState],
+     * or directly from the telephony manager's CDMA ERI number value. Note that we don't consider a
+     * connection to be roaming while carrier network change is active
+     */
+    val isRoaming: StateFlow<Boolean>
+
     /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
     val level: StateFlow<Int>
 
@@ -75,10 +101,30 @@
 ) : MobileIconInteractor {
     private val connectionInfo = connectionRepository.connectionInfo
 
+    override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
+
+    override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+
     override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
 
     override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
 
+    override val networkName =
+        combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
+                if (
+                    networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
+                ) {
+                    NetworkNameModel.Derived(connection.operatorAlphaShort)
+                } else {
+                    networkName
+                }
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                connectionRepository.networkName.value
+            )
+
     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
     override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
         combine(
@@ -95,6 +141,18 @@
             .mapLatest { it.isEmergencyOnly }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
+    override val isRoaming: StateFlow<Boolean> =
+        combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
+                if (connection.carrierNetworkChangeActive) {
+                    false
+                } else if (connection.isGsm) {
+                    connection.isRoaming
+                } else {
+                    cdmaRoaming
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
     override val level: StateFlow<Int> =
         connectionInfo
             .mapLatest { connection ->
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 62fa723..829a5ca 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
@@ -20,7 +20,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
-import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
@@ -70,6 +69,9 @@
     private val mobileSubIdsState: StateFlow<List<Int>> =
         mobileSubIds.stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
 
+    /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */
+    val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState)
+
     override fun start() {
         // Only notify the icon controller if we want to *render* the new icons.
         // Note that this flow may still run if
@@ -81,12 +83,4 @@
             }
         }
     }
-
-    /**
-     * Create a MobileIconsViewModel for a given [IconManager], and bind it to to the manager's
-     * lifecycle. This will start collecting on [mobileSubIdsState] and link our new pipeline with
-     * the old view system.
-     */
-    fun createMobileIconsViewModel(): MobileIconsViewModel =
-        iconsViewModelFactory.create(mobileSubIdsState)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 67ea139..ab442b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
@@ -17,10 +17,12 @@
 package com.android.systemui.statusbar.pipeline.mobile.ui.binder
 
 import android.content.res.ColorStateList
+import android.view.View
 import android.view.View.GONE
 import android.view.View.VISIBLE
 import android.view.ViewGroup
 import android.widget.ImageView
+import android.widget.Space
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
@@ -28,8 +30,7 @@
 import com.android.systemui.R
 import com.android.systemui.common.ui.binder.IconViewBinder
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
-import kotlinx.coroutines.flow.collect
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
@@ -38,11 +39,16 @@
     @JvmStatic
     fun bind(
         view: ViewGroup,
-        viewModel: MobileIconViewModel,
+        viewModel: LocationBasedMobileViewModel,
     ) {
+        val activityContainer = view.requireViewById<View>(R.id.inout_container)
+        val activityIn = view.requireViewById<ImageView>(R.id.mobile_in)
+        val activityOut = view.requireViewById<ImageView>(R.id.mobile_out)
         val networkTypeView = view.requireViewById<ImageView>(R.id.mobile_type)
         val iconView = view.requireViewById<ImageView>(R.id.mobile_signal)
         val mobileDrawable = SignalDrawable(view.context).also { iconView.setImageDrawable(it) }
+        val roamingView = view.requireViewById<ImageView>(R.id.mobile_roaming)
+        val roamingSpace = view.requireViewById<Space>(R.id.mobile_roaming_space)
 
         view.isVisible = true
         iconView.isVisible = true
@@ -64,12 +70,32 @@
                     }
                 }
 
+                // Set the roaming indicator
+                launch {
+                    viewModel.roaming.distinctUntilChanged().collect { isRoaming ->
+                        roamingView.isVisible = isRoaming
+                        roamingSpace.isVisible = isRoaming
+                    }
+                }
+
+                // Set the activity indicators
+                launch { viewModel.activityInVisible.collect { activityIn.isVisible = it } }
+
+                launch { viewModel.activityOutVisible.collect { activityOut.isVisible = it } }
+
+                launch {
+                    viewModel.activityContainerVisible.collect { activityContainer.isVisible = it }
+                }
+
                 // Set the tint
                 launch {
                     viewModel.tint.collect { tint ->
                         val tintList = ColorStateList.valueOf(tint)
                         iconView.imageTintList = tintList
                         networkTypeView.imageTintList = tintList
+                        roamingView.imageTintList = tintList
+                        activityIn.imageTintList = tintList
+                        activityOut.imageTintList = tintList
                     }
                 }
             }
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 0ab7bcd..e86fee2 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
@@ -24,7 +24,7 @@
 import com.android.systemui.statusbar.BaseStatusBarFrameLayout
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconBinder
-import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModel
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.LocationBasedMobileViewModel
 import java.util.ArrayList
 
 class ModernStatusBarMobileView(
@@ -71,7 +71,7 @@
         fun constructAndBind(
             context: Context,
             slot: String,
-            viewModel: MobileIconViewModel,
+            viewModel: LocationBasedMobileViewModel,
         ): ModernStatusBarMobileView {
             return (LayoutInflater.from(context)
                     .inflate(R.layout.status_bar_mobile_signal_group_new, null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
new file mode 100644
index 0000000..b0dc41f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileViewModel.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for an individual mobile icon that embeds the notion of a [StatusBarLocation]. This
+ * allows the mobile icon to change some view parameters at different locations
+ *
+ * @param commonImpl for convenience, this class wraps a base interface that can provides all of the
+ * common implementations between locations. See [MobileIconViewModel]
+ */
+abstract class LocationBasedMobileViewModel(
+    val commonImpl: MobileIconViewModelCommon,
+    val logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon by commonImpl {
+    abstract val tint: Flow<Int>
+
+    companion object {
+        fun viewModelForLocation(
+            commonImpl: MobileIconViewModelCommon,
+            logger: ConnectivityPipelineLogger,
+            loc: StatusBarLocation,
+        ): LocationBasedMobileViewModel =
+            when (loc) {
+                StatusBarLocation.HOME -> HomeMobileIconViewModel(commonImpl, logger)
+                StatusBarLocation.KEYGUARD -> KeyguardMobileIconViewModel(commonImpl, logger)
+                StatusBarLocation.QS -> QsMobileIconViewModel(commonImpl, logger)
+            }
+    }
+}
+
+class HomeMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.CYAN)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "HOME tint(${commonImpl.subscriptionId})")
+}
+
+class QsMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.GREEN)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "QS tint(${commonImpl.subscriptionId})")
+}
+
+class KeyguardMobileIconViewModel(
+    commonImpl: MobileIconViewModelCommon,
+    logger: ConnectivityPipelineLogger,
+) : MobileIconViewModelCommon, LocationBasedMobileViewModel(commonImpl, logger) {
+    override val tint: Flow<Int> =
+        flowOf(Color.MAGENTA)
+            .distinctUntilChanged()
+            .logOutputChange(logger, "KEYGUARD tint(${commonImpl.subscriptionId})")
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8ebd718..2d6ac4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -16,20 +16,40 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
-import android.graphics.Color
 import com.android.settingslib.graph.SignalDrawable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconInteractor
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
-import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
+
+/** Common interface for all of the location-based mobile icon view models. */
+interface MobileIconViewModelCommon {
+    val subscriptionId: Int
+    /** An int consumable by [SignalDrawable] for display */
+    val iconId: Flow<Int>
+    val roaming: Flow<Boolean>
+    /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
+    val networkTypeIcon: Flow<Icon?>
+    val activityInVisible: Flow<Boolean>
+    val activityOutVisible: Flow<Boolean>
+    val activityContainerVisible: Flow<Boolean>
+}
 
 /**
  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -37,24 +57,29 @@
  * subscription's information.
  *
  * There will be exactly one [MobileIconViewModel] per filtered subscription offered from
- * [MobileIconsInteractor.filteredSubscriptions]
+ * [MobileIconsInteractor.filteredSubscriptions].
  *
- * TODO: figure out where carrier merged and VCN models go (probably here?)
+ * For the sake of keeping log spam in check, every flow funding the [MobileIconViewModelCommon]
+ * interface is implemented as a [StateFlow]. This ensures that each location-based mobile icon view
+ * model gets the exact same information, as well as allows us to log that unified state only once
+ * per icon.
  */
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconViewModel
 constructor(
-    val subscriptionId: Int,
+    override val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     logger: ConnectivityPipelineLogger,
-) {
+    constants: ConnectivityConstants,
+    scope: CoroutineScope,
+) : MobileIconViewModelCommon {
     /** Whether or not to show the error state of [SignalDrawable] */
     private val showExclamationMark: Flow<Boolean> =
         iconInteractor.isDefaultDataEnabled.mapLatest { !it }
 
-    /** An int consumable by [SignalDrawable] for display */
-    val iconId: Flow<Int> =
+    override val iconId: Flow<Int> = run {
+        val initial = SignalDrawable.getEmptyState(iconInteractor.numberOfLevels.value)
         combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
                 level,
                 numberOfLevels,
@@ -62,30 +87,97 @@
                 SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
             }
             .distinctUntilChanged()
-            .logOutputChange(logger, "iconId($subscriptionId)")
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "iconId",
+                initialValue = initial,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
+    }
 
-    /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
-    val networkTypeIcon: Flow<Icon?> =
+    override val networkTypeIcon: Flow<Icon?> =
         combine(
-            iconInteractor.networkTypeIconGroup,
-            iconInteractor.isDataConnected,
-            iconInteractor.isDataEnabled,
-            iconInteractor.isDefaultConnectionFailed,
-            iconInteractor.alwaysShowDataRatIcon,
-        ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
-            val desc =
-                if (networkTypeIconGroup.dataContentDescription != 0)
-                    ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
-                else null
-            val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
-            return@combine when {
-                alwaysShow -> icon
-                !dataConnected -> null
-                !dataEnabled -> null
-                failedConnection -> null
-                else -> icon
+                iconInteractor.networkTypeIconGroup,
+                iconInteractor.isDataConnected,
+                iconInteractor.isDataEnabled,
+                iconInteractor.isDefaultConnectionFailed,
+                iconInteractor.alwaysShowDataRatIcon,
+            ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection, alwaysShow ->
+                val desc =
+                    if (networkTypeIconGroup.dataContentDescription != 0)
+                        ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+                    else null
+                val icon = Icon.Resource(networkTypeIconGroup.dataType, desc)
+                return@combine when {
+                    alwaysShow -> icon
+                    !dataConnected -> null
+                    !dataEnabled -> null
+                    failedConnection -> null
+                    else -> icon
+                }
             }
+            .distinctUntilChanged()
+            .onEach {
+                // This is done as an onEach side effect since Icon is not Diffable (yet)
+                iconInteractor.tableLogBuffer.logChange(
+                    prefix = "",
+                    columnName = "networkTypeIcon",
+                    value = it.toString(),
+                )
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    override val roaming: StateFlow<Boolean> =
+        iconInteractor.isRoaming
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "roaming",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    private val activity: Flow<DataActivityModel?> =
+        if (!constants.shouldShowActivityConfig) {
+            flowOf(null)
+        } else {
+            iconInteractor.activity
         }
 
-    val tint: Flow<Int> = flowOf(Color.CYAN)
+    override val activityInVisible: Flow<Boolean> =
+        activity
+            .map { it?.hasActivityIn ?: false }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityInVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val activityOutVisible: Flow<Boolean> =
+        activity
+            .map { it?.hasActivityOut ?: false }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityOutVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val activityContainerVisible: Flow<Boolean> =
+        activity
+            .map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                iconInteractor.tableLogBuffer,
+                columnPrefix = "",
+                columnName = "activityContainerVisible",
+                initialValue = false,
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
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 2349cb7..b9318b1 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
@@ -14,16 +14,19 @@
  * limitations under the License.
  */
 
-@file:OptIn(InternalCoroutinesApi::class)
-
 package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
 
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import javax.inject.Inject
-import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
 
 /**
  * View model for describing the system's current mobile cellular connections. The result is a list
@@ -36,26 +39,50 @@
     val subscriptionIdsFlow: StateFlow<List<Int>>,
     private val interactor: MobileIconsInteractor,
     private val logger: ConnectivityPipelineLogger,
+    private val constants: ConnectivityConstants,
+    @Application private val scope: CoroutineScope,
 ) {
-    /** TODO: do we need to cache these? */
-    fun viewModelForSub(subId: Int): MobileIconViewModel =
-        MobileIconViewModel(
-            subId,
-            interactor.createMobileConnectionInteractorForSubId(subId),
-            logger
-        )
+    @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>()
+
+    init {
+        scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } }
+    }
+
+    fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel {
+        val common =
+            mobileIconSubIdCache[subId]
+                ?: MobileIconViewModel(
+                        subId,
+                        interactor.createMobileConnectionInteractorForSubId(subId),
+                        logger,
+                        constants,
+                        scope,
+                    )
+                    .also { mobileIconSubIdCache[subId] = it }
+
+        return LocationBasedMobileViewModel.viewModelForLocation(common, logger, location)
+    }
+
+    private fun removeInvalidModelsFromCache(subIds: List<Int>) {
+        val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) }
+        subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) }
+    }
 
     class Factory
     @Inject
     constructor(
         private val interactor: MobileIconsInteractor,
         private val logger: ConnectivityPipelineLogger,
+        private val constants: ConnectivityConstants,
+        @Application private val scope: CoroutineScope,
     ) {
         fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
             return MobileIconsViewModel(
                 subscriptionIdsFlow,
                 interactor,
                 logger,
+                constants,
+                scope,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
index 6efb10f..0c9b86c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -16,8 +16,10 @@
 
 package com.android.systemui.statusbar.pipeline.shared
 
+import android.content.Context
 import android.telephony.TelephonyManager
 import com.android.systemui.Dumpable
+import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
@@ -32,15 +34,25 @@
 @SysUISingleton
 class ConnectivityConstants
 @Inject
-constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
+constructor(
+    context: Context,
+    dumpManager: DumpManager,
+    telephonyManager: TelephonyManager,
+) : Dumpable {
     init {
-        dumpManager.registerDumpable("${SB_LOGGING_TAG}Constants", this)
+        dumpManager.registerNormalDumpable("${SB_LOGGING_TAG}Constants", this)
     }
 
     /** True if this device has the capability for data connections and false otherwise. */
     val hasDataCapabilities = telephonyManager.isDataCapable
 
+    /** True if we should show the activityIn/activityOut icons and false otherwise */
+    val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.apply { println("hasDataCapabilities=$hasDataCapabilities") }
+        pw.apply {
+            println("hasDataCapabilities=$hasDataCapabilities")
+            println("shouldShowActivityConfig=$shouldShowActivityConfig")
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
index d3cf32f..d3ff357 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -18,8 +18,11 @@
 
 import android.net.Network
 import android.net.NetworkCapabilities
-import com.android.systemui.log.dagger.StatusBarConnectivityLog
+import android.telephony.ServiceState
+import android.telephony.SignalStrength
+import android.telephony.TelephonyDisplayInfo
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.dagger.StatusBarConnectivityLog
 import com.android.systemui.plugins.log.LogBuffer
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.toString
@@ -28,7 +31,9 @@
 import kotlinx.coroutines.flow.onEach
 
 @SysUISingleton
-class ConnectivityPipelineLogger @Inject constructor(
+class ConnectivityPipelineLogger
+@Inject
+constructor(
     @StatusBarConnectivityLog private val buffer: LogBuffer,
 ) {
     /**
@@ -37,34 +42,23 @@
      * Use this method for inputs that don't have any extra information besides their callback name.
      */
     fun logInputChange(callbackName: String) {
+        buffer.log(SB_LOGGING_TAG, LogLevel.INFO, { str1 = callbackName }, { "Input: $str1" })
+    }
+
+    /** Logs a change in one of the **raw inputs** to the connectivity pipeline. */
+    fun logInputChange(callbackName: String, changeInfo: String?) {
         buffer.log(
             SB_LOGGING_TAG,
             LogLevel.INFO,
-            { str1 = callbackName },
-            { "Input: $str1" }
+            {
+                str1 = callbackName
+                str2 = changeInfo
+            },
+            { "Input: $str1: $str2" }
         )
     }
 
-    /**
-     * Logs a change in one of the **raw inputs** to the connectivity pipeline.
-     */
-    fun logInputChange(callbackName: String, changeInfo: String?) {
-        buffer.log(
-                SB_LOGGING_TAG,
-                LogLevel.INFO,
-                {
-                    str1 = callbackName
-                    str2 = changeInfo
-                },
-                {
-                    "Input: $str1: $str2"
-                }
-        )
-    }
-
-    /**
-     * Logs a **data transformation** that we performed within the connectivity pipeline.
-     */
+    /** Logs a **data transformation** that we performed within the connectivity pipeline. */
     fun logTransformation(transformationName: String, oldValue: Any?, newValue: Any?) {
         if (oldValue == newValue) {
             buffer.log(
@@ -74,9 +68,7 @@
                     str1 = transformationName
                     str2 = oldValue.toString()
                 },
-                {
-                    "Transform: $str1: $str2 (transformation didn't change it)"
-                }
+                { "Transform: $str1: $str2 (transformation didn't change it)" }
             )
         } else {
             buffer.log(
@@ -87,27 +79,21 @@
                     str2 = oldValue.toString()
                     str3 = newValue.toString()
                 },
-                {
-                    "Transform: $str1: $str2 -> $str3"
-                }
+                { "Transform: $str1: $str2 -> $str3" }
             )
         }
     }
 
-    /**
-     * Logs a change in one of the **outputs** to the connectivity pipeline.
-     */
+    /** Logs a change in one of the **outputs** to the connectivity pipeline. */
     fun logOutputChange(outputParamName: String, changeInfo: String) {
         buffer.log(
-                SB_LOGGING_TAG,
-                LogLevel.INFO,
-                {
-                    str1 = outputParamName
-                    str2 = changeInfo
-                },
-                {
-                    "Output: $str1: $str2"
-                }
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                str1 = outputParamName
+                str2 = changeInfo
+            },
+            { "Output: $str1: $str2" }
         )
     }
 
@@ -119,9 +105,7 @@
                 int1 = network.getNetId()
                 str1 = networkCapabilities.toString()
             },
-            {
-                "onCapabilitiesChanged: net=$int1 capabilities=$str1"
-            }
+            { "onCapabilitiesChanged: net=$int1 capabilities=$str1" }
         )
     }
 
@@ -129,21 +113,93 @@
         buffer.log(
             SB_LOGGING_TAG,
             LogLevel.INFO,
+            { int1 = network.getNetId() },
+            { "onLost: net=$int1" }
+        )
+    }
+
+    fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
             {
-                int1 = network.getNetId()
+                int1 = subId
+                bool1 = serviceState.isEmergencyOnly
+                bool2 = serviceState.roaming
+                str1 = serviceState.operatorAlphaShort
             },
             {
-                "onLost: net=$int1"
+                "onServiceStateChanged: subId=$int1 emergencyOnly=$bool1 roaming=$bool2" +
+                    " operator=$str1"
             }
         )
     }
 
+    fun logOnSignalStrengthsChanged(signalStrength: SignalStrength, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                str1 = signalStrength.toString()
+            },
+            { "onSignalStrengthsChanged: subId=$int1 strengths=$str1" }
+        )
+    }
+
+    fun logOnDataConnectionStateChanged(dataState: Int, networkType: Int, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                int2 = dataState
+                str1 = networkType.toString()
+            },
+            { "onDataConnectionStateChanged: subId=$int1 dataState=$int2 networkType=$str1" },
+        )
+    }
+
+    fun logOnDataActivity(direction: Int, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                int2 = direction
+            },
+            { "onDataActivity: subId=$int1 direction=$int2" },
+        )
+    }
+
+    fun logOnCarrierNetworkChange(active: Boolean, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                bool1 = active
+            },
+            { "onCarrierNetworkChange: subId=$int1 active=$bool1" },
+        )
+    }
+
+    fun logOnDisplayInfoChanged(displayInfo: TelephonyDisplayInfo, subId: Int) {
+        buffer.log(
+            SB_LOGGING_TAG,
+            LogLevel.INFO,
+            {
+                int1 = subId
+                str1 = displayInfo.toString()
+            },
+            { "onDisplayInfoChanged: subId=$int1 displayInfo=$str1" },
+        )
+    }
+
     companion object {
         const val SB_LOGGING_TAG = "SbConnectivity"
 
-        /**
-         * Log a change in one of the **inputs** to the connectivity pipeline.
-         */
+        /** Log a change in one of the **inputs** to the connectivity pipeline. */
         fun Flow<Unit>.logInputChange(
             logger: ConnectivityPipelineLogger,
             inputParamName: String,
@@ -155,26 +211,26 @@
          * Log a change in one of the **inputs** to the connectivity pipeline.
          *
          * @param prettyPrint an optional function to transform the value into a readable string.
-         *   [toString] is used if no custom function is provided.
+         * [toString] is used if no custom function is provided.
          */
         fun <T> Flow<T>.logInputChange(
             logger: ConnectivityPipelineLogger,
             inputParamName: String,
             prettyPrint: (T) -> String = { it.toString() }
         ): Flow<T> {
-            return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+            return this.onEach { logger.logInputChange(inputParamName, prettyPrint(it)) }
         }
 
         /**
          * Log a change in one of the **outputs** to the connectivity pipeline.
          *
          * @param prettyPrint an optional function to transform the value into a readable string.
-         *   [toString] is used if no custom function is provided.
+         * [toString] is used if no custom function is provided.
          */
         fun <T> Flow<T>.logOutputChange(
-                logger: ConnectivityPipelineLogger,
-                outputParamName: String,
-                prettyPrint: (T) -> String = { it.toString() }
+            logger: ConnectivityPipelineLogger,
+            outputParamName: String,
+            prettyPrint: (T) -> String = { it.toString() }
         ): Flow<T> {
             return this.onEach { logger.logOutputChange(outputParamName, prettyPrint(it)) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
new file mode 100644
index 0000000..05d0714
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/model/DataActivityModel.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.shared.data.model
+
+import android.net.wifi.WifiManager
+import android.telephony.Annotation
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
+
+/** Provides information about the current data activity direction */
+data class DataActivityModel(
+    /** True if the connection has activity in (download). */
+    val hasActivityIn: Boolean,
+    /** True if the connection has activity out (upload). */
+    val hasActivityOut: Boolean,
+) : Diffable<DataActivityModel> {
+    override fun logDiffs(prevVal: DataActivityModel, row: TableRowLogger) {
+        if (prevVal.hasActivityIn != hasActivityIn) {
+            row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+        }
+        if (prevVal.hasActivityOut != hasActivityOut) {
+            row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+        }
+    }
+
+    override fun logFull(row: TableRowLogger) {
+        row.logChange(COL_ACTIVITY_IN, hasActivityIn)
+        row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
+    }
+}
+
+const val ACTIVITY_PREFIX = "dataActivity"
+private const val COL_ACTIVITY_IN = "in"
+private const val COL_ACTIVITY_OUT = "out"
+
+fun @receiver:Annotation.DataActivityType Int.toMobileDataActivityModel(): DataActivityModel =
+    when (this) {
+        TelephonyManager.DATA_ACTIVITY_IN ->
+            DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+        TelephonyManager.DATA_ACTIVITY_OUT ->
+            DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+        TelephonyManager.DATA_ACTIVITY_INOUT ->
+            DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+        else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+    }
+
+fun Int.toWifiDataActivityModel(): DataActivityModel =
+    when (this) {
+        WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN ->
+            DataActivityModel(hasActivityIn = true, hasActivityOut = false)
+        WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT ->
+            DataActivityModel(hasActivityIn = false, hasActivityOut = true)
+        WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT ->
+            DataActivityModel(hasActivityIn = true, hasActivityOut = true)
+        else -> DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index 0c9c1cc..5ccd6f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -42,9 +42,9 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.SB_LOGGING_TAG
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.ACTIVITY_PREFIX
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -74,7 +74,7 @@
     val wifiNetwork: StateFlow<WifiNetworkModel>
 
     /** Observable for the current wifi network activity. */
-    val wifiActivity: StateFlow<WifiActivityModel>
+    val wifiActivity: StateFlow<DataActivityModel>
 }
 
 /** Real implementation of [WifiRepository]. */
@@ -230,7 +230,7 @@
             initialValue = WIFI_NETWORK_DEFAULT
         )
 
-    override val wifiActivity: StateFlow<WifiActivityModel> =
+    override val wifiActivity: StateFlow<DataActivityModel> =
             if (wifiManager == null) {
                 Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
                 flowOf(ACTIVITY_DEFAULT)
@@ -238,7 +238,7 @@
                 conflatedCallbackFlow {
                     val callback = TrafficStateCallback { state ->
                         logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
-                        trySend(trafficStateToWifiActivityModel(state))
+                        trySend(state.toWifiDataActivityModel())
                     }
                     wifiManager.registerTrafficStateCallback(mainExecutor, callback)
                     awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
@@ -256,7 +256,9 @@
                 )
 
     companion object {
-        val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        private const val ACTIVITY_PREFIX = "wifiActivity"
+
+        val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         // Start out with no known wifi network.
         // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
         // initial fetch to get a starting wifi network. But, it uses a deprecated API
@@ -265,15 +267,6 @@
         // NetworkCallback inside [wifiNetwork] for our wifi network information.
         val WIFI_NETWORK_DEFAULT = WifiNetworkModel.Inactive
 
-        private fun trafficStateToWifiActivityModel(state: Int): WifiActivityModel {
-            return WifiActivityModel(
-                hasActivityIn = state == TrafficStateCallback.DATA_ACTIVITY_IN ||
-                    state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
-                hasActivityOut = state == TrafficStateCallback.DATA_ACTIVITY_OUT ||
-                    state == TrafficStateCallback.DATA_ACTIVITY_INOUT,
-            )
-        }
-
         private fun networkCapabilitiesToWifiInfo(
             networkCapabilities: NetworkCapabilities
         ): WifiInfo? {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
new file mode 100644
index 0000000..73bcdfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -0,0 +1,120 @@
+/*
+ * 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.data.repository
+
+import android.os.Bundle
+import androidx.annotation.VisibleForTesting
+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.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+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
+
+/**
+ * Provides the [WifiRepository] interface either through the [DemoWifiRepository] implementation,
+ * or the [WifiRepositoryImpl]'s prod implementation, based on the current demo mode value. In this
+ * way, downstream clients can all consist of real implementations and not care about which
+ * repository is responsible for the data. Graphically:
+ *
+ * ```
+ * RealRepository
+ *                 │
+ *                 ├──►RepositorySwitcher──►RealInteractor──►RealViewModel
+ *                 │
+ * DemoRepository
+ * ```
+ *
+ * When demo mode turns on, every flow will [flatMapLatest] to the current provider's version of
+ * that flow.
+ */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+class WifiRepositorySwitcher
+@Inject
+constructor(
+    private val realImpl: WifiRepositoryImpl,
+    private val demoImpl: DemoWifiRepository,
+    private val demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) : WifiRepository {
+    private val isDemoMode =
+        conflatedCallbackFlow {
+                val callback =
+                    object : DemoMode {
+                        override fun dispatchDemoCommand(command: String?, args: Bundle?) {
+                            // Don't care
+                        }
+
+                        override fun onDemoModeStarted() {
+                            demoImpl.startProcessingCommands()
+                            trySend(true)
+                        }
+
+                        override fun onDemoModeFinished() {
+                            demoImpl.stopProcessingCommands()
+                            trySend(false)
+                        }
+                    }
+
+                demoModeController.addCallback(callback)
+                awaitClose { demoModeController.removeCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), demoModeController.isInDemoMode)
+
+    @VisibleForTesting
+    val activeRepo =
+        isDemoMode
+            .mapLatest { isDemoMode ->
+                if (isDemoMode) {
+                    demoImpl
+                } else {
+                    realImpl
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl)
+
+    override val isWifiEnabled: StateFlow<Boolean> =
+        activeRepo
+            .flatMapLatest { it.isWifiEnabled }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiEnabled.value)
+
+    override val isWifiDefault: StateFlow<Boolean> =
+        activeRepo
+            .flatMapLatest { it.isWifiDefault }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.isWifiDefault.value)
+
+    override val wifiNetwork: StateFlow<WifiNetworkModel> =
+        activeRepo
+            .flatMapLatest { it.wifiNetwork }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
+
+    override val wifiActivity: StateFlow<DataActivityModel> =
+        activeRepo
+            .flatMapLatest { it.wifiActivity }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiActivity.value)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
new file mode 100644
index 0000000..c588945
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -0,0 +1,74 @@
+/*
+ * 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.data.repository.demo
+
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.demomode.DemoMode.COMMAND_NETWORK
+import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+
+/** Data source to map between demo mode commands and inputs into [DemoWifiRepository]'s flows */
+@SysUISingleton
+class DemoModeWifiDataSource
+@Inject
+constructor(
+    demoModeController: DemoModeController,
+    @Application scope: CoroutineScope,
+) {
+    private val demoCommandStream = demoModeController.demoFlowForCommand(COMMAND_NETWORK)
+    private val _wifiCommands = demoCommandStream.map { args -> args.toWifiEvent() }
+    val wifiEvents = _wifiCommands.shareIn(scope, SharingStarted.WhileSubscribed())
+
+    private fun Bundle.toWifiEvent(): FakeWifiEventModel? {
+        val wifi = getString("wifi") ?: return null
+        return if (wifi == "show") {
+            activeWifiEvent()
+        } else {
+            FakeWifiEventModel.WifiDisabled
+        }
+    }
+
+    private fun Bundle.activeWifiEvent(): FakeWifiEventModel.Wifi {
+        val level = getString("level")?.toInt()
+        val activity = getString("activity")?.toActivity()
+        val ssid = getString("ssid")
+        val validated = getString("fully").toBoolean()
+
+        return FakeWifiEventModel.Wifi(
+            level = level,
+            activity = activity,
+            ssid = ssid,
+            validated = validated,
+        )
+    }
+
+    private fun String.toActivity(): Int =
+        when (this) {
+            "inout" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT
+            "in" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN
+            "out" -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT
+            else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
new file mode 100644
index 0000000..7890074
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -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.statusbar.pipeline.wifi.data.repository.demo
+
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.launch
+
+/** Demo-able wifi repository to support SystemUI demo mode commands. */
+class DemoWifiRepository
+@Inject
+constructor(
+    private val dataSource: DemoModeWifiDataSource,
+    @Application private val scope: CoroutineScope,
+) : WifiRepository {
+    private var demoCommandJob: Job? = null
+
+    private val _isWifiEnabled = MutableStateFlow(false)
+    override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
+    private val _isWifiDefault = MutableStateFlow(false)
+    override val isWifiDefault: StateFlow<Boolean> = _isWifiDefault
+
+    private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
+    override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
+
+    private val _wifiActivity =
+        MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+    override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
+
+    fun startProcessingCommands() {
+        demoCommandJob =
+            scope.launch {
+                dataSource.wifiEvents.filterNotNull().collect { event -> processEvent(event) }
+            }
+    }
+
+    fun stopProcessingCommands() {
+        demoCommandJob?.cancel()
+    }
+
+    private fun processEvent(event: FakeWifiEventModel) =
+        when (event) {
+            is FakeWifiEventModel.Wifi -> processEnabledWifiState(event)
+            is FakeWifiEventModel.WifiDisabled -> processDisabledWifiState()
+        }
+
+    private fun processDisabledWifiState() {
+        _isWifiEnabled.value = false
+        _isWifiDefault.value = false
+        _wifiActivity.value = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiNetwork.value = WifiNetworkModel.Inactive
+    }
+
+    private fun processEnabledWifiState(event: FakeWifiEventModel.Wifi) {
+        _isWifiEnabled.value = true
+        _isWifiDefault.value = true
+        _wifiActivity.value =
+            event.activity?.toWifiDataActivityModel()
+                ?: DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+        _wifiNetwork.value = event.toWifiNetworkModel()
+    }
+
+    private fun FakeWifiEventModel.Wifi.toWifiNetworkModel(): WifiNetworkModel =
+        WifiNetworkModel.Active(
+            networkId = DEMO_NET_ID,
+            isValidated = validated ?: true,
+            level = level,
+            ssid = ssid,
+
+            // These fields below aren't supported in demo mode, since they aren't needed to satisfy
+            // the interface.
+            isPasspointAccessPoint = false,
+            isOnlineSignUpForPasspointAccessPoint = false,
+            passpointProviderFriendlyName = null,
+        )
+
+    companion object {
+        private const val DEMO_NET_ID = 1234
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
new file mode 100644
index 0000000..2353fb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.data.repository.demo.model
+
+/**
+ * Model for demo wifi commands, ported from [NetworkControllerImpl]
+ *
+ * Nullable fields represent optional command line arguments
+ */
+sealed interface FakeWifiEventModel {
+    data class Wifi(
+        val level: Int?,
+        val activity: Int?,
+        val ssid: String?,
+        val validated: Boolean?,
+    ) : FakeWifiEventModel
+
+    object WifiDisabled : FakeWifiEventModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
index ec935fe..93041ce 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt
@@ -19,10 +19,10 @@
 import android.net.wifi.WifiManager
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
@@ -50,8 +50,8 @@
     /** Our current wifi network. See [WifiNetworkModel]. */
     val wifiNetwork: Flow<WifiNetworkModel>
 
-    /** Our current wifi activity. See [WifiActivityModel]. */
-    val activity: StateFlow<WifiActivityModel>
+    /** Our current wifi activity. See [DataActivityModel]. */
+    val activity: StateFlow<DataActivityModel>
 
     /** True if we're configured to force-hide the wifi icon and false otherwise. */
     val isForceHidden: Flow<Boolean>
@@ -82,7 +82,7 @@
 
     override val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
 
-    override val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+    override val activity: StateFlow<DataActivityModel> = wifiRepository.wifiActivity
 
     override val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
         it.contains(ConnectivitySlot.WIFI)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index 3c0eb91..4f7fe28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -38,16 +38,12 @@
         dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this)
     }
 
-    /** True if we should show the activityIn/activityOut icons and false otherwise. */
-    val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
-
     /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
     val alwaysShowIconIfEnabled =
         context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
         pw.apply {
-            println("shouldShowActivityConfig=$shouldShowActivityConfig")
             println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
deleted file mode 100644
index a4ca41c..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
+++ /dev/null
@@ -1,47 +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.statusbar.pipeline.wifi.shared.model
-
-import com.android.systemui.log.table.Diffable
-import com.android.systemui.log.table.TableRowLogger
-
-/** Provides information on the current wifi activity. */
-data class WifiActivityModel(
-    /** True if the wifi has activity in (download). */
-    val hasActivityIn: Boolean,
-    /** True if the wifi has activity out (upload). */
-    val hasActivityOut: Boolean,
-) : Diffable<WifiActivityModel> {
-
-    override fun logDiffs(prevVal: WifiActivityModel, row: TableRowLogger) {
-        if (prevVal.hasActivityIn != hasActivityIn) {
-            row.logChange(COL_ACTIVITY_IN, hasActivityIn)
-        }
-        if (prevVal.hasActivityOut != hasActivityOut) {
-            row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
-        }
-    }
-
-    override fun logFull(row: TableRowLogger) {
-        row.logChange(COL_ACTIVITY_IN, hasActivityIn)
-        row.logChange(COL_ACTIVITY_OUT, hasActivityOut)
-    }
-}
-
-const val ACTIVITY_PREFIX = "wifiActivity"
-private const val COL_ACTIVITY_IN = "in"
-private const val COL_ACTIVITY_OUT = "out"
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 ec7ba65..ab464cc 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
@@ -37,10 +37,10 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -147,8 +147,8 @@
             )
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
-    private val activity: Flow<WifiActivityModel?> =
-        if (!wifiConstants.shouldShowActivityConfig) {
+    private val activity: Flow<DataActivityModel?> =
+        if (!connectivityConstants.shouldShowActivityConfig) {
             flowOf(null)
         } else {
             combine(interactor.activity, interactor.ssid) { activity, ssid ->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d8a8c5d..c9ed0cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -47,6 +47,7 @@
 import android.view.View;
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
 import android.view.ViewRootImpl;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
@@ -57,7 +58,6 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
-import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -133,6 +133,7 @@
     private RevealParams mRevealParams;
     private Rect mContentBackgroundBounds;
     private boolean mIsFocusAnimationFlagActive;
+    private boolean mIsAnimatingAppearance = false;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -142,10 +143,6 @@
     private boolean mSending;
     private NotificationViewWrapper mWrapper;
 
-    private Integer mDefocusTargetHeight = null;
-    private boolean mIsAnimatingAppearance = false;
-
-
     // TODO(b/193539698): remove this; views shouldn't have access to their controller, and places
     //  that need the controller shouldn't have access to the view
     private RemoteInputViewController mViewController;
@@ -423,18 +420,6 @@
         return mIsAnimatingAppearance;
     }
 
-    /**
-     * View will ensure to use at most the provided defocusTargetHeight, when defocusing animated.
-     * This is to ensure that the parent can resize itself to the targetHeight while the defocus
-     * animation of the RemoteInputView is running.
-     *
-     * @param defocusTargetHeight The target height the parent will resize itself to. If null, the
-     *                            RemoteInputView will not resize itself.
-     */
-    public void setDefocusTargetHeight(Integer defocusTargetHeight) {
-        mDefocusTargetHeight = defocusTargetHeight;
-    }
-
     @VisibleForTesting
     void onDefocus(boolean animate, boolean logClose) {
         mController.removeRemoteInput(mEntry, mToken);
@@ -443,35 +428,28 @@
         // During removal, we get reattached and lose focus. Not hiding in that
         // case to prevent flicker.
         if (!mRemoved) {
-            if (animate && mIsFocusAnimationFlagActive) {
-                Animator animator = getDefocusAnimator();
+            ViewGroup parent = (ViewGroup) getParent();
+            if (animate && parent != null && mIsFocusAnimationFlagActive) {
 
-                // When defocusing, the notification needs to shrink. Therefore, we need to free
-                // up the space that is needed for the RemoteInputView. This is done by setting
-                // a negative top margin of the height difference of the RemoteInputView and its
-                // sibling (the actions_container_layout containing the Reply button)
-                if (mDefocusTargetHeight != null && mDefocusTargetHeight < getHeight()
-                        && mDefocusTargetHeight >= 0
-                        && getLayoutParams() instanceof FrameLayout.LayoutParams) {
-                    int heightToShrink = getHeight() - mDefocusTargetHeight;
-                    FrameLayout.LayoutParams layoutParams =
-                            (FrameLayout.LayoutParams) getLayoutParams();
-                    layoutParams.topMargin = -heightToShrink;
-                    setLayoutParams(layoutParams);
-                    ((ViewGroup) getParent().getParent()).setClipChildren(false);
-                }
 
+                ViewGroup grandParent = (ViewGroup) parent.getParent();
+                ViewGroupOverlay overlay = parent.getOverlay();
+
+                // After adding this RemoteInputView to the overlay of the parent (and thus removing
+                // it from the parent itself), the parent will shrink in height. This causes the
+                // overlay to be moved. To correct the position of the overlay we need to offset it.
+                int overlayOffsetY = getMaxSiblingHeight() - getHeight();
+                overlay.add(this);
+                if (grandParent != null) grandParent.setClipChildren(false);
+
+                Animator animator = getDefocusAnimator(overlayOffsetY);
+                View self = this;
                 animator.addListener(new AnimatorListenerAdapter() {
                     @Override
                     public void onAnimationEnd(Animator animation) {
-                        //reset top margin after the animation
-                        if (getLayoutParams() instanceof FrameLayout.LayoutParams) {
-                            FrameLayout.LayoutParams layoutParams =
-                                    (FrameLayout.LayoutParams) getLayoutParams();
-                            layoutParams.topMargin = 0;
-                            setLayoutParams(layoutParams);
-                            ((ViewGroup) getParent().getParent()).setClipChildren(true);
-                        }
+                        overlay.remove(self);
+                        parent.addView(self);
+                        if (grandParent != null) grandParent.setClipChildren(true);
                         setVisibility(GONE);
                         if (mWrapper != null) {
                             mWrapper.setRemoteInputVisible(false);
@@ -609,7 +587,7 @@
     }
 
     /**
-     * Sets whether the feature flag for the updated inline reply animation is active or not.
+     * Sets whether the feature flag for the revised inline reply animation is active or not.
      * @param active
      */
     public void setIsFocusAnimationFlagActive(boolean active) {
@@ -846,6 +824,23 @@
         }
     }
 
+    /**
+     * @return max sibling height (0 in case of no siblings)
+     */
+    public int getMaxSiblingHeight() {
+        ViewGroup parentView = (ViewGroup) getParent();
+        int maxHeight = 0;
+        if (parentView == null) return 0;
+        for (int i = 0; i < parentView.getChildCount(); i++) {
+            View siblingView = parentView.getChildAt(i);
+            if (siblingView != this) maxHeight = Math.max(maxHeight, siblingView.getHeight());
+        }
+        return maxHeight;
+    }
+
+    /**
+     * Creates an animator for the focus animation.
+     */
     private Animator getFocusAnimator(View crossFadeView) {
         final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
         alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
@@ -854,7 +849,7 @@
 
         ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
         scaleAnimator.addUpdateListener(valueAnimator -> {
-            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0);
         });
         scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
         scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
@@ -875,21 +870,26 @@
         return animatorSet;
     }
 
-    private Animator getDefocusAnimator() {
+    /**
+     * Creates an animator for the defocus animation.
+     *
+     * @param offsetY The RemoteInputView will be offset by offsetY during the animation
+     */
+    private Animator getDefocusAnimator(int offsetY) {
         final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
         alphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
         alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
 
         ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
         scaleAnimator.addUpdateListener(valueAnimator -> {
-            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY);
         });
         scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
         scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
         scaleAnimator.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation, boolean isReverse) {
-                setFocusAnimationScaleY(1f);
+                setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */);
             }
         });
 
@@ -901,15 +901,21 @@
     /**
      * Sets affected view properties for a vertical scale animation
      *
-     * @param scaleY desired vertical view scale
+     * @param scaleY         desired vertical view scale
+     * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation
      */
-    private void setFocusAnimationScaleY(float scaleY) {
+    private void setFocusAnimationScaleY(float scaleY, int verticalOffset) {
         int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
-        mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+        Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
                 mContentView.getHeight() - verticalBoundOffset);
-        mContentBackground.setBounds(mContentBackgroundBounds);
+        mContentBackground.setBounds(contentBackgroundBounds);
         mContentView.setBackground(mContentBackground);
-        setTranslationY(verticalBoundOffset);
+        if (scaleY == 1f) {
+            mContentBackgroundBounds = null;
+        } else {
+            mContentBackgroundBounds = contentBackgroundBounds;
+        }
+        setTranslationY(verticalBoundOffset + verticalOffset);
     }
 
     /** Handler for button click on send action in IME. */
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index db7315f..ad48e21 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -30,12 +30,16 @@
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
 import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
+import androidx.annotation.CallSuper
 import com.android.systemui.CoreStartable
+import com.android.systemui.Dumpable
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.util.wakelock.WakeLock
+import java.io.PrintWriter
 
 /**
  * A generic controller that can temporarily display a new view in a new window.
@@ -69,11 +73,12 @@
     @Main private val mainExecutor: DelayableExecutor,
     private val accessibilityManager: AccessibilityManager,
     private val configurationController: ConfigurationController,
+    private val dumpManager: DumpManager,
     private val powerManager: PowerManager,
     @LayoutRes private val viewLayoutRes: Int,
     private val wakeLockBuilder: WakeLock.Builder,
     private val systemClock: SystemClock,
-) : CoreStartable {
+) : CoreStartable, Dumpable {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
      * all subclasses.
@@ -109,6 +114,11 @@
         return activeViews.getOrNull(0)
     }
 
+    @CallSuper
+    override fun start() {
+        dumpManager.registerNormalDumpable(this)
+    }
+
     /**
      * Displays the view with the provided [newInfo].
      *
@@ -321,7 +331,7 @@
             return
         }
 
-        removeViewFromWindow(displayInfo)
+        removeViewFromWindow(displayInfo, removalReason)
 
         // Prune anything that's already timed out before determining if we should re-display a
         // different chipbar.
@@ -348,14 +358,14 @@
         removeViewFromWindow(displayInfo)
     }
 
-    private fun removeViewFromWindow(displayInfo: DisplayInfo) {
+    private fun removeViewFromWindow(displayInfo: DisplayInfo, removalReason: String? = null) {
         val view = displayInfo.view
         if (view == null) {
             logger.logViewRemovalIgnored(displayInfo.info.id, "View is null")
             return
         }
         displayInfo.view = null // Need other places??
-        animateViewOut(view) {
+        animateViewOut(view, removalReason) {
             windowManager.removeView(view)
             displayInfo.wakeLock?.release(displayInfo.info.wakeReason)
         }
@@ -382,6 +392,19 @@
         }
     }
 
+    @Synchronized
+    @CallSuper
+    override fun dump(pw: PrintWriter, args: Array<out String>) {
+        pw.println("Current time millis: ${systemClock.currentTimeMillis()}")
+        pw.println("Active views size: ${activeViews.size}")
+        activeViews.forEachIndexed { index, displayInfo ->
+            pw.println("View[$index]:")
+            pw.println("  info=${displayInfo.info}")
+            pw.println("  hasView=${displayInfo.view != null}")
+            pw.println("  timeExpiration=${displayInfo.timeExpirationMillis}")
+        }
+    }
+
     /**
      * A method implemented by subclasses to update [currentView] based on [newInfo].
      */
@@ -405,7 +428,11 @@
      *
      * @param onAnimationEnd an action that *must* be run once the animation finishes successfully.
      */
-    internal open fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+    internal open fun animateViewOut(
+        view: ViewGroup,
+        removalReason: String? = null,
+        onAnimationEnd: Runnable
+    ) {
         onAnimationEnd.run()
     }
 
@@ -445,8 +472,6 @@
          */
         var cancelViewTimeout: Runnable?,
     )
-
-    // TODO(b/258019006): Add a dump method that dumps the currently active views.
 }
 
 private const val REMOVAL_REASON_TIMEOUT = "TIMEOUT"
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 f37ef82..52980c3 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -41,6 +41,7 @@
 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.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -75,6 +76,7 @@
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
+        dumpManager: DumpManager,
         powerManager: PowerManager,
         private val falsingManager: FalsingManager,
         private val falsingCollector: FalsingCollector,
@@ -89,6 +91,7 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
+        dumpManager,
         powerManager,
         R.layout.chipbar,
         wakeLockBuilder,
@@ -208,7 +211,7 @@
         )
     }
 
-    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+    override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         val innerView = view.getInnerView()
         innerView.accessibilityLiveRegion = ACCESSIBILITY_LIVE_REGION_NONE
         ViewHierarchyAnimator.animateRemoval(
@@ -225,8 +228,6 @@
         return requireViewById(R.id.chipbar_inner)
     }
 
-    override fun start() {}
-
     override fun getTouchableRegion(view: View, outRect: Rect) {
         viewUtil.setRectToViewWindowLocation(view, outRect)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
index 1f44434..2464886 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/DemoModeFragment.java
@@ -33,6 +33,7 @@
 import com.android.systemui.demomode.DemoMode;
 import com.android.systemui.demomode.DemoModeAvailabilityTracker;
 import com.android.systemui.demomode.DemoModeController;
+import com.android.systemui.util.settings.GlobalSettings;
 
 public class DemoModeFragment extends PreferenceFragment implements OnPreferenceChangeListener {
 
@@ -54,13 +55,15 @@
     private SwitchPreference mOnSwitch;
 
     private DemoModeController mDemoModeController;
+    private GlobalSettings mGlobalSettings;
     private Tracker mDemoModeTracker;
 
     // We are the only ones who ever call this constructor, so don't worry about the warning
     @SuppressLint("ValidFragment")
-    public DemoModeFragment(DemoModeController demoModeController) {
+    public DemoModeFragment(DemoModeController demoModeController, GlobalSettings globalSettings) {
         super();
         mDemoModeController = demoModeController;
+        mGlobalSettings = globalSettings;
     }
 
 
@@ -80,7 +83,7 @@
         screen.addPreference(mOnSwitch);
         setPreferenceScreen(screen);
 
-        mDemoModeTracker = new Tracker(context);
+        mDemoModeTracker = new Tracker(context, mGlobalSettings);
         mDemoModeTracker.startTracking();
         updateDemoModeEnabled();
         updateDemoModeOn();
@@ -202,8 +205,8 @@
     }
 
     private class Tracker extends DemoModeAvailabilityTracker {
-        Tracker(Context context) {
-            super(context);
+        Tracker(Context context, GlobalSettings globalSettings) {
+            super(context, globalSettings);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
index 3231aec..32ecb67 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java
@@ -33,6 +33,7 @@
 import com.android.systemui.R;
 import com.android.systemui.demomode.DemoModeController;
 import com.android.systemui.fragments.FragmentService;
+import com.android.systemui.util.settings.GlobalSettings;
 
 import javax.inject.Inject;
 
@@ -44,12 +45,18 @@
 
     private final DemoModeController mDemoModeController;
     private final TunerService mTunerService;
+    private final GlobalSettings mGlobalSettings;
 
     @Inject
-    TunerActivity(DemoModeController demoModeController, TunerService tunerService) {
+    TunerActivity(
+            DemoModeController demoModeController,
+            TunerService tunerService,
+            GlobalSettings globalSettings
+    ) {
         super();
         mDemoModeController = demoModeController;
         mTunerService = tunerService;
+        mGlobalSettings = globalSettings;
     }
 
     protected void onCreate(Bundle savedInstanceState) {
@@ -69,7 +76,7 @@
             boolean showDemoMode = action != null && action.equals(
                     "com.android.settings.action.DEMO_MODE");
             final PreferenceFragment fragment = showDemoMode
-                    ? new DemoModeFragment(mDemoModeController)
+                    ? new DemoModeFragment(mDemoModeController, mGlobalSettings)
                     : new TunerFragment(mTunerService);
             getFragmentManager().beginTransaction().replace(R.id.content_frame,
                     fragment, TAG_TUNER).commit();
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
index 9cca950..523cf68 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt
@@ -159,18 +159,24 @@
         ensureOverlayRemoved()
 
         val newRoot = SurfaceControlViewHost(context, context.display!!, wwm)
-        val newView =
-            LightRevealScrim(context, null).apply {
-                revealEffect = createLightRevealEffect()
-                isScrimOpaqueChangedListener = Consumer {}
-                revealAmount =
-                    when (reason) {
-                        FOLD -> TRANSPARENT
-                        UNFOLD -> BLACK
-                    }
-            }
-
         val params = getLayoutParams()
+        val newView =
+            LightRevealScrim(
+                    context,
+                    attrs = null,
+                    initialWidth = params.width,
+                    initialHeight = params.height
+                )
+                .apply {
+                    revealEffect = createLightRevealEffect()
+                    isScrimOpaqueChangedListener = Consumer {}
+                    revealAmount =
+                        when (reason) {
+                            FOLD -> TRANSPARENT
+                            UNFOLD -> BLACK
+                        }
+                }
+
         newRoot.setView(newView, params)
 
         if (onOverlayReady != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index b56c403..cd21a45 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -32,6 +32,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.users.EditUserInfoController;
+import com.android.settingslib.users.GrantAdminDialogController;
 import com.android.systemui.R;
 import com.android.systemui.plugins.ActivityStarter;
 
@@ -60,9 +61,10 @@
     private final IActivityManager mActivityManager;
     private final ActivityStarter mActivityStarter;
 
+    private Dialog mGrantAdminDialog;
     private Dialog mSetupUserDialog;
     private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
-
+    private Boolean mGrantAdminRights;
     @Inject
     public CreateUserActivity(UserCreator userCreator,
             EditUserInfoController editUserInfoController, IActivityManager activityManager,
@@ -78,14 +80,17 @@
         super.onCreate(savedInstanceState);
         setShowWhenLocked(true);
         setContentView(R.layout.activity_create_new_user);
-
         if (savedInstanceState != null) {
             mEditUserInfoController.onRestoreInstanceState(savedInstanceState);
         }
 
-        mSetupUserDialog = createDialog();
-        mSetupUserDialog.show();
-
+        if (mUserCreator.isHeadlessSystemUserMode()) {
+            mGrantAdminDialog = buildGrantAdminDialog();
+            mGrantAdminDialog.show();
+        } else {
+            mSetupUserDialog = createDialog();
+            mSetupUserDialog.show();
+        }
         getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
                         OnBackInvokedDispatcher.PRIORITY_DEFAULT,
                         mBackCallback);
@@ -124,6 +129,22 @@
         );
     }
 
+    private Dialog buildGrantAdminDialog() {
+        return new GrantAdminDialogController().createDialog(
+                this,
+                (grantAdminRights) -> {
+                    mGrantAdminDialog.dismiss();
+                    mGrantAdminRights = grantAdminRights;
+                    mSetupUserDialog = createDialog();
+                    mSetupUserDialog.show();
+                },
+                () -> {
+                    mGrantAdminRights = false;
+                    finish();
+                }
+        );
+    }
+
     @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         super.onActivityResult(requestCode, resultCode, data);
@@ -139,6 +160,9 @@
         if (mSetupUserDialog != null) {
             mSetupUserDialog.dismiss();
         }
+        if (mGrantAdminDialog != null) {
+            mGrantAdminDialog.dismiss();
+        }
         finish();
     }
 
@@ -150,13 +174,15 @@
 
     private void addUserNow(String userName, Drawable userIcon) {
         mSetupUserDialog.dismiss();
-
         userName = (userName == null || userName.trim().isEmpty())
                 ? getString(com.android.settingslib.R.string.user_new_user_name)
                 : userName;
 
         mUserCreator.createUser(userName, userIcon,
                 userInfo -> {
+                    if (mGrantAdminRights) {
+                        mUserCreator.setUserAdmin(userInfo.id);
+                    }
                     switchToUser(userInfo.id);
                     finishIfNeeded();
                 }, () -> {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
index dcbbe74..277f670 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -32,7 +32,9 @@
  * A class to do the user creation process. It shows a progress dialog, and manages the user
  * creation
  */
-class UserCreator @Inject constructor(
+class UserCreator
+@Inject
+constructor(
     private val context: Context,
     private val userManager: UserManager,
     @Main private val mainExecutor: Executor,
@@ -42,14 +44,14 @@
      * Shows a progress dialog then starts the user creation process on the main thread.
      *
      * @param successCallback is called when the user creation is successful.
-     * @param errorCallback is called when userManager.createUser returns null.
-     * (Exceptions are not handled by this class)
+     * @param errorCallback is called when userManager.createUser returns null. (Exceptions are not
+     * handled by this class)
      */
     fun createUser(
         userName: String?,
         userIcon: Drawable?,
         successCallback: Consumer<UserInfo?>,
-       errorCallback: Runnable
+        errorCallback: Runnable
     ) {
         val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
         userCreationProgressDialog.show()
@@ -71,11 +73,21 @@
                         newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
                     }
                     userManager.setUserIcon(
-                        user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
+                        user.id,
+                        UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon)
+                    )
                 }
                 userCreationProgressDialog.dismiss()
                 successCallback.accept(user)
             }
         }
     }
+
+    fun setUserAdmin(userId: Int) {
+        userManager.setUserAdmin(userId)
+    }
+
+    fun isHeadlessSystemUserMode(): Boolean {
+        return UserManager.isHeadlessSystemUserMode()
+    }
 }
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 74295f0..45cb11e 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
@@ -56,6 +56,7 @@
 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.user.utils.MultiUserActionsEventHelper
 import com.android.systemui.util.kotlin.pairwise
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -372,6 +373,9 @@
         if (LegacyUserDataHelper.isUser(record)) {
             // It's safe to use checkNotNull around record.info because isUser only returns true
             // if record.info is not null.
+            uiEventLogger.log(
+                MultiUserActionsEventHelper.userSwitchMetric(checkNotNull(record.info))
+            )
             selectUser(checkNotNull(record.info).id, dialogShower)
         } else {
             executeAction(LegacyUserDataHelper.toUserActionModel(record), dialogShower)
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
index bfedac9..ec79b6d 100644
--- a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEvent.kt
@@ -23,7 +23,13 @@
     @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);
+    CREATE_RESTRICTED_USER_FROM_USER_SWITCHER(1259),
+    @UiEvent(doc = "Switch to   User tap from User Switcher.")
+    SWITCH_TO_USER_FROM_USER_SWITCHER(1266),
+    @UiEvent(doc = "Switch to  Guest User tap from User Switcher.")
+    SWITCH_TO_GUEST_FROM_USER_SWITCHER(1267),
+    @UiEvent(doc = "Switch to Restricted User tap from User Switcher.")
+    SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER(1268);
 
     override fun getId(): Int {
         return value
diff --git a/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.kt b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.kt
new file mode 100644
index 0000000..d12a788
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/utils/MultiUserActionsEventHelper.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 android.content.pm.UserInfo
+
+class MultiUserActionsEventHelper {
+    /** Return MultiUserActionsEvent based on UserInfo. */
+    companion object {
+        fun userSwitchMetric(userInfo: UserInfo): MultiUserActionsEvent {
+            if (userInfo.isGuest) return MultiUserActionsEvent.SWITCH_TO_GUEST_FROM_USER_SWITCHER
+            if (userInfo.isRestricted)
+                return MultiUserActionsEvent.SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER
+            return MultiUserActionsEvent.SWITCH_TO_USER_FROM_USER_SWITCHER
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
new file mode 100644
index 0000000..35af444
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -0,0 +1,215 @@
+/*
+ * 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.volume;
+
+import android.annotation.StringRes;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.os.CountDownTimer;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+/**
+ * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
+ * <ul>
+ *     <li>{@link AudioManager#CSD_WARNING_DOSE_REACHED_1X}</li>
+ *     <li>{@link AudioManager#CSD_WARNING_DOSE_REPEATED_5X}</li>
+ *     <li>{@link AudioManager#CSD_WARNING_ACCUMULATION_START}</li>
+ *     <li>{@link AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE}</li>
+ * </ul>
+ * Rather than basing volume safety messages on a fixed volume index, the CSD feature derives its
+ * warnings from the computation of the "sound dose". The dose computation is based on a
+ * frequency-dependent analysis of the audio signal which estimates how loud and potentially harmful
+ * the signal content is. This is combined with the volume attenuation/amplification applied to it
+ * and integrated over time to derive the dose exposure over a 7 day rolling window.
+ * <p>The UI behaviors implemented in this class are defined in IEC 62368 in "Safeguards against
+ * acoustic energy sources". The events that trigger those warnings originate in SoundDoseHelper
+ * which runs in the "audio" system_server service (see
+ * frameworks/base/services/core/java/com/android/server/audio/AudioService.java for the
+ * communication between the audio framework and the volume controller, and
+ * frameworks/base/services/core/java/com/android/server/audio/SoundDoseHelper.java for the
+ * communication between the native audio framework that implements the dose computation and the
+ * audio service.
+ */
+public abstract class CsdWarningDialog extends SystemUIDialog
+        implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
+
+    private static final String TAG = Util.logTag(CsdWarningDialog.class);
+
+    private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
+    // time after which action is taken when the user hasn't ack'd or dismissed the dialog
+    private static final int NO_ACTION_TIMEOUT_MS = 5000;
+
+    private final Context mContext;
+    private final AudioManager mAudioManager;
+    private final @AudioManager.CsdWarning int mCsdWarning;
+    private final Object mTimerLock = new Object();
+    /**
+     * Timer to keep track of how long the user has before an action (here volume reduction) is
+     * taken on their behalf.
+     */
+    @GuardedBy("mTimerLock")
+    private final CountDownTimer mNoUserActionTimer;
+
+    private long mShowTime;
+
+    public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
+            AudioManager audioManager) {
+        super(context);
+        mCsdWarning = csdWarning;
+        mContext = context;
+        mAudioManager = audioManager;
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+        setShowForAllUsers(true);
+        setMessage(mContext.getString(getStringForWarning(csdWarning)));
+        setButton(DialogInterface.BUTTON_POSITIVE,
+                mContext.getString(com.android.internal.R.string.yes), this);
+        setButton(DialogInterface.BUTTON_NEGATIVE,
+                mContext.getString(com.android.internal.R.string.no), this);
+        setOnDismissListener(this);
+
+        final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        context.registerReceiver(mReceiver, filter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
+
+        if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+            mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
+                @Override
+                public void onTick(long millisUntilFinished) { }
+
+                @Override
+                public void onFinish() {
+                    if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+                        // unlike on the 5x dose repeat, level is only reduced to RS1
+                        // when the warning is not acknowledged quick enough
+                        mAudioManager.lowerVolumeToRs1();
+                    }
+                }
+            };
+        } else {
+            mNoUserActionTimer = null;
+        }
+    }
+
+    protected abstract void cleanUp();
+
+    // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
+    // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
+    // key down -> up sequence
+    //@Override
+    //public boolean onKeyDown(int keyCode, KeyEvent event) {
+    //    return super.onKeyDown(keyCode, event);
+    //}
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        // never allow to raise volume
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+            return true;
+        }
+        // VOLUME_DOWN will dismiss the dialog
+        if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN
+                && (System.currentTimeMillis() - mShowTime) > KEY_CONFIRM_ALLOWED_AFTER_MS) {
+            Log.i(TAG, "Confirmed CSD exposure warning via VOLUME_DOWN");
+            dismiss();
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            Log.d(TAG, "OK pressed for CSD warning " + mCsdWarning);
+            dismiss();
+
+        }
+        if (D.BUG) Log.d(TAG, "on click " + which);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mShowTime = System.currentTimeMillis();
+        synchronized (mTimerLock) {
+            if (mNoUserActionTimer != null) {
+                new Thread(() -> {
+                    synchronized (mTimerLock) {
+                        mNoUserActionTimer.start();
+                    }
+                }).start();
+            }
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        synchronized (mTimerLock) {
+            if (mNoUserActionTimer != null) {
+                mNoUserActionTimer.cancel();
+            }
+        }
+    }
+
+    @Override
+    public void onDismiss(DialogInterface unused) {
+        if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REPEATED_5X) {
+            // level is always reduced to RS1 beyond the 5x dose
+            mAudioManager.lowerVolumeToRs1();
+        }
+        try {
+            mContext.unregisterReceiver(mReceiver);
+        } catch (IllegalArgumentException e) {
+            // Don't crash if the receiver has already been unregistered.
+            Log.e(TAG, "Error unregistering broadcast receiver", e);
+        }
+        cleanUp();
+    }
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                if (D.BUG) Log.d(TAG, "Received ACTION_CLOSE_SYSTEM_DIALOGS");
+                cancel();
+                cleanUp();
+            }
+        }
+    };
+
+    private @StringRes int getStringForWarning(@AudioManager.CsdWarning int csdWarning) {
+        switch (csdWarning) {
+            case AudioManager.CSD_WARNING_DOSE_REACHED_1X:
+                return com.android.internal.R.string.csd_dose_reached_warning;
+            case AudioManager.CSD_WARNING_DOSE_REPEATED_5X:
+                return com.android.internal.R.string.csd_dose_repeat_warning;
+            case AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE:
+                return com.android.internal.R.string.csd_momentary_exposure_warning;
+            case AudioManager.CSD_WARNING_ACCUMULATION_START:
+                return com.android.internal.R.string.csd_entering_RS2_warning;
+        }
+        Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
+        return com.android.internal.R.string.csd_dose_reached_warning;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 369552f..fc0033d 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -97,6 +97,7 @@
     public static final int DISMISS_STREAM_GONE = 7;
     public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
     public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9;
+    public static final int DISMISS_REASON_CSD_WARNING_TIMEOUT = 10;
     public static final String[] DISMISS_REASONS = {
             "unknown",
             "touch_outside",
@@ -107,7 +108,8 @@
             "done_clicked",
             "a11y_stream_changed",
             "output_chooser",
-            "usb_temperature_below_threshold"
+            "usb_temperature_below_threshold",
+            "csd_warning_timeout"
     };
 
     public static final int SHOW_REASON_UNKNOWN = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 9349966..2fc8b03 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -410,6 +410,10 @@
         }
     }
 
+    private void onShowCsdWarningW(@AudioManager.CsdWarning int csdWarning, int durationMs) {
+            mCallbacks.onShowCsdWarning(csdWarning, durationMs);
+    }
+
     private void onGetCaptionsComponentStateW(boolean fromTooltip) {
         mCallbacks.onCaptionComponentStateChanged(
                 mCaptioningManager.isSystemAudioCaptioningUiEnabled(), fromTooltip);
@@ -707,6 +711,27 @@
             mWorker.obtainMessage(W.SHOW_SAFETY_WARNING, flags, 0).sendToTarget();
         }
 
+        /**
+         * Display a sound-dose related warning.
+         * This method will never be called if the CSD (Computed Sound Dose) feature is
+         * not enabled. See com.android.android.server.audio.SoundDoseHelper for the state of
+         * the feature.
+         * @param warning the type of warning to display, values are one of
+         *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REACHED_1X},
+         *        {@link android.media.AudioManager#CSD_WARNING_DOSE_REPEATED_5X},
+         *        {@link android.media.AudioManager#CSD_WARNING_MOMENTARY_EXPOSURE},
+         *        {@link android.media.AudioManager#CSD_WARNING_ACCUMULATION_START}.
+         * @param displayDurationMs the time expressed in milliseconds after which the dialog will be
+         *        automatically dismissed, or -1 if there is no automatic timeout.
+         */
+        @Override
+        public void displayCsdWarning(int csdWarning, int displayDurationMs) throws RemoteException
+        {
+            if (D.BUG) Log.d(TAG, "displayCsdWarning durMs=" + displayDurationMs);
+            mWorker.obtainMessage(W.SHOW_CSD_WARNING, csdWarning, displayDurationMs)
+                    .sendToTarget();
+        }
+
         @Override
         public void volumeChanged(int streamType, int flags) throws RemoteException {
             if (D.BUG) Log.d(TAG, "volumeChanged " + AudioSystem.streamToString(streamType)
@@ -769,6 +794,7 @@
         private static final int SHOW_SAFETY_WARNING = 14;
         private static final int ACCESSIBILITY_MODE_CHANGED = 15;
         private static final int GET_CAPTIONS_COMPONENT_STATE = 16;
+        private static final int SHOW_CSD_WARNING = 17;
 
         W(Looper looper) {
             super(looper);
@@ -794,6 +820,8 @@
                 case GET_CAPTIONS_COMPONENT_STATE:
                     onGetCaptionsComponentStateW((Boolean) msg.obj); break;
                 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
+                    break;
+                case SHOW_CSD_WARNING: onShowCsdWarningW(msg.arg1, msg.arg2); break;
             }
         }
     }
@@ -925,6 +953,21 @@
         }
 
         @Override
+        public void onShowCsdWarning(int csdWarning, int durationMs) {
+            if (Callbacks.VERSION < 2) {
+                return;
+            }
+            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+                entry.getValue().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        entry.getKey().onShowCsdWarning(csdWarning, durationMs);
+                    }
+                });
+            }
+        }
+
+        @Override
         public void onAccessibilityModeChanged(Boolean showA11yStream) {
             boolean show = showA11yStream != null && showA11yStream;
             for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 1d640db..7d23399 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -109,6 +109,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.graphics.drawable.BackgroundBlurDrawable;
@@ -272,7 +273,10 @@
     private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
     private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
     private State mState;
+    @GuardedBy("mSafetyWarningLock")
     private SafetyWarningDialog mSafetyWarning;
+    @GuardedBy("mSafetyWarningLock")
+    private CsdWarningDialog mCsdDialog;
     private boolean mHovering = false;
     private final boolean mShowActiveStreamOnly;
     private boolean mConfigChanged = false;
@@ -1476,6 +1480,23 @@
                 AccessibilityManager.FLAG_CONTENT_CONTROLS);
     }
 
+    protected void scheduleCsdTimeoutH(int timeoutMs) {
+        mHandler.removeMessages(H.CSD_TIMEOUT);
+        mHandler.sendMessageDelayed(mHandler.obtainMessage(H.CSD_TIMEOUT,
+                Events.DISMISS_REASON_CSD_WARNING_TIMEOUT, 0), timeoutMs);
+        Log.i(TAG, "scheduleCsdTimeoutH " + timeoutMs + "ms " + Debug.getCaller());
+        mController.userActivity();
+    }
+
+    private void onCsdTimeoutH() {
+        synchronized (mSafetyWarningLock) {
+            if (mCsdDialog == null) {
+                return;
+            }
+            mCsdDialog.dismiss();
+        }
+    }
+
     protected void dismissH(int reason) {
         Trace.beginSection("VolumeDialogImpl#dismissH");
 
@@ -2060,6 +2081,30 @@
         rescheduleTimeoutH();
     }
 
+    private void showCsdWarningH(int csdWarning, int durationMs) {
+        synchronized (mSafetyWarningLock) {
+            if (mCsdDialog != null) {
+                return;
+            }
+            mCsdDialog = new CsdWarningDialog(csdWarning,
+                    mContext, mController.getAudioManager()) {
+                @Override
+                protected void cleanUp() {
+                    synchronized (mSafetyWarningLock) {
+                        mCsdDialog = null;
+                    }
+                    recheckH(null);
+                }
+            };
+            mCsdDialog.show();
+        }
+        recheckH(null);
+        if (durationMs > 0) {
+            scheduleCsdTimeoutH(durationMs);
+        }
+        rescheduleTimeoutH();
+    }
+
     private String getStreamLabelH(StreamState ss) {
         if (ss == null) {
             return "";
@@ -2239,6 +2284,11 @@
         }
 
         @Override
+        public void onShowCsdWarning(int csdWarning, int durationMs) {
+            showCsdWarningH(csdWarning, durationMs);
+        }
+
+        @Override
         public void onAccessibilityModeChanged(Boolean showA11yStream) {
             mShowA11yStream = showA11yStream == null ? false : showA11yStream;
             VolumeRow activeRow = getActiveRow();
@@ -2265,6 +2315,7 @@
         private static final int SET_STREAM_IMPORTANT = 5;
         private static final int RESCHEDULE_TIMEOUT = 6;
         private static final int STATE_CHANGED = 7;
+        private static final int CSD_TIMEOUT = 8;
 
         public H() {
             super(Looper.getMainLooper());
@@ -2281,6 +2332,7 @@
                 case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
                 case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
                 case STATE_CHANGED: onStateChangedH(mState); break;
+                case CSD_TIMEOUT: onCsdTimeoutH(); break;
             }
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
index fa9bab2..1059543 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardAbsKeyInputViewControllerTest.java
@@ -150,10 +150,4 @@
                 getContext().getResources().getString(R.string.kg_prompt_reason_restart_password),
                 false);
     }
-
-    @Test
-    public void testReset() {
-        mKeyguardAbsKeyInputViewController.reset();
-        verify(mKeyguardMessageAreaController).setMessage("", false);
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index c94c97c..be4bbdf 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -25,6 +25,7 @@
 import android.testing.AndroidTestingRunner;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.plugins.ClockAnimations;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -59,6 +60,8 @@
     @Mock
     DozeParameters mDozeParameters;
     @Mock
+    FeatureFlags mFeatureFlags;
+    @Mock
     ScreenOffAnimationController mScreenOffAnimationController;
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallbackCaptor;
@@ -77,6 +80,7 @@
                 mKeyguardUpdateMonitor,
                 mConfigurationController,
                 mDozeParameters,
+                mFeatureFlags,
                 mScreenOffAnimationController);
     }
 
@@ -96,9 +100,9 @@
     public void setTranslationYExcludingMedia_forwardsCallToView() {
         float translationY = 123f;
 
-        mController.setTranslationYExcludingMedia(translationY);
+        mController.setTranslationY(translationY, /* excludeMedia= */true);
 
-        verify(mKeyguardStatusView).setChildrenTranslationYExcludingMediaView(translationY);
+        verify(mKeyguardStatusView).setChildrenTranslationY(translationY, /* excludeMedia= */true);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index ce44f4d..508aea5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -37,19 +37,23 @@
     fun setChildrenTranslationYExcludingMediaView_mediaViewIsNotTranslated() {
         val translationY = 1234f
 
-        keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
+        keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */true)
 
         assertThat(mediaView.translationY).isEqualTo(0)
-    }
-
-    @Test
-    fun setChildrenTranslationYExcludingMediaView_childrenAreTranslated() {
-        val translationY = 1234f
-
-        keyguardStatusView.setChildrenTranslationYExcludingMediaView(translationY)
 
         childrenExcludingMedia.forEach {
             assertThat(it.translationY).isEqualTo(translationY)
         }
     }
-}
\ No newline at end of file
+
+    @Test
+    fun setChildrenTranslationYIncludeMediaView() {
+        val translationY = 1234f
+
+        keyguardStatusView.setChildrenTranslationY(translationY, /* excludeMedia= */false)
+
+        statusViewContainer.children.forEach {
+            assertThat(it.translationY).isEqualTo(translationY)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
index 6c1f008..bb03f28 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUnfoldTransitionTest.kt
@@ -22,9 +22,13 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
 import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -33,7 +37,6 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 /**
@@ -50,7 +53,9 @@
 
     @Mock private lateinit var parent: ViewGroup
 
-    private lateinit var keyguardUnfoldTransition: KeyguardUnfoldTransition
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    private lateinit var underTest: KeyguardUnfoldTransition
     private lateinit var progressListener: TransitionProgressListener
     private var xTranslationMax = 0f
 
@@ -61,10 +66,10 @@
         xTranslationMax =
             context.resources.getDimensionPixelSize(R.dimen.keyguard_unfold_translation_x).toFloat()
 
-        keyguardUnfoldTransition = KeyguardUnfoldTransition(context, progressProvider)
+        underTest = KeyguardUnfoldTransition(context, statusBarStateController, progressProvider)
 
-        keyguardUnfoldTransition.setup(parent)
-        keyguardUnfoldTransition.statusViewCentered = false
+        underTest.setup(parent)
+        underTest.statusViewCentered = false
 
         verify(progressProvider).addCallback(capture(progressListenerCaptor))
         progressListener = progressListenerCaptor.value
@@ -72,10 +77,11 @@
 
     @Test
     fun onTransition_centeredViewDoesNotMove() {
-        keyguardUnfoldTransition.statusViewCentered = true
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+        underTest.statusViewCentered = true
 
         val view = View(context)
-        `when`(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
 
         progressListener.onTransitionStarted()
         assertThat(view.translationX).isZero()
@@ -89,4 +95,44 @@
         progressListener.onTransitionFinished()
         assertThat(view.translationX).isZero()
     }
+
+    @Test
+    fun whenInShadeState_viewDoesNotMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInKeyguardState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.lockscreen_clock_view_large)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 898f370..b4696e4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -35,8 +35,6 @@
 import android.view.WindowManager
 import android.widget.ScrollView
 import androidx.test.filters.SmallTest
-import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
-import com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.R
@@ -81,6 +79,8 @@
     @Mock
     lateinit var wakefulnessLifecycle: WakefulnessLifecycle
     @Mock
+    lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector
+    @Mock
     lateinit var windowToken: IBinder
     @Mock
     lateinit var interactionJankMonitor: InteractionJankMonitor
@@ -170,26 +170,6 @@
     }
 
     @Test
-    fun testDismissesOnFocusLoss() {
-        val container = initializeFingerprintContainer()
-        waitForIdleSync()
-
-        val requestID = authContainer?.requestId ?: 0L
-
-        verify(callback).onDialogAnimatedIn(requestID)
-
-        container.onWindowFocusChanged(false)
-        waitForIdleSync()
-
-        verify(callback).onDismissed(
-                eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-                eq<ByteArray?>(null), /* credentialAttestation */
-                eq(requestID)
-        )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
     fun testFocusLossAfterRotating() {
         val container = initializeFingerprintContainer()
         waitForIdleSync()
@@ -209,35 +189,6 @@
     }
 
     @Test
-    fun testDismissesOnFocusLoss_hidesKeyboardWhenVisible() {
-        val container = initializeFingerprintContainer(
-            authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL
-        )
-        waitForIdleSync()
-
-        val requestID = authContainer?.requestId ?: 0L
-
-        // Simulate keyboard was shown on the credential view
-        val windowInsetsController = container.windowInsetsController
-        spyOn(windowInsetsController)
-        spyOn(container.rootWindowInsets)
-        doReturn(true).`when`(container.rootWindowInsets).isVisible(WindowInsets.Type.ime())
-
-        container.onWindowFocusChanged(false)
-        waitForIdleSync()
-
-        // Expect hiding IME request will be invoked when dismissing the view
-        verify(windowInsetsController)?.hide(WindowInsets.Type.ime())
-
-        verify(callback).onDismissed(
-            eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
-            eq<ByteArray?>(null), /* credentialAttestation */
-            eq(requestID)
-        )
-        assertThat(container.parent).isNull()
-    }
-
-    @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
         container.mBiometricCallback.onAction(
@@ -519,6 +470,7 @@
         fingerprintProps,
         faceProps,
         wakefulnessLifecycle,
+        panelInteractionDetector,
         userManager,
         lockPatternUtils,
         interactionJankMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 67b293f44..5afe49e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -152,6 +152,8 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
+    private AuthDialogPanelInteractionDetector mPanelInteractionDetector;
+    @Mock
     private UserManager mUserManager;
     @Mock
     private LockPatternUtils mLockPatternUtils;
@@ -953,9 +955,10 @@
             super(context, mExecution, mCommandQueue, mActivityTaskManager, mWindowManager,
                     mFingerprintManager, mFaceManager, () -> mUdfpsController,
                     () -> mSideFpsController, mDisplayManager, mWakefulnessLifecycle,
-                    mUserManager, mLockPatternUtils, mUdfpsLogger, mLogContextInteractor,
-                    () -> mBiometricPromptCredentialInteractor, () -> mCredentialViewModel,
-                    mInteractionJankMonitor, mHandler, mBackgroundExecutor, mVibratorHelper);
+                    mPanelInteractionDetector, mUserManager, mLockPatternUtils, mUdfpsLogger,
+                    mLogContextInteractor, () -> mBiometricPromptCredentialInteractor,
+                    () -> mCredentialViewModel, mInteractionJankMonitor, mHandler,
+                    mBackgroundExecutor, mVibratorHelper);
         }
 
         @Override
@@ -963,7 +966,9 @@
                 boolean requireConfirmation, int userId, int[] sensorIds,
                 String opPackageName, boolean skipIntro, long operationId, long requestId,
                 @BiometricManager.BiometricMultiSensorMode int multiSensorConfig,
-                WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager,
+                WakefulnessLifecycle wakefulnessLifecycle,
+                AuthDialogPanelInteractionDetector panelInteractionDetector,
+                UserManager userManager,
                 LockPatternUtils lockPatternUtils) {
 
             mLastBiometricPromptInfo = promptInfo;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
index e7e6918..bdd496e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardListenerTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_ENABLED;
 
+import static com.google.android.setupcompat.util.WizardManagerHelper.SETTINGS_SECURE_USER_SETUP_COMPLETE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -32,6 +34,7 @@
 import android.content.ClipboardManager;
 import android.os.PersistableBundle;
 import android.provider.DeviceConfig;
+import android.provider.Settings;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -66,6 +69,8 @@
     @Mock
     private ClipboardOverlayController mOverlayController;
     @Mock
+    private ClipboardToast mClipboardToast;
+    @Mock
     private UiEventLogger mUiEventLogger;
     @Mock
     private FeatureFlags mFeatureFlags;
@@ -84,6 +89,8 @@
     @Spy
     private Provider<ClipboardOverlayController> mOverlayControllerProvider;
 
+    private ClipboardListener mClipboardListener;
+
 
     @Before
     public void setup() {
@@ -93,7 +100,8 @@
         when(mClipboardOverlayControllerLegacyFactory.create(any()))
                 .thenReturn(mOverlayControllerLegacy);
         when(mClipboardManager.hasPrimaryClip()).thenReturn(true);
-
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 1);
 
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
@@ -101,16 +109,17 @@
         when(mClipboardManager.getPrimaryClipSource()).thenReturn(mSampleSource);
 
         mDeviceConfigProxy = new DeviceConfigProxyFake();
+
+        mClipboardListener = new ClipboardListener(getContext(), mDeviceConfigProxy,
+                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
+                mClipboardToast, mClipboardManager, mUiEventLogger, mFeatureFlags);
     }
 
     @Test
     public void test_disabled() {
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "false", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
         verifyZeroInteractions(mClipboardManager);
         verifyZeroInteractions(mUiEventLogger);
     }
@@ -119,10 +128,7 @@
     public void test_enabled() {
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
         verify(mClipboardManager).addPrimaryClipChangedListener(any());
         verifyZeroInteractions(mUiEventLogger);
     }
@@ -133,11 +139,8 @@
 
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory).create(any());
 
@@ -152,14 +155,14 @@
         // Should clear the overlay controller
         mRunnableCaptor.getValue().run();
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mClipboardOverlayControllerLegacyFactory, times(2)).create(any());
         verifyZeroInteractions(mOverlayControllerProvider);
@@ -171,11 +174,8 @@
 
         mDeviceConfigProxy.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI, CLIPBOARD_OVERLAY_ENABLED,
                 "true", false);
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider).get();
 
@@ -190,14 +190,14 @@
         // Should clear the overlay controller
         mRunnableCaptor.getValue().run();
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider, times(2)).get();
 
         // Not calling the runnable here, just change the clip again and verify that the overlay is
         // NOT recreated.
 
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mOverlayControllerProvider, times(2)).get();
         verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
@@ -233,13 +233,10 @@
     public void test_logging_enterAndReenter() {
         when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(false);
 
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
 
-        listener.onPrimaryClipChanged();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
@@ -251,17 +248,29 @@
     public void test_logging_enterAndReenter_new() {
         when(mFeatureFlags.isEnabled(Flags.CLIPBOARD_OVERLAY_REFACTOR)).thenReturn(true);
 
-        ClipboardListener listener = new ClipboardListener(getContext(), mDeviceConfigProxy,
-                mOverlayControllerProvider, mClipboardOverlayControllerLegacyFactory,
-                mClipboardManager, mUiEventLogger, mFeatureFlags);
-        listener.start();
+        mClipboardListener.start();
 
-        listener.onPrimaryClipChanged();
-        listener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
+        mClipboardListener.onPrimaryClipChanged();
 
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ENTERED, 0, mSampleSource);
         verify(mUiEventLogger, times(1)).log(
                 ClipboardOverlayEvent.CLIPBOARD_OVERLAY_UPDATED, 0, mSampleSource);
     }
+
+    @Test
+    public void test_userSetupIncomplete_showsToast() {
+        Settings.Secure.putInt(
+                mContext.getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0);
+
+        mClipboardListener.start();
+        mClipboardListener.onPrimaryClipChanged();
+
+        verify(mUiEventLogger, times(1)).log(
+                ClipboardOverlayEvent.CLIPBOARD_TOAST_SHOWN, 0, mSampleSource);
+        verify(mClipboardToast, times(1)).showCopiedToast();
+        verifyZeroInteractions(mOverlayControllerProvider);
+        verifyZeroInteractions(mClipboardOverlayControllerLegacyFactory);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
new file mode 100644
index 0000000..87c66b5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/demomode/DemoModeControllerTest.kt
@@ -0,0 +1,104 @@
+/*
+ * 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.demomode
+
+import android.content.Intent
+import android.os.Bundle
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.demomode.DemoMode.ACTION_DEMO
+import com.android.systemui.demomode.DemoMode.COMMAND_STATUS
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+@SmallTest
+class DemoModeControllerTest : SysuiTestCase() {
+    private lateinit var underTest: DemoModeController
+
+    @Mock private lateinit var dumpManager: DumpManager
+
+    private val globalSettings = FakeSettings()
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        allowTestableLooperAsMainThread()
+
+        MockitoAnnotations.initMocks(this)
+
+        globalSettings.putInt(DemoModeController.DEMO_MODE_ALLOWED, 1)
+        globalSettings.putInt(DemoModeController.DEMO_MODE_ON, 1)
+
+        underTest =
+            DemoModeController(
+                context = context,
+                dumpManager = dumpManager,
+                globalSettings = globalSettings,
+                broadcastDispatcher = fakeBroadcastDispatcher
+            )
+
+        underTest.initialize()
+    }
+
+    @Test
+    fun `demo command flow - returns args`() =
+        testScope.runTest {
+            var latest: Bundle? = null
+            val flow = underTest.demoFlowForCommand(TEST_COMMAND)
+            val job = launch { flow.collect { latest = it } }
+
+            sendDemoCommand(args = mapOf("key1" to "val1"))
+            assertThat(latest!!.getString("key1")).isEqualTo("val1")
+
+            sendDemoCommand(args = mapOf("key2" to "val2"))
+            assertThat(latest!!.getString("key2")).isEqualTo("val2")
+
+            job.cancel()
+        }
+
+    private fun sendDemoCommand(command: String? = TEST_COMMAND, args: Map<String, String>) {
+        val intent = Intent(ACTION_DEMO)
+        intent.putExtra("command", command)
+        args.forEach { arg -> intent.putExtra(arg.key, arg.value) }
+
+        fakeBroadcastDispatcher.registeredReceivers.forEach { it.onReceive(context, intent) }
+    }
+
+    companion object {
+        // Use a valid command until we properly fake out everything
+        const val TEST_COMMAND = COMMAND_STATUS
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 07d7e79..cefba62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -37,6 +37,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.hardware.Sensor;
 import android.hardware.display.AmbientDisplayConfiguration;
@@ -78,7 +79,8 @@
 @RunWithLooper
 @SmallTest
 public class DozeSensorsTest extends SysuiTestCase {
-
+    @Mock
+    private Resources mResources;
     @Mock
     private AsyncSensorManager mSensorManager;
     @Mock
@@ -428,7 +430,7 @@
 
     @Test
     public void testGesturesAllInitiallyRespectSettings() {
-        DozeSensors dozeSensors = new DozeSensors(mSensorManager, mDozeParameters,
+        DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
                 mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                 mProximitySensor, mFakeSettings, mAuthController,
                 mDevicePostureController, mUserTracker);
@@ -438,9 +440,47 @@
         }
     }
 
+    @Test
+    public void liftToWake_defaultSetting_configDefaultFalse() {
+        // WHEN the default lift to wake gesture setting is false
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_dozePickupGestureEnabled)).thenReturn(false);
+
+        DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
+                mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+                mProximitySensor, mFakeSettings, mAuthController,
+                mDevicePostureController, mUserTracker);
+
+        for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
+            // THEN lift to wake's TriggerSensor enabledBySettings is false
+            if (sensor.mPulseReason == DozeLog.REASON_SENSOR_PICKUP) {
+                assertFalse(sensor.enabledBySetting());
+            }
+        }
+    }
+
+    @Test
+    public void liftToWake_defaultSetting_configDefaultTrue() {
+        // WHEN the default lift to wake gesture setting is true
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_dozePickupGestureEnabled)).thenReturn(true);
+
+        DozeSensors dozeSensors = new DozeSensors(mResources, mSensorManager, mDozeParameters,
+                mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
+                mProximitySensor, mFakeSettings, mAuthController,
+                mDevicePostureController, mUserTracker);
+
+        for (TriggerSensor sensor : dozeSensors.mTriggerSensors) {
+            // THEN lift to wake's TriggerSensor enabledBySettings is true
+            if (sensor.mPulseReason == DozeLog.REASON_SENSOR_PICKUP) {
+                assertTrue(sensor.enabledBySetting());
+            }
+        }
+    }
+
     private class TestableDozeSensors extends DozeSensors {
         TestableDozeSensors() {
-            super(mSensorManager, mDozeParameters,
+            super(mResources, mSensorManager, mDozeParameters,
                     mAmbientDisplayConfiguration, mWakeLock, mCallback, mProxCallback, mDozeLog,
                     mProximitySensor, mFakeSettings, mAuthController,
                     mDevicePostureController, mUserTracker);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
index 8e689cf..6c23254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt
@@ -1,18 +1,25 @@
 package com.android.systemui.dreams
 
-import android.animation.Animator
 import android.animation.AnimatorSet
 import android.testing.AndroidTestingRunner
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel
 import com.android.systemui.statusbar.BlurUtils
-import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.anyLong
+import org.mockito.Mockito.eq
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -28,14 +35,6 @@
         private const val DREAM_IN_COMPLICATIONS_ANIMATION_DURATION = 3L
         private const val DREAM_IN_TRANSLATION_Y_DISTANCE = 6
         private const val DREAM_IN_TRANSLATION_Y_DURATION = 7L
-        private const val DREAM_OUT_TRANSLATION_Y_DISTANCE = 6
-        private const val DREAM_OUT_TRANSLATION_Y_DURATION = 7L
-        private const val DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM = 8L
-        private const val DREAM_OUT_TRANSLATION_Y_DELAY_TOP = 9L
-        private const val DREAM_OUT_ALPHA_DURATION = 10L
-        private const val DREAM_OUT_ALPHA_DELAY_BOTTOM = 11L
-        private const val DREAM_OUT_ALPHA_DELAY_TOP = 12L
-        private const val DREAM_OUT_BLUR_DURATION = 13L
     }
 
     @Mock private lateinit var mockAnimator: AnimatorSet
@@ -43,6 +42,8 @@
     @Mock private lateinit var hostViewController: ComplicationHostViewController
     @Mock private lateinit var statusBarViewController: DreamOverlayStatusBarViewController
     @Mock private lateinit var stateController: DreamOverlayStateController
+    @Mock private lateinit var configController: ConfigurationController
+    @Mock private lateinit var transitionViewModel: DreamingToLockscreenTransitionViewModel
     private lateinit var controller: DreamOverlayAnimationsController
 
     @Before
@@ -55,71 +56,48 @@
                 statusBarViewController,
                 stateController,
                 DREAM_BLUR_RADIUS,
+                transitionViewModel,
+                configController,
                 DREAM_IN_BLUR_ANIMATION_DURATION,
                 DREAM_IN_COMPLICATIONS_ANIMATION_DURATION,
                 DREAM_IN_TRANSLATION_Y_DISTANCE,
                 DREAM_IN_TRANSLATION_Y_DURATION,
-                DREAM_OUT_TRANSLATION_Y_DISTANCE,
-                DREAM_OUT_TRANSLATION_Y_DURATION,
-                DREAM_OUT_TRANSLATION_Y_DELAY_BOTTOM,
-                DREAM_OUT_TRANSLATION_Y_DELAY_TOP,
-                DREAM_OUT_ALPHA_DURATION,
-                DREAM_OUT_ALPHA_DELAY_BOTTOM,
-                DREAM_OUT_ALPHA_DELAY_TOP,
-                DREAM_OUT_BLUR_DURATION
             )
+
+        val mockView: View = mock()
+        whenever(mockView.resources).thenReturn(mContext.resources)
+
+        runBlocking(Dispatchers.Main.immediate) { controller.init(mockView) }
     }
 
     @Test
-    fun testExitAnimationOnEnd() {
-        val mockCallback: () -> Unit = mock()
+    fun testWakeUpCallsExecutor() {
+        val mockExecutor: DelayableExecutor = mock()
+        val mockCallback: Runnable = mock()
 
-        controller.startExitAnimations(
-            view = mock(),
+        controller.wakeUp(
             doneCallback = mockCallback,
-            animatorBuilder = { mockAnimator }
+            executor = mockExecutor,
         )
 
-        val captor = argumentCaptor<Animator.AnimatorListener>()
-        verify(mockAnimator).addListener(captor.capture())
-        val listener = captor.value
-
-        verify(mockCallback, never()).invoke()
-        listener.onAnimationEnd(mockAnimator)
-        verify(mockCallback, times(1)).invoke()
+        verify(mockExecutor).executeDelayed(eq(mockCallback), anyLong())
     }
 
     @Test
-    fun testCancellation() {
-        controller.startExitAnimations(
-            view = mock(),
-            doneCallback = mock(),
-            animatorBuilder = { mockAnimator }
-        )
-
-        verify(mockAnimator, never()).cancel()
-        controller.cancelAnimations()
-        verify(mockAnimator, times(1)).cancel()
-    }
-
-    @Test
-    fun testExitAfterStartWillCancel() {
+    fun testWakeUpAfterStartWillCancel() {
         val mockStartAnimator: AnimatorSet = mock()
-        val mockExitAnimator: AnimatorSet = mock()
 
-        controller.startEntryAnimations(view = mock(), animatorBuilder = { mockStartAnimator })
+        controller.startEntryAnimations(animatorBuilder = { mockStartAnimator })
 
         verify(mockStartAnimator, never()).cancel()
 
-        controller.startExitAnimations(
-            view = mock(),
+        controller.wakeUp(
             doneCallback = mock(),
-            animatorBuilder = { mockExitAnimator }
+            executor = mock(),
         )
 
         // Verify that we cancelled the start animator in favor of the exit
         // animator.
         verify(mockStartAnimator, times(1)).cancel()
-        verify(mockExitAnimator, never()).cancel()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.kt
new file mode 100644
index 0000000..9f534ef
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayCallbackControllerTest.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.systemui.dreams
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class DreamOverlayCallbackControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var callback: DreamOverlayCallbackController.Callback
+
+    private lateinit var underTest: DreamOverlayCallbackController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = DreamOverlayCallbackController()
+    }
+
+    @Test
+    fun onWakeUpInvokesCallback() {
+        underTest.onStartDream()
+        assertThat(underTest.isDreaming).isEqualTo(true)
+
+        underTest.addCallback(callback)
+        underTest.onWakeUp()
+        verify(callback).onWakeUp()
+        assertThat(underTest.isDreaming).isEqualTo(false)
+
+        // Adding twice should not invoke twice
+        reset(callback)
+        underTest.addCallback(callback)
+        underTest.onWakeUp()
+        verify(callback, times(1)).onWakeUp()
+
+        // After remove, no call to callback
+        reset(callback)
+        underTest.removeCallback(callback)
+        underTest.onWakeUp()
+        verify(callback, never()).onWakeUp()
+    }
+
+    @Test
+    fun onStartDreamInvokesCallback() {
+        underTest.addCallback(callback)
+
+        assertThat(underTest.isDreaming).isEqualTo(false)
+
+        underTest.onStartDream()
+        verify(callback).onStartDream()
+        assertThat(underTest.isDreaming).isEqualTo(true)
+
+        // Adding twice should not invoke twice
+        reset(callback)
+        underTest.addCallback(callback)
+        underTest.onStartDream()
+        verify(callback, times(1)).onStartDream()
+
+        // After remove, no call to callback
+        reset(callback)
+        underTest.removeCallback(callback)
+        underTest.onStartDream()
+        verify(callback, never()).onStartDream()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index 73c226d..2799a25 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -203,7 +203,7 @@
 
         mController.onViewAttached();
 
-        verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+        verify(mAnimationsController).startEntryAnimations();
         verify(mAnimationsController, never()).cancelAnimations();
     }
 
@@ -213,7 +213,7 @@
 
         mController.onViewAttached();
 
-        verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+        verify(mAnimationsController, never()).startEntryAnimations();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index 430575c..8f97026 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -139,6 +139,9 @@
     @Mock
     UiEventLogger mUiEventLogger;
 
+    @Mock
+    DreamOverlayCallbackController mDreamOverlayCallbackController;
+
     @Captor
     ArgumentCaptor<View> mViewCaptor;
 
@@ -172,8 +175,8 @@
 
         mService = new DreamOverlayService(
                 mContext,
-                mMainExecutor,
                 mLifecycleOwner,
+                mMainExecutor,
                 mWindowManager,
                 mComplicationComponentFactory,
                 mDreamComplicationComponentFactory,
@@ -182,7 +185,8 @@
                 mKeyguardUpdateMonitor,
                 mUiEventLogger,
                 mTouchInsetManager,
-                LOW_LIGHT_COMPONENT);
+                LOW_LIGHT_COMPONENT,
+                mDreamOverlayCallbackController);
     }
 
     @Test
@@ -394,6 +398,7 @@
         mService.onWakeUp(callback);
         mMainExecutor.runAllReady();
         verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+        verify(mDreamOverlayCallbackController).onWakeUp();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5deac19..be712f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamOverlayCallbackController
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -66,6 +67,7 @@
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -83,6 +85,7 @@
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 authController,
+                dreamOverlayCallbackController,
             )
     }
 
@@ -167,6 +170,29 @@
         }
 
     @Test
+    fun isKeyguardOccluded() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardStateController.isOccluded).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isKeyguardOccluded.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            val captor = argumentCaptor<KeyguardStateController.Callback>()
+            verify(keyguardStateController).addCallback(captor.capture())
+
+            whenever(keyguardStateController.isOccluded).thenReturn(true)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isTrue()
+
+            whenever(keyguardStateController.isOccluded).thenReturn(false)
+            captor.value.onKeyguardShowingChanged()
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun isDozing() =
         runTest(UnconfinedTestDispatcher()) {
             var latest: Boolean? = null
@@ -318,7 +344,7 @@
         }
 
     @Test
-    fun isDreaming() =
+    fun isDreamingFromKeyguardUpdateMonitor() =
         runTest(UnconfinedTestDispatcher()) {
             whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
             var latest: Boolean? = null
@@ -339,6 +365,29 @@
         }
 
     @Test
+    fun isDreamingFromDreamOverlayCallbackController() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(dreamOverlayCallbackController.isDreaming).thenReturn(false)
+            var latest: Boolean? = null
+            val job = underTest.isDreamingWithOverlay.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            val listener =
+                withArgCaptor<DreamOverlayCallbackController.Callback> {
+                    verify(dreamOverlayCallbackController).addCallback(capture())
+                }
+
+            listener.onStartDream()
+            assertThat(latest).isTrue()
+
+            listener.onWakeUp()
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
     fun biometricUnlockState() =
         runTest(UnconfinedTestDispatcher()) {
             val values = mutableListOf<BiometricUnlockModel>()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index a6cf840..d2b7838 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepositoryImpl
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -42,6 +44,7 @@
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import org.mockito.Mock
+import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -64,8 +67,8 @@
     // Used to verify transition requests for test output
     @Mock private lateinit var mockTransitionRepository: KeyguardTransitionRepository
 
-    private lateinit var lockscreenBouncerTransitionInteractor:
-        LockscreenBouncerTransitionInteractor
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    private lateinit var fromDreamingTransitionInteractor: FromDreamingTransitionInteractor
 
     @Before
     fun setUp() {
@@ -79,25 +82,82 @@
         transitionRepository = KeyguardTransitionRepositoryImpl()
         runner = KeyguardTransitionRunner(transitionRepository)
 
-        lockscreenBouncerTransitionInteractor =
-            LockscreenBouncerTransitionInteractor(
+        fromLockscreenTransitionInteractor =
+            FromLockscreenTransitionInteractor(
                 scope = testScope,
                 keyguardInteractor = KeyguardInteractor(keyguardRepository),
                 shadeRepository = shadeRepository,
                 keyguardTransitionRepository = mockTransitionRepository,
                 keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
             )
-        lockscreenBouncerTransitionInteractor.start()
+        fromLockscreenTransitionInteractor.start()
+
+        fromDreamingTransitionInteractor =
+            FromDreamingTransitionInteractor(
+                scope = testScope,
+                keyguardInteractor = KeyguardInteractor(keyguardRepository),
+                keyguardTransitionRepository = mockTransitionRepository,
+                keyguardTransitionInteractor = KeyguardTransitionInteractor(transitionRepository),
+            )
+        fromDreamingTransitionInteractor.start()
     }
 
     @Test
+    fun `DREAMING to LOCKSCREEN`() =
+        testScope.runTest {
+            // GIVEN a device is dreaming and occluded
+            keyguardRepository.setDreamingWithOverlay(true)
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to DREAMING
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.DREAMING,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN doze is complete
+            keyguardRepository.setDozeTransitionModel(
+                DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+            )
+            // AND dreaming has stopped
+            keyguardRepository.setDreamingWithOverlay(false)
+            // AND occluded has stopped
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture())
+                }
+            // THEN a transition to BOUNCER should occur
+            assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.DREAMING)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
     fun `LOCKSCREEN to BOUNCER via bouncer showing call`() =
         testScope.runTest {
             // GIVEN a device that has at least woken up
             keyguardRepository.setWakefulnessModel(startingToWake())
             runCurrent()
 
-            // GIVEN a transition has run to LOCKSCREEN
+            // GIVEN a prior transition has run to LOCKSCREEN
             runner.startTransition(
                 testScope,
                 TransitionInfo(
@@ -122,7 +182,7 @@
                     verify(mockTransitionRepository).startTransition(capture())
                 }
             // THEN a transition to BOUNCER should occur
-            assertThat(info.ownerName).isEqualTo("LockscreenBouncerTransitionInteractor")
+            assertThat(info.ownerName).isEqualTo("FromLockscreenTransitionInteractor")
             assertThat(info.from).isEqualTo(KeyguardState.LOCKSCREEN)
             assertThat(info.to).isEqualTo(KeyguardState.BOUNCER)
             assertThat(info.animator).isNotNull()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..5571663
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.keyguard.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE
+import com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.AnimationParams
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.DREAM_OVERLAY_TRANSLATION_Y
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_ALPHA
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel.Companion.LOCKSCREEN_TRANSLATION_Y
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class DreamingToLockscreenTransitionViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: DreamingToLockscreenTransitionViewModel
+    private lateinit var repository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardTransitionRepository()
+        val interactor = KeyguardTransitionInteractor(repository)
+        underTest = DreamingToLockscreenTransitionViewModel(interactor)
+    }
+
+    @Test
+    fun dreamOverlayTranslationY() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val pixels = 100
+            val job =
+                underTest.dreamOverlayTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(1f))
+
+            // Only 3 values should be present, since the dream overlay runs for a small fraction
+            // of the overall animation time
+            assertThat(values.size).isEqualTo(3)
+            assertThat(values[0])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0f, DREAM_OVERLAY_TRANSLATION_Y)
+                    ) * pixels
+                )
+            assertThat(values[1])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0.3f, DREAM_OVERLAY_TRANSLATION_Y)
+                    ) * pixels
+                )
+            assertThat(values[2])
+                .isEqualTo(
+                    EMPHASIZED_ACCELERATE.getInterpolation(
+                        animValue(0.5f, DREAM_OVERLAY_TRANSLATION_Y)
+                    ) * pixels
+                )
+
+            job.cancel()
+        }
+
+    @Test
+    fun dreamOverlayFadeOut() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val job = underTest.dreamOverlayAlpha.onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.1f))
+            repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(1f))
+
+            // Only two values should be present, since the dream overlay runs for a small fraction
+            // of the overall animation time
+            assertThat(values.size).isEqualTo(2)
+            assertThat(values[0]).isEqualTo(1f - animValue(0f, DREAM_OVERLAY_ALPHA))
+            assertThat(values[1]).isEqualTo(1f - animValue(0.1f, DREAM_OVERLAY_ALPHA))
+
+            job.cancel()
+        }
+
+    @Test
+    fun lockscreenFadeIn() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val job = underTest.lockscreenAlpha.onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.1f))
+            // Should start running here...
+            repository.sendTransitionStep(step(0.2f))
+            repository.sendTransitionStep(step(0.3f))
+            // ...up to here
+            repository.sendTransitionStep(step(1f))
+
+            // Only two values should be present, since the dream overlay runs for a small fraction
+            // of the overall animation time
+            assertThat(values.size).isEqualTo(2)
+            assertThat(values[0]).isEqualTo(animValue(0.2f, LOCKSCREEN_ALPHA))
+            assertThat(values[1]).isEqualTo(animValue(0.3f, LOCKSCREEN_ALPHA))
+
+            job.cancel()
+        }
+
+    @Test
+    fun lockscreenTranslationY() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val pixels = 100
+            val job =
+                underTest.lockscreenTranslationY(pixels).onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(step(0f))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.5f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            assertThat(values[0])
+                .isEqualTo(
+                    -pixels +
+                        EMPHASIZED_DECELERATE.getInterpolation(
+                            animValue(0f, LOCKSCREEN_TRANSLATION_Y)
+                        ) * pixels
+                )
+            assertThat(values[1])
+                .isEqualTo(
+                    -pixels +
+                        EMPHASIZED_DECELERATE.getInterpolation(
+                            animValue(0.3f, LOCKSCREEN_TRANSLATION_Y)
+                        ) * pixels
+                )
+            assertThat(values[2])
+                .isEqualTo(
+                    -pixels +
+                        EMPHASIZED_DECELERATE.getInterpolation(
+                            animValue(0.5f, LOCKSCREEN_TRANSLATION_Y)
+                        ) * pixels
+                )
+            assertThat(values[3])
+                .isEqualTo(
+                    -pixels +
+                        EMPHASIZED_DECELERATE.getInterpolation(
+                            animValue(1f, LOCKSCREEN_TRANSLATION_Y)
+                        ) * pixels
+                )
+
+            job.cancel()
+        }
+
+    private fun animValue(stepValue: Float, params: AnimationParams): Float {
+        val totalDuration = TO_LOCKSCREEN_DURATION
+        val startValue = (params.startTime / totalDuration).toFloat()
+
+        val multiplier = (totalDuration / params.duration).toFloat()
+        return (stepValue - startValue) * multiplier
+    }
+
+    private fun step(value: Float): TransitionStep {
+        return TransitionStep(
+            from = KeyguardState.DREAMING,
+            to = KeyguardState.LOCKSCREEN,
+            value = value,
+            transitionState = TransitionState.RUNNING,
+            ownerName = "DreamingToLockscreenTransitionViewModelTest"
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
index bad3f03..9c4e849 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/FakeMediaTttChipControllerReceiver.kt
@@ -22,6 +22,7 @@
 import android.view.ViewGroup
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
@@ -39,6 +40,7 @@
     mainExecutor: DelayableExecutor,
     accessibilityManager: AccessibilityManager,
     configurationController: ConfigurationController,
+    dumpManager: DumpManager,
     powerManager: PowerManager,
     mainHandler: Handler,
     mediaTttFlags: MediaTttFlags,
@@ -55,6 +57,7 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
+        dumpManager,
         powerManager,
         mainHandler,
         mediaTttFlags,
@@ -63,7 +66,7 @@
         wakeLockBuilder,
         systemClock,
     ) {
-    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+    override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
         onAnimationEnd.run()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index ef0bfb7..cefc742 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -34,6 +34,7 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.statusbar.CommandQueue
@@ -73,6 +74,8 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
     @Mock
+    private lateinit var dumpManager: DumpManager
+    @Mock
     private lateinit var mediaTttFlags: MediaTttFlags
     @Mock
     private lateinit var powerManager: PowerManager
@@ -95,6 +98,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(mediaTttFlags.isMediaTttEnabled()).thenReturn(true)
+        whenever(mediaTttFlags.isMediaTttReceiverSuccessRippleEnabled()).thenReturn(true)
 
         fakeAppIconDrawable = context.getDrawable(R.drawable.ic_cake)!!
         whenever(packageManager.getApplicationIcon(PACKAGE_NAME)).thenReturn(fakeAppIconDrawable)
@@ -122,6 +126,7 @@
             fakeExecutor,
             accessibilityManager,
             configurationController,
+            dumpManager,
             powerManager,
             Handler.getMain(),
             mediaTttFlags,
@@ -150,6 +155,7 @@
             FakeExecutor(FakeSystemClock()),
             accessibilityManager,
             configurationController,
+            dumpManager,
             powerManager,
             Handler.getMain(),
             mediaTttFlags,
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 b03a545..4cc12c7 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
@@ -38,6 +38,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.common.shared.model.Text.Companion.loadText
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.media.taptotransfer.MediaTttFlags
 import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.plugins.FalsingManager
@@ -81,6 +82,7 @@
     @Mock private lateinit var applicationInfo: ApplicationInfo
     @Mock private lateinit var commandQueue: CommandQueue
     @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var falsingCollector: FalsingCollector
     @Mock private lateinit var chipbarLogger: ChipbarLogger
@@ -137,6 +139,7 @@
                 fakeExecutor,
                 accessibilityManager,
                 configurationController,
+                dumpManager,
                 powerManager,
                 falsingManager,
                 falsingCollector,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index 17eb6e2..bbd62c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -147,8 +147,8 @@
         setUserProfiles(0);
         setShowUserVisibleJobs(true);
 
-        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
-        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", null, 1);
         Assert.assertEquals(0, mFmc.getNumRunningPackages());
         mIUserVisibleJobObserver.onUserVisibleJobStateChanged(j1, true);
         Assert.assertEquals(1, mFmc.getNumRunningPackages());
@@ -167,8 +167,8 @@
 
         Binder b1 = new Binder();
         Binder b2 = new Binder();
-        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
-        UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", 1);
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+        UserVisibleJobSummary j3 = new UserVisibleJobSummary(1, 0, "pkg3", null, 1);
         Assert.assertEquals(0, mFmc.getNumRunningPackages());
         mIForegroundServiceObserver.onForegroundStateChanged(b1, "pkg1", 0, true);
         Assert.assertEquals(1, mFmc.getNumRunningPackages());
@@ -359,8 +359,8 @@
         // pkg1 has only job
         // pkg2 has both job and fgs
         // pkg3 has only fgs
-        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", 0);
-        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", 1);
+        UserVisibleJobSummary j1 = new UserVisibleJobSummary(0, 0, "pkg1", null, 0);
+        UserVisibleJobSummary j2 = new UserVisibleJobSummary(1, 0, "pkg2", null, 1);
         Binder b2 = new Binder();
         Binder b3 = new Binder();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
new file mode 100644
index 0000000..db6fc13
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationControllerTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.ViewGroup
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState.KEYGUARD
+import com.android.systemui.statusbar.StatusBarState.SHADE
+import com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED
+import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+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.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Translates items away/towards the hinge when the device is opened/closed. This is controlled by
+ * the set of ids, which also dictact which direction to move and when, via a filter fn.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class NotificationPanelUnfoldAnimationControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
+
+    @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
+
+    @Mock private lateinit var parent: ViewGroup
+
+    @Mock private lateinit var statusBarStateController: StatusBarStateController
+
+    private lateinit var underTest: NotificationPanelUnfoldAnimationController
+    private lateinit var progressListener: TransitionProgressListener
+    private var xTranslationMax = 0f
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        xTranslationMax =
+            context.resources.getDimensionPixelSize(R.dimen.notification_side_paddings).toFloat()
+
+        underTest =
+            NotificationPanelUnfoldAnimationController(
+                context,
+                statusBarStateController,
+                progressProvider
+            )
+        underTest.setup(parent)
+
+        verify(progressProvider).addCallback(capture(progressListenerCaptor))
+        progressListener = progressListenerCaptor.value
+    }
+
+    @Test
+    fun whenInKeyguardState_viewDoesNotMove() {
+        whenever(statusBarStateController.getState()).thenReturn(KEYGUARD)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+
+    @Test
+    fun whenInShadeLockedState_viewDoesMove() {
+        whenever(statusBarStateController.getState()).thenReturn(SHADE_LOCKED)
+
+        val view = View(context)
+        whenever(parent.findViewById<View>(R.id.quick_settings_panel)).thenReturn(view)
+
+        progressListener.onTransitionStarted()
+        assertThat(view.translationX).isZero()
+
+        progressListener.onTransitionProgress(0f)
+        assertThat(view.translationX).isEqualTo(xTranslationMax)
+
+        progressListener.onTransitionProgress(0.5f)
+        assertThat(view.translationX).isEqualTo(0.5f * xTranslationMax)
+
+        progressListener.onTransitionFinished()
+        assertThat(view.translationX).isZero()
+    }
+}
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 4843c76..0586a0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -105,6 +105,8 @@
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
 import com.android.systemui.media.controls.pipeline.MediaDataManager;
 import com.android.systemui.media.controls.ui.KeyguardMediaController;
@@ -288,6 +290,8 @@
     @Mock private KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
     @Mock private KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
     @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock private DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel;
+    @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock private MotionEvent mDownMotionEvent;
     @Captor
     private ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener>
@@ -505,6 +509,8 @@
                 mKeyguardBottomAreaViewModel,
                 mKeyguardBottomAreaInteractor,
                 mAlternateBouncerInteractor,
+                mDreamingToLockscreenTransitionViewModel,
+                mKeyguardTransitionInteractor,
                 mDumpManager);
         mNotificationPanelViewController.initDependencies(
                 mCentralSurfaces,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 43c6942..3e769e9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -21,6 +21,7 @@
 import android.provider.Settings.Secure.DOZE_TAP_SCREEN_GESTURE
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
+import android.os.PowerManager
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,9 +37,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
-import org.mockito.ArgumentMatchers.anyObject
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.never
@@ -106,7 +107,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces).wakeUpIfDozing(
+                anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
     }
 
     @Test
@@ -125,7 +127,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN wake up device if dozing
-        verify(centralSurfaces).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces).wakeUpIfDozing(
+                anyLong(), any(), anyString(), eq(PowerManager.WAKE_REASON_TAP))
     }
 
     @Test
@@ -156,7 +159,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -203,7 +207,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -222,7 +227,8 @@
         underTest.onSingleTapUp(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     @Test
@@ -241,7 +247,8 @@
         underTest.onDoubleTapEvent(upEv)
 
         // THEN the device doesn't wake up
-        verify(centralSurfaces, never()).wakeUpIfDozing(anyLong(), anyObject(), anyString())
+        verify(centralSurfaces, never()).wakeUpIfDozing(
+                anyLong(), any(), anyString(), anyInt())
     }
 
     fun updateSettings() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index 0dac9a2..942b6ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -675,7 +675,6 @@
         createController();
         String message = mContext.getString(R.string.keyguard_retry);
         when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
 
         mController.setVisible(true);
@@ -686,21 +685,6 @@
     }
 
     @Test
-    public void transientIndication_swipeUpToRetry_faceAuthenticated() {
-        createController();
-        String message = mContext.getString(R.string.keyguard_retry);
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true);
-
-        mController.setVisible(true);
-        mController.getKeyguardCallback().onBiometricError(FACE_ERROR_TIMEOUT,
-                "A message", BiometricSourceType.FACE);
-
-        verify(mStatusBarKeyguardViewManager, never()).setKeyguardMessage(eq(message), any());
-    }
-
-    @Test
     public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() {
         createController();
         when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
index 97fe25d..d3befb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt
@@ -20,6 +20,7 @@
 import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -36,7 +37,7 @@
 
   @Before
   fun setUp() {
-    scrim = LightRevealScrim(context, null)
+    scrim = LightRevealScrim(context, null, DEFAULT_WIDTH, DEFAULT_HEIGHT)
     scrim.isScrimOpaqueChangedListener = Consumer { opaque ->
       isOpaque = opaque
     }
@@ -63,4 +64,25 @@
     scrim.revealAmount = 0.5f
     assertFalse("Scrim is opaque even though it's revealed", scrim.isScrimOpaque)
   }
+
+  @Test
+  fun testBeforeOnMeasure_defaultDimensions() {
+    assertThat(scrim.viewWidth).isEqualTo(DEFAULT_WIDTH)
+    assertThat(scrim.viewHeight).isEqualTo(DEFAULT_HEIGHT)
+  }
+
+  @Test
+  fun testAfterOnMeasure_measuredDimensions() {
+    scrim.measure(/* widthMeasureSpec= */ exact(1), /* heightMeasureSpec= */ exact(2))
+
+    assertThat(scrim.viewWidth).isEqualTo(1)
+    assertThat(scrim.viewHeight).isEqualTo(2)
+  }
+
+  private fun exact(value: Int) = View.MeasureSpec.makeMeasureSpec(value, View.MeasureSpec.EXACTLY)
+
+  private companion object {
+    private const val DEFAULT_WIDTH = 42
+    private const val DEFAULT_HEIGHT = 24
+  }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 1ce460c..3a1f9b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -31,6 +31,7 @@
 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.Mockito.clearInvocations;
@@ -177,6 +178,8 @@
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.startingsurface.StartingSurface;
 
+import dagger.Lazy;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -189,8 +192,6 @@
 import java.io.PrintWriter;
 import java.util.Optional;
 
-import dagger.Lazy;
-
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper(setAsMainLooper = true)
@@ -306,6 +307,7 @@
     @Mock private ViewRootImpl mViewRootImpl;
     @Mock private WindowOnBackInvokedDispatcher mOnBackInvokedDispatcher;
     @Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
+    @Mock IPowerManager mPowerManagerService;
 
     private ShadeController mShadeController;
     private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -319,9 +321,8 @@
     public void setup() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        IPowerManager powerManagerService = mock(IPowerManager.class);
         IThermalService thermalService = mock(IThermalService.class);
-        mPowerManager = new PowerManager(mContext, powerManagerService, thermalService,
+        mPowerManager = new PowerManager(mContext, mPowerManagerService, thermalService,
                 Handler.createAsync(Looper.myLooper()));
 
         mNotificationInterruptStateProvider =
@@ -363,7 +364,7 @@
         when(mStackScrollerController.getView()).thenReturn(mStackScroller);
         when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
         when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
-        when(powerManagerService.isInteractive()).thenReturn(true);
+        when(mPowerManagerService.isInteractive()).thenReturn(true);
         when(mStackScroller.getActivatedChild()).thenReturn(null);
 
         doAnswer(invocation -> {
@@ -1190,6 +1191,34 @@
         verify(mStatusBarStateController).setState(SHADE);
     }
 
+    @Test
+    public void dozing_wakeUp() throws RemoteException {
+        // GIVEN can wakeup when dozing & is dozing
+        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+        setDozing(true);
+
+        // WHEN wakeup is requested
+        final int wakeReason = PowerManager.WAKE_REASON_TAP;
+        mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+        // THEN power manager receives wakeup
+        verify(mPowerManagerService).wakeUp(eq(0L), eq(wakeReason), anyString(), anyString());
+    }
+
+    @Test
+    public void notDozing_noWakeUp() throws RemoteException {
+        // GIVEN can wakeup when dozing and NOT dozing
+        when(mScreenOffAnimationController.allowWakeUpIfDozing()).thenReturn(true);
+        setDozing(false);
+
+        // WHEN wakeup is requested
+        final int wakeReason = PowerManager.WAKE_REASON_TAP;
+        mCentralSurfaces.wakeUpIfDozing(0, null, "", wakeReason);
+
+        // THEN power manager receives wakeup
+        verify(mPowerManagerService, never()).wakeUp(anyLong(), anyInt(), anyString(), anyString());
+    }
+
     /**
      * Configures the appropriate mocks and then calls {@link CentralSurfacesImpl#updateIsKeyguard}
      * to reconfigure the keyguard to reflect the requested showing/occluded states.
@@ -1226,6 +1255,13 @@
                 states);
     }
 
+    private void setDozing(boolean isDozing) {
+        ArgumentCaptor<StatusBarStateController.StateListener> callbackCaptor =
+                ArgumentCaptor.forClass(StatusBarStateController.StateListener.class);
+        verify(mStatusBarStateController).addCallback(callbackCaptor.capture(), anyInt());
+        callbackCaptor.getValue().onDozingChanged(isDozing);
+    }
+
     public static class TestableNotificationInterruptStateProviderImpl extends
             NotificationInterruptStateProviderImpl {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
new file mode 100644
index 0000000..f822ba0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableRowLogger
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+@SmallTest
+class MobileConnectionModelTest : SysuiTestCase() {
+
+    @Test
+    fun `log diff - initial log contains all columns`() {
+        val logger = TestLogger()
+        val connection = MobileConnectionModel()
+
+        connection.logFull(logger)
+
+        assertThat(logger.changes)
+            .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
+        assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
+        assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
+        assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
+        assertThat(logger.changes)
+            .contains(Pair(COL_ACTIVITY_DIRECTION, connection.dataActivityDirection.toString()))
+        assertThat(logger.changes)
+            .contains(
+                Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
+            )
+        assertThat(logger.changes)
+            .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
+    }
+
+    @Test
+    fun `log diff - primary level changes - only level is logged`() {
+        val logger = TestLogger()
+        val connectionOld = MobileConnectionModel(primaryLevel = 1)
+
+        val connectionNew = MobileConnectionModel(primaryLevel = 2)
+
+        connectionNew.logDiffs(connectionOld, logger)
+
+        assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
+    }
+
+    private class TestLogger : TableRowLogger {
+        val changes = mutableListOf<Pair<String, String>>()
+
+        override fun logChange(columnName: String, value: String?) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Int) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+
+        override fun logChange(columnName: String, value: Boolean) {
+            changes.add(Pair(columnName, value.toString()))
+        }
+    }
+}
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 5265ec6..d6a9ee3 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,11 +16,16 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
-class FakeMobileConnectionRepository(override val subId: Int) : MobileConnectionRepository {
+class FakeMobileConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionRepository {
     private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
     override val connectionInfo = _connectionInfo
 
@@ -30,6 +35,11 @@
     private val _isDefaultDataSubscription = MutableStateFlow(true)
     override val isDefaultDataSubscription = _isDefaultDataSubscription
 
+    override val cdmaRoaming = MutableStateFlow(false)
+
+    override val networkName =
+        MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
+
     fun setConnectionInfo(model: MobileConnectionModel) {
         _connectionInfo.value = model
     }
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 04d3cdd..7f93328 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
@@ -22,14 +22,17 @@
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
 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.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionsRepository
-class FakeMobileConnectionsRepository(mobileMappings: MobileMappingsProxy) :
-    MobileConnectionsRepository {
+class FakeMobileConnectionsRepository(
+    mobileMappings: MobileMappingsProxy,
+    val tableLogBuffer: TableLogBuffer,
+) : MobileConnectionsRepository {
     val GSM_KEY = mobileMappings.toIconKey(GSM)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val UMTS_KEY = mobileMappings.toIconKey(UMTS)
@@ -63,7 +66,7 @@
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId]
-            ?: FakeMobileConnectionRepository(subId).also { subIdRepos[subId] = it }
+            ?: FakeMobileConnectionRepository(subId, tableLogBuffer).also { subIdRepos[subId] = it }
     }
 
     private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index 18ae90d..5d377a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -24,6 +24,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.demomode.DemoMode
 import com.android.systemui.demomode.DemoModeController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.DemoModeMobileConnectionDataSource
@@ -37,6 +39,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -69,12 +72,14 @@
     private lateinit var realRepo: MobileConnectionsRepositoryImpl
     private lateinit var demoRepo: DemoMobileConnectionsRepository
     private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
+    private lateinit var logFactory: TableLogBufferFactory
 
     @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
+    @Mock private lateinit var dumpManager: DumpManager
 
     private val globalSettings = FakeSettings()
     private val fakeNetworkEventsFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
@@ -86,6 +91,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
         // Never start in demo mode
         whenever(demoModeController.isInDemoMode).thenReturn(false)
 
@@ -114,6 +121,7 @@
                 dataSource = mockDataSource,
                 scope = scope,
                 context = context,
+                logFactory = logFactory,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index e943de2..2102085 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -18,15 +18,20 @@
 
 import android.telephony.Annotation
 import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
 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.log.table.TableLogBufferFactory
 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.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
@@ -51,6 +56,9 @@
 @RunWith(Parameterized::class)
 internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
     SysuiTestCase() {
+
+    private val logFactory = TableLogBufferFactory(mock(), FakeSystemClock())
+
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -73,6 +81,7 @@
                 dataSource = mockDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
+                logFactory = logFactory,
             )
 
         connectionsRepo.startProcessingCommands()
@@ -95,6 +104,8 @@
                     inflateStrength = testCase.inflateStrength,
                     activity = testCase.activity,
                     carrierNetworkChange = testCase.carrierNetworkChange,
+                    roaming = testCase.roaming,
+                    name = "demo name",
                 )
 
             fakeNetworkEventFlow.value = networkModel
@@ -113,9 +124,12 @@
                 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.dataActivityDirection)
+                    .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
                 assertThat(connectionInfo.carrierNetworkChangeActive)
                     .isEqualTo(model.carrierNetworkChange)
+                assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+                assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
 
                 // TODO(b/261029387): check these once we start handling them
                 assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -138,6 +152,8 @@
         val inflateStrength: Boolean,
         @Annotation.DataActivityType val activity: Int,
         val carrierNetworkChange: Boolean,
+        val roaming: Boolean,
+        val name: String,
     ) {
         override fun toString(): String {
             return "INPUT(level=$level, " +
@@ -146,7 +162,9 @@
                 "carrierId=$carrierId, " +
                 "inflateStrength=$inflateStrength, " +
                 "activity=$activity, " +
-                "carrierNetworkChange=$carrierNetworkChange)"
+                "carrierNetworkChange=$carrierNetworkChange, " +
+                "roaming=$roaming, " +
+                "name=$name)"
         }
 
         // Convenience for iterating test data and creating new cases
@@ -158,6 +176,8 @@
             inflateStrength: Boolean? = null,
             @Annotation.DataActivityType activity: Int? = null,
             carrierNetworkChange: Boolean? = null,
+            roaming: Boolean? = null,
+            name: String? = null,
         ): TestCase =
             TestCase(
                 level = level ?: this.level,
@@ -166,7 +186,9 @@
                 carrierId = carrierId ?: this.carrierId,
                 inflateStrength = inflateStrength ?: this.inflateStrength,
                 activity = activity ?: this.activity,
-                carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange
+                carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
+                roaming = roaming ?: this.roaming,
+                name = name ?: this.name,
             )
     }
 
@@ -193,6 +215,9 @@
                 TelephonyManager.DATA_ACTIVITY_INOUT
             )
         private val carrierNetworkChange = booleanList
+        // false first so the base case doesn't have roaming set (more common)
+        private val roaming = listOf(false, true)
+        private val names = listOf("name 1", "name 2")
 
         @Parameters(name = "{0}") @JvmStatic fun data() = testData()
 
@@ -226,7 +251,9 @@
                     carrierIds.first(),
                     inflateStrength.first(),
                     activity.first(),
-                    carrierNetworkChange.first()
+                    carrierNetworkChange.first(),
+                    roaming.first(),
+                    names.first(),
                 )
 
             val tail =
@@ -237,6 +264,8 @@
                         inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
                         activity.map { baseCase.modifiedBy(activity = it) },
                         carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
+                        roaming.map { baseCase.modifiedBy(roaming = it) },
+                        names.map { baseCase.modifiedBy(name = it) },
                     )
                     .flatten()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index 32d0410..cdbe75e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -17,18 +17,24 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.demo
 
 import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT
+import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
 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.dump.DumpManager
+import com.android.systemui.log.table.TableLogBufferFactory
 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.NetworkNameModel
 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.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -44,6 +50,9 @@
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class DemoMobileConnectionsRepositoryTest : SysuiTestCase() {
+    private val dumpManager: DumpManager = mock()
+    private val logFactory = TableLogBufferFactory(dumpManager, FakeSystemClock())
+
     private val testDispatcher = UnconfinedTestDispatcher()
     private val testScope = TestScope(testDispatcher)
 
@@ -65,6 +74,7 @@
                 dataSource = mockDataSource,
                 scope = testScope.backgroundScope,
                 context = context,
+                logFactory = logFactory,
             )
 
         underTest.startProcessingCommands()
@@ -289,9 +299,12 @@
                 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.dataActivityDirection)
+                    .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
                 assertThat(connectionInfo.carrierNetworkChangeActive)
                     .isEqualTo(model.carrierNetworkChange)
+                assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+                assertThat(conn.networkName.value).isEqualTo(NetworkNameModel.Derived(model.name))
 
                 // TODO(b/261029387) check these once we start handling them
                 assertThat(connectionInfo.isEmergencyOnly).isFalse()
@@ -313,6 +326,7 @@
     inflateStrength: Boolean? = false,
     activity: Int? = null,
     carrierNetworkChange: Boolean = false,
+    roaming: Boolean = false,
 ): FakeNetworkEventModel =
     FakeNetworkEventModel.Mobile(
         level = level,
@@ -322,4 +336,6 @@
         inflateStrength = inflateStrength,
         activity = activity,
         carrierNetworkChange = carrierNetworkChange,
+        roaming = roaming,
+        name = "demo name",
     )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 1fc9c60..7970443 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.content.Intent
 import android.os.UserHandle
 import android.provider.Settings
 import android.telephony.CellSignalStrengthCdma
@@ -23,27 +24,45 @@
 import android.telephony.SignalStrength
 import android.telephony.SubscriptionInfo
 import android.telephony.TelephonyCallback
+import android.telephony.TelephonyCallback.DataActivityListener
 import android.telephony.TelephonyCallback.ServiceStateListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_ACTIVITY_DORMANT
+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 android.telephony.TelephonyManager.DATA_CONNECTED
 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.ERI_OFF
+import android.telephony.TelephonyManager.ERI_ON
+import android.telephony.TelephonyManager.EXTRA_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_PLMN
+import android.telephony.TelephonyManager.EXTRA_SHOW_SPN
+import android.telephony.TelephonyManager.EXTRA_SPN
+import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 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.log.table.TableLogBuffer
 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.NetworkNameModel
 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.toNetworkNameModel
 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.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
@@ -69,14 +88,15 @@
 @SmallTest
 class MobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionRepositoryImpl
+    private lateinit var connectionsRepo: FakeMobileConnectionsRepository
 
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
 
     private val scope = CoroutineScope(IMMEDIATE)
     private val mobileMappings = FakeMobileMappingsProxy()
     private val globalSettings = FakeSettings()
-    private val connectionsRepo = FakeMobileConnectionsRepository(mobileMappings)
 
     @Before
     fun setUp() {
@@ -84,17 +104,23 @@
         globalSettings.userId = UserHandle.USER_ALL
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
+        connectionsRepo = FakeMobileConnectionsRepository(mobileMappings, tableLogger)
+
         underTest =
             MobileConnectionRepositoryImpl(
                 context,
                 SUB_1_ID,
+                DEFAULT_NAME,
+                SEP,
                 telephonyManager,
                 globalSettings,
+                fakeBroadcastDispatcher,
                 connectionsRepo.defaultDataSubId,
                 connectionsRepo.globalMobileDataSettingChangedEvent,
                 mobileMappings,
                 IMMEDIATE,
                 logger,
+                tableLogger,
                 scope,
             )
     }
@@ -247,10 +273,11 @@
             var latest: MobileConnectionModel? = null
             val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
 
-            val callback = getTelephonyCallbackForType<TelephonyCallback.DataActivityListener>()
-            callback.onDataActivity(3)
+            val callback = getTelephonyCallbackForType<DataActivityListener>()
+            callback.onDataActivity(DATA_ACTIVITY_INOUT)
 
-            assertThat(latest?.dataActivityDirection).isEqualTo(3)
+            assertThat(latest?.dataActivityDirection)
+                .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
 
             job.cancel()
         }
@@ -402,6 +429,167 @@
             job.cancel()
         }
 
+    @Test
+    fun `roaming - cdma - queries telephony manager`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            // Start the telephony collection job so that cdmaRoaming starts updating
+            val telephonyJob = underTest.connectionInfo.launchIn(this)
+            val job = underTest.cdmaRoaming.onEach { latest = it }.launchIn(this)
+
+            val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+            val serviceState = ServiceState()
+            serviceState.roaming = false
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onServiceStateChanged(serviceState)
+
+            assertThat(latest).isFalse()
+
+            // CDMA roaming is off, GSM roaming is on
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_ON)
+            cb.onServiceStateChanged(serviceState)
+
+            assertThat(latest).isTrue()
+
+            telephonyJob.cancel()
+            job.cancel()
+        }
+
+    @Test
+    fun `roaming - gsm - queries service state`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+
+            val serviceState = ServiceState()
+            serviceState.roaming = false
+
+            val cb = getTelephonyCallbackForType<ServiceStateListener>()
+
+            // CDMA roaming is off, GSM roaming is off
+            whenever(telephonyManager.cdmaEnhancedRoamingIndicatorDisplayNumber).thenReturn(ERI_OFF)
+            cb.onServiceStateChanged(serviceState)
+
+            assertThat(latest).isFalse()
+
+            // CDMA roaming is off, GSM roaming is on
+            serviceState.roaming = true
+            cb.onServiceStateChanged(serviceState)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `activity - updates from callback`() =
+        runBlocking(IMMEDIATE) {
+            var latest: DataActivityModel? = null
+            val job =
+                underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+            val cb = getTelephonyCallbackForType<DataActivityListener>()
+            cb.onDataActivity(DATA_ACTIVITY_IN)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+
+            cb.onDataActivity(DATA_ACTIVITY_OUT)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+
+            cb.onDataActivity(DATA_ACTIVITY_INOUT)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+
+            cb.onDataActivity(DATA_ACTIVITY_NONE)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+            cb.onDataActivity(DATA_ACTIVITY_DORMANT)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+            cb.onDataActivity(1234)
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - default`() =
+        runBlocking(IMMEDIATE) {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - uses broadcast info - returns derived`() =
+        runBlocking(IMMEDIATE) {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent()
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(context, intent)
+            }
+
+            assertThat(latest).isEqualTo(intent.toNetworkNameModel(SEP))
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - broadcast not for this sub id - returns default`() =
+        runBlocking(IMMEDIATE) {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val intent = spnIntent(subId = 101)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(context, intent)
+            }
+
+            assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - operatorAlphaShort - tracked`() =
+        runBlocking(IMMEDIATE) {
+            var latest: String? = null
+
+            val job =
+                underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+
+            val shortName = "short name"
+            val serviceState = ServiceState()
+            serviceState.setOperatorName(
+                /* longName */ "long name",
+                /* shortName */ shortName,
+                /* numeric */ "12345",
+            )
+
+            getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
+
+            assertThat(latest).isEqualTo(shortName)
+
+            job.cancel()
+        }
+
     private fun getTelephonyCallbacks(): List<TelephonyCallback> {
         val callbackCaptor = argumentCaptor<TelephonyCallback>()
         Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
@@ -427,10 +615,31 @@
         return signalStrength
     }
 
+    private fun spnIntent(
+        subId: Int = SUB_1_ID,
+        showSpn: Boolean = true,
+        spn: String = SPN,
+        showPlmn: Boolean = true,
+        plmn: String = PLMN,
+    ): Intent =
+        Intent(TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED).apply {
+            putExtra(EXTRA_SUBSCRIPTION_ID, subId)
+            putExtra(EXTRA_SHOW_SPN, showSpn)
+            putExtra(EXTRA_SPN, spn)
+            putExtra(EXTRA_SHOW_PLMN, showPlmn)
+            putExtra(EXTRA_PLMN, plmn)
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
         private val SUB_1 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private val DEFAULT_NAME = NetworkNameModel.Default("default name")
+        private const val SEP = "-"
+
+        private const val SPN = "testSpn"
+        private const val PLMN = "testPlmn"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 6d80acb..b8cd7a4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -34,12 +34,15 @@
 import com.android.settingslib.R
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.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
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
@@ -57,6 +60,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -72,6 +76,7 @@
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var logBufferFactory: TableLogBufferFactory
 
     private val mobileMappings = FakeMobileMappingsProxy()
 
@@ -89,8 +94,13 @@
             }
         }
 
+        whenever(logBufferFactory.create(anyString(), anyInt())).thenAnswer { _ ->
+            mock<TableLogBuffer>()
+        }
+
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
+                fakeBroadcastDispatcher,
                 context = context,
                 telephonyManager = telephonyManager,
                 bgDispatcher = IMMEDIATE,
@@ -98,6 +108,7 @@
                 logger = logger,
                 mobileMappingsProxy = mobileMappings,
                 scope = scope,
+                logFactory = logBufferFactory,
             )
 
         underTest =
@@ -270,6 +281,32 @@
         }
 
     @Test
+    fun `connection repository - log buffer contains sub id in its name`() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.subscriptions.launchIn(this)
+
+            whenever(subscriptionManager.completeActiveSubscriptionInfoList)
+                .thenReturn(listOf(SUB_1, SUB_2))
+            getSubscriptionCallback().onSubscriptionsChanged()
+
+            // Get repos to trigger creation
+            underTest.getRepoForSubId(SUB_1_ID)
+            verify(logBufferFactory)
+                .create(
+                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_1_ID)),
+                    anyInt(),
+                )
+            underTest.getRepoForSubId(SUB_2_ID)
+            verify(logBufferFactory)
+                .create(
+                    eq(MobileConnectionRepositoryImpl.tableBufferLogName(SUB_2_ID)),
+                    anyInt(),
+                )
+
+            job.cancel()
+        }
+
+    @Test
     fun testDefaultDataSubId_updatesOnBroadcast() =
         runBlocking(IMMEDIATE) {
             var latest: Int? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index 1ff1636a..c494589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -19,17 +19,34 @@
 import android.telephony.CellSignalStrength
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileIconInteractor : MobileIconInteractor {
+class FakeMobileIconInteractor(
+    override val tableLogBuffer: TableLogBuffer,
+) : MobileIconInteractor {
     override val alwaysShowDataRatIcon = MutableStateFlow(false)
 
+    override val activity =
+        MutableStateFlow(
+            DataActivityModel(
+                hasActivityIn = false,
+                hasActivityOut = false,
+            )
+        )
+
     private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
     override val networkTypeIconGroup = _iconGroup
 
+    override val networkName = MutableStateFlow(NetworkNameModel.Derived("demo mode"))
+
     private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly = _isEmergencyOnly
 
+    override val isRoaming = MutableStateFlow(false)
+
     private val _isFailedConnection = MutableStateFlow(false)
     override val isDefaultConnectionFailed = _isFailedConnection
 
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 9f300e9..19e5516 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
@@ -22,12 +22,15 @@
 import android.telephony.TelephonyManager.NETWORK_TYPE_UMTS
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.TableLogBuffer
 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 {
+class FakeMobileIconsInteractor(
+    mobileMappings: MobileMappingsProxy,
+    val tableLogBuffer: TableLogBuffer,
+) : MobileIconsInteractor {
     val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -48,8 +51,7 @@
 
     override val isDefaultConnectionFailed = MutableStateFlow(false)
 
-    private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
-    override val filteredSubscriptions: Flow<List<SubscriptionModel>> = _filteredSubscriptions
+    override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf())
 
     private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
     override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
@@ -67,7 +69,7 @@
 
     /** Always returns a new fake interactor */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor {
-        return FakeMobileIconInteractor()
+        return FakeMobileIconInteractor(tableLogBuffer)
     }
 
     companion object {
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 2281e89b..83c5055 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
@@ -25,6 +25,7 @@
 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.NetworkNameModel
 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
@@ -48,8 +49,8 @@
 class MobileIconInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconInteractor
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
-    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID)
+    private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy, mock())
+    private val connectionRepository = FakeMobileConnectionRepository(SUB_1_ID, mock())
 
     private val scope = CoroutineScope(IMMEDIATE)
 
@@ -298,6 +299,135 @@
             job.cancel()
         }
 
+    @Test
+    fun `roaming - is gsm - uses connection model`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.cdmaRoaming.value = true
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = true,
+                    isRoaming = false,
+                )
+            )
+            yield()
+
+            assertThat(latest).isFalse()
+
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = true,
+                    isRoaming = true,
+                )
+            )
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `roaming - is cdma - uses cdma roaming bit`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.cdmaRoaming.value = false
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = false,
+                    isRoaming = true,
+                )
+            )
+            yield()
+
+            assertThat(latest).isFalse()
+
+            connectionRepository.cdmaRoaming.value = true
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = false,
+                    isRoaming = false,
+                )
+            )
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `roaming - false while carrierNetworkChangeActive`() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.cdmaRoaming.value = true
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = false,
+                    isRoaming = true,
+                    carrierNetworkChangeActive = true,
+                )
+            )
+            yield()
+
+            assertThat(latest).isFalse()
+
+            connectionRepository.cdmaRoaming.value = true
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(
+                    isGsm = true,
+                    isRoaming = true,
+                    carrierNetworkChangeActive = true,
+                )
+            )
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `network name - uses operatorAlphaShot when non null and repo is default`() =
+        runBlocking(IMMEDIATE) {
+            var latest: NetworkNameModel? = null
+            val job = underTest.networkName.onEach { latest = it }.launchIn(this)
+
+            val testOperatorName = "operatorAlphaShort"
+
+            // Default network name, operator name is non-null, uses the operator name
+            connectionRepository.networkName.value = DEFAULT_NAME
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(operatorAlphaShort = testOperatorName)
+            )
+            yield()
+
+            assertThat(latest).isEqualTo(NetworkNameModel.Derived(testOperatorName))
+
+            // Default network name, operator name is null, uses the default
+            connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+            yield()
+
+            assertThat(latest).isEqualTo(DEFAULT_NAME)
+
+            // Derived network name, operator name non-null, uses the derived name
+            connectionRepository.networkName.value = DERIVED_NAME
+            connectionRepository.setConnectionInfo(
+                MobileConnectionModel(operatorAlphaShort = testOperatorName)
+            )
+            yield()
+
+            assertThat(latest).isEqualTo(DERIVED_NAME)
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
@@ -307,5 +437,8 @@
         private const val SUB_1_ID = 1
         private val SUB_1 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+
+        private val DEFAULT_NAME = NetworkNameModel.Default("test default name")
+        private val DERIVED_NAME = NetworkNameModel.Derived("test derived name")
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index 8557894..2fa3467 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
@@ -20,6 +20,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
 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
@@ -28,6 +29,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -44,9 +46,9 @@
 @SmallTest
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
+    private lateinit var connectionsRepository: FakeMobileConnectionsRepository
     private val userSetupRepository = FakeUserSetupRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
-    private val connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy)
     private val scope = CoroutineScope(IMMEDIATE)
 
     @Mock private lateinit var carrierConfigTracker: CarrierConfigTracker
@@ -55,6 +57,7 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
+        connectionsRepository = FakeMobileConnectionsRepository(mobileMappingsProxy, tableLogBuffer)
         connectionsRepository.setMobileConnectionRepositoryMap(
             mapOf(
                 SUB_1_ID to CONNECTION_1,
@@ -290,21 +293,23 @@
 
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
+        private val tableLogBuffer =
+            TableLogBuffer(8, "MobileIconsInteractorTest", FakeSystemClock())
 
         private const val SUB_1_ID = 1
         private val SUB_1 = SubscriptionModel(subscriptionId = SUB_1_ID)
-        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID)
+        private val CONNECTION_1 = FakeMobileConnectionRepository(SUB_1_ID, tableLogBuffer)
 
         private const val SUB_2_ID = 2
         private val SUB_2 = SubscriptionModel(subscriptionId = SUB_2_ID)
-        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID)
+        private val CONNECTION_2 = FakeMobileConnectionRepository(SUB_2_ID, tableLogBuffer)
 
         private const val SUB_3_ID = 3
         private val SUB_3_OPP = SubscriptionModel(subscriptionId = SUB_3_ID, isOpportunistic = true)
-        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID)
+        private val CONNECTION_3 = FakeMobileConnectionRepository(SUB_3_ID, tableLogBuffer)
 
         private const val SUB_4_ID = 4
         private val SUB_4_OPP = SubscriptionModel(subscriptionId = SUB_4_ID, isOpportunistic = true)
-        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID)
+        private val CONNECTION_4 = FakeMobileConnectionRepository(SUB_4_ID, tableLogBuffer)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
new file mode 100644
index 0000000..043d55a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/LocationBasedMobileIconViewModelTest.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconViewModelTest.Companion.defaultSignal
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class LocationBasedMobileIconViewModelTest : SysuiTestCase() {
+    private lateinit var commonImpl: MobileIconViewModelCommon
+    private lateinit var homeIcon: HomeMobileIconViewModel
+    private lateinit var qsIcon: QsMobileIconViewModel
+    private lateinit var keyguardIcon: KeyguardMobileIconViewModel
+    private lateinit var interactor: FakeMobileIconInteractor
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        interactor = FakeMobileIconInteractor(tableLogBuffer)
+        interactor.apply {
+            setLevel(1)
+            setIsDefaultDataEnabled(true)
+            setIsFailedConnection(false)
+            setIconGroup(TelephonyIcons.THREE_G)
+            setIsEmergencyOnly(false)
+            setNumberOfLevels(4)
+            isDataConnected.value = true
+        }
+        commonImpl =
+            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
+
+        homeIcon = HomeMobileIconViewModel(commonImpl, logger)
+        qsIcon = QsMobileIconViewModel(commonImpl, logger)
+        keyguardIcon = KeyguardMobileIconViewModel(commonImpl, logger)
+    }
+
+    @Test
+    fun `location based view models receive same icon id when common impl updates`() =
+        testScope.runTest {
+            var latestHome: Int? = null
+            val homeJob = homeIcon.iconId.onEach { latestHome = it }.launchIn(this)
+
+            var latestQs: Int? = null
+            val qsJob = qsIcon.iconId.onEach { latestQs = it }.launchIn(this)
+
+            var latestKeyguard: Int? = null
+            val keyguardJob = keyguardIcon.iconId.onEach { latestKeyguard = it }.launchIn(this)
+
+            var expected = defaultSignal(level = 1)
+
+            assertThat(latestHome).isEqualTo(expected)
+            assertThat(latestQs).isEqualTo(expected)
+            assertThat(latestKeyguard).isEqualTo(expected)
+
+            interactor.setLevel(2)
+            expected = defaultSignal(level = 2)
+
+            assertThat(latestHome).isEqualTo(expected)
+            assertThat(latestQs).isEqualTo(expected)
+            assertThat(latestKeyguard).isEqualTo(expected)
+
+            homeJob.cancel()
+            qsJob.cancel()
+            keyguardJob.cancel()
+        }
+
+    companion object {
+        private const val SUB_1_ID = 1
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index f2533a9..50221bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -22,28 +22,42 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 class MobileIconViewModelTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconViewModel
-    private val interactor = FakeMobileIconInteractor()
+    private lateinit var interactor: FakeMobileIconInteractor
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
+    @Mock private lateinit var tableLogBuffer: TableLogBuffer
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        interactor = FakeMobileIconInteractor(tableLogBuffer)
         interactor.apply {
             setLevel(1)
             setIsDefaultDataEnabled(true)
@@ -53,12 +67,13 @@
             setNumberOfLevels(4)
             isDataConnected.value = true
         }
-        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+        underTest =
+            MobileIconViewModel(SUB_1_ID, interactor, logger, constants, testScope.backgroundScope)
     }
 
     @Test
     fun iconId_correctLevel_notCutout() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             var latest: Int? = null
             val job = underTest.iconId.onEach { latest = it }.launchIn(this)
             val expected = defaultSignal()
@@ -70,7 +85,7 @@
 
     @Test
     fun iconId_cutout_whenDefaultDataDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIsDefaultDataEnabled(false)
 
             var latest: Int? = null
@@ -84,7 +99,7 @@
 
     @Test
     fun networkType_dataEnabled_groupIsRepresented() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -102,7 +117,7 @@
 
     @Test
     fun networkType_nullWhenDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(false)
             var latest: Icon? = null
@@ -115,7 +130,7 @@
 
     @Test
     fun networkType_nullWhenFailedConnection() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(true)
             interactor.setIsFailedConnection(true)
@@ -129,7 +144,7 @@
 
     @Test
     fun networkType_nullWhenDataDisconnects() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val initial =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -153,7 +168,7 @@
 
     @Test
     fun networkType_null_changeToDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             val expected =
                 Icon.Resource(
                     THREE_G.dataType,
@@ -176,7 +191,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenDisabled() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsDataEnabled(true)
             interactor.alwaysShowDataRatIcon.value = true
@@ -196,7 +211,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenDisconnected() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.isDataConnected.value = false
             interactor.alwaysShowDataRatIcon.value = true
@@ -216,7 +231,7 @@
 
     @Test
     fun networkType_alwaysShow_shownEvenWhenFailedConnection() =
-        runBlocking(IMMEDIATE) {
+        testScope.runTest {
             interactor.setIconGroup(THREE_G)
             interactor.setIsFailedConnection(true)
             interactor.alwaysShowDataRatIcon.value = true
@@ -234,16 +249,131 @@
             job.cancel()
         }
 
-    /** Convenience constructor for these tests */
-    private fun defaultSignal(
-        level: Int = 1,
-        connected: Boolean = true,
-    ): Int {
-        return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
-    }
+    @Test
+    fun roaming() =
+        testScope.runTest {
+            interactor.isRoaming.value = true
+            var latest: Boolean? = null
+            val job = underTest.roaming.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            interactor.isRoaming.value = false
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun `data activity - null when config is off`() =
+        testScope.runTest {
+            // Create a new view model here so the constants are properly read
+            whenever(constants.shouldShowActivityConfig).thenReturn(false)
+            underTest =
+                MobileIconViewModel(
+                    SUB_1_ID,
+                    interactor,
+                    logger,
+                    constants,
+                    testScope.backgroundScope,
+                )
+
+            var inVisible: Boolean? = null
+            val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+            var outVisible: Boolean? = null
+            val outJob = underTest.activityInVisible.onEach { outVisible = it }.launchIn(this)
+
+            var containerVisible: Boolean? = null
+            val containerJob =
+                underTest.activityInVisible.onEach { containerVisible = it }.launchIn(this)
+
+            interactor.activity.value =
+                DataActivityModel(
+                    hasActivityIn = true,
+                    hasActivityOut = true,
+                )
+
+            assertThat(inVisible).isFalse()
+            assertThat(outVisible).isFalse()
+            assertThat(containerVisible).isFalse()
+
+            inJob.cancel()
+            outJob.cancel()
+            containerJob.cancel()
+        }
+
+    @Test
+    fun `data activity - config on - test indicators`() =
+        testScope.runTest {
+            // Create a new view model here so the constants are properly read
+            whenever(constants.shouldShowActivityConfig).thenReturn(true)
+            underTest =
+                MobileIconViewModel(
+                    SUB_1_ID,
+                    interactor,
+                    logger,
+                    constants,
+                    testScope.backgroundScope,
+                )
+
+            var inVisible: Boolean? = null
+            val inJob = underTest.activityInVisible.onEach { inVisible = it }.launchIn(this)
+
+            var outVisible: Boolean? = null
+            val outJob = underTest.activityOutVisible.onEach { outVisible = it }.launchIn(this)
+
+            var containerVisible: Boolean? = null
+            val containerJob =
+                underTest.activityContainerVisible.onEach { containerVisible = it }.launchIn(this)
+
+            interactor.activity.value =
+                DataActivityModel(
+                    hasActivityIn = true,
+                    hasActivityOut = false,
+                )
+
+            yield()
+
+            assertThat(inVisible).isTrue()
+            assertThat(outVisible).isFalse()
+            assertThat(containerVisible).isTrue()
+
+            interactor.activity.value =
+                DataActivityModel(
+                    hasActivityIn = false,
+                    hasActivityOut = true,
+                )
+
+            assertThat(inVisible).isFalse()
+            assertThat(outVisible).isTrue()
+            assertThat(containerVisible).isTrue()
+
+            interactor.activity.value =
+                DataActivityModel(
+                    hasActivityIn = false,
+                    hasActivityOut = false,
+                )
+
+            assertThat(inVisible).isFalse()
+            assertThat(outVisible).isFalse()
+            assertThat(containerVisible).isFalse()
+
+            inJob.cancel()
+            outJob.cancel()
+            containerJob.cancel()
+        }
 
     companion object {
-        private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
+
+        /** Convenience constructor for these tests */
+        fun defaultSignal(
+            level: Int = 1,
+            connected: Boolean = true,
+        ): Int {
+            return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
new file mode 100644
index 0000000..d6cb762
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt
@@ -0,0 +1,106 @@
+/*
+ * 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.ui.viewmodel
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
+import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor
+import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class MobileIconsViewModelTest : SysuiTestCase() {
+    private lateinit var underTest: MobileIconsViewModel
+    private val interactor = FakeMobileIconsInteractor(FakeMobileMappingsProxy(), mock())
+
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        val subscriptionIdsFlow =
+            interactor.filteredSubscriptions
+                .map { subs -> subs.map { it.subscriptionId } }
+                .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf())
+
+        underTest =
+            MobileIconsViewModel(
+                subscriptionIdsFlow,
+                interactor,
+                logger,
+                constants,
+                testScope.backgroundScope,
+            )
+
+        interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2)
+    }
+
+    @Test
+    fun `caching - mobile icon view model is reused for same sub id`() =
+        testScope.runTest {
+            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+            val model2 = underTest.viewModelForSub(1, StatusBarLocation.QS)
+
+            assertThat(model1.commonImpl).isSameInstanceAs(model2.commonImpl)
+        }
+
+    @Test
+    fun `caching - invalid view models are removed from cache when sub disappears`() =
+        testScope.runTest {
+            // Retrieve models to trigger caching
+            val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME)
+            val model2 = underTest.viewModelForSub(2, StatusBarLocation.QS)
+
+            // Both impls are cached
+            assertThat(underTest.mobileIconSubIdCache)
+                .containsExactly(1, model1.commonImpl, 2, model2.commonImpl)
+
+            // SUB_1 is removed from the list...
+            interactor.filteredSubscriptions.value = listOf(SUB_2)
+
+            // ... and dropped from the cache
+            assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl)
+        }
+
+    companion object {
+        private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false)
+        private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 2f18ce3..4e15b4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -35,7 +35,7 @@
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
-    override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
+    override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
 
     fun setIsWifiEnabled(enabled: Boolean) {
         _isWifiEnabled.value = enabled
@@ -49,7 +49,7 @@
         _wifiNetwork.value = wifiNetworkModel
     }
 
-    fun setWifiActivity(activity: WifiActivityModel) {
+    fun setWifiActivity(activity: DataActivityModel) {
         _wifiActivity.value = activity
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
index 800f3c0..5d0d87b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt
@@ -31,10 +31,10 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -724,7 +724,7 @@
     fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
         underTest = createRepo(wifiManagerToUse = null)
 
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
                 .wifiActivity
                 .onEach { latest = it }
@@ -737,7 +737,7 @@
 
     @Test
     fun wifiActivity_callbackGivesNone_activityFlowHasNone() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
                 .wifiActivity
                 .onEach { latest = it }
@@ -746,7 +746,7 @@
         getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_NONE)
 
         assertThat(latest).isEqualTo(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+            DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         )
 
         job.cancel()
@@ -754,7 +754,7 @@
 
     @Test
     fun wifiActivity_callbackGivesIn_activityFlowHasIn() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
                 .wifiActivity
                 .onEach { latest = it }
@@ -763,7 +763,7 @@
         getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_IN)
 
         assertThat(latest).isEqualTo(
-            WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+            DataActivityModel(hasActivityIn = true, hasActivityOut = false)
         )
 
         job.cancel()
@@ -771,7 +771,7 @@
 
     @Test
     fun wifiActivity_callbackGivesOut_activityFlowHasOut() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
                 .wifiActivity
                 .onEach { latest = it }
@@ -780,7 +780,7 @@
         getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_OUT)
 
         assertThat(latest).isEqualTo(
-            WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+            DataActivityModel(hasActivityIn = false, hasActivityOut = true)
         )
 
         job.cancel()
@@ -788,7 +788,7 @@
 
     @Test
     fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
                 .wifiActivity
                 .onEach { latest = it }
@@ -796,7 +796,7 @@
 
         getTrafficStateCallback().onStateChanged(TrafficStateCallback.DATA_ACTIVITY_INOUT)
 
-        assertThat(latest).isEqualTo(WifiActivityModel(hasActivityIn = true, hasActivityOut = true))
+        assertThat(latest).isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
 
         job.cancel()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
new file mode 100644
index 0000000..b935442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -0,0 +1,137 @@
+/*
+ * 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.data.repository
+
+import android.net.ConnectivityManager
+import android.net.wifi.WifiManager
+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.log.table.TableLogBuffer
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.kotlinArgumentCaptor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.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
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+class WifiRepositorySwitcherTest : SysuiTestCase() {
+    private lateinit var underTest: WifiRepositorySwitcher
+    private lateinit var realImpl: WifiRepositoryImpl
+    private lateinit var demoImpl: DemoWifiRepository
+
+    @Mock private lateinit var demoModeController: DemoModeController
+    @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var tableLogger: TableLogBuffer
+    @Mock private lateinit var connectivityManager: ConnectivityManager
+    @Mock private lateinit var wifiManager: WifiManager
+    @Mock private lateinit var demoModeWifiDataSource: DemoModeWifiDataSource
+    private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
+
+    private val mainExecutor = FakeExecutor(FakeSystemClock())
+
+    private val testDispatcher = UnconfinedTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+
+        // Never start in demo mode
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+
+        realImpl =
+            WifiRepositoryImpl(
+                fakeBroadcastDispatcher,
+                connectivityManager,
+                logger,
+                tableLogger,
+                mainExecutor,
+                testScope.backgroundScope,
+                wifiManager,
+            )
+
+        whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
+
+        demoImpl =
+            DemoWifiRepository(
+                demoModeWifiDataSource,
+                testScope.backgroundScope,
+            )
+
+        underTest =
+            WifiRepositorySwitcher(
+                realImpl,
+                demoImpl,
+                demoModeController,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun `switcher active repo - updates when demo mode changes`() =
+        testScope.runTest {
+            assertThat(underTest.activeRepo.value).isSameInstanceAs(realImpl)
+
+            var latest: WifiRepository? = null
+            val job = underTest.activeRepo.onEach { latest = it }.launchIn(this)
+
+            startDemoMode()
+
+            assertThat(latest).isSameInstanceAs(demoImpl)
+
+            finishDemoMode()
+
+            assertThat(latest).isSameInstanceAs(realImpl)
+
+            job.cancel()
+        }
+
+    private fun startDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(true)
+        getDemoModeCallback().onDemoModeStarted()
+    }
+
+    private fun finishDemoMode() {
+        whenever(demoModeController.isInDemoMode).thenReturn(false)
+        getDemoModeCallback().onDemoModeFinished()
+    }
+
+    private fun getDemoModeCallback(): DemoMode {
+        val captor = kotlinArgumentCaptor<DemoMode>()
+        Mockito.verify(demoModeController).addCallback(captor.capture())
+        return captor.value
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
index b38497a..2ecb17b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorImplTest.kt
@@ -20,10 +20,10 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
-import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -225,23 +225,23 @@
 
     @Test
     fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
-        var latest: WifiActivityModel? = null
+        var latest: DataActivityModel? = null
         val job = underTest
             .activity
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        val activity1 = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity1)
         yield()
         assertThat(latest).isEqualTo(activity1)
 
-        val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        val activity2 = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity2)
         yield()
         assertThat(latest).isEqualTo(activity2)
 
-        val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        val activity3 = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity3)
         yield()
         assertThat(latest).isEqualTo(activity3)
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 7502020..4158434 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
@@ -27,13 +27,13 @@
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
 import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 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.shared.model.WifiActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -146,7 +146,7 @@
 
     @Test
     fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -183,7 +183,7 @@
 
     @Test
     fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(false)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -209,7 +209,7 @@
             .launchIn(this)
 
         // WHEN we update the repo to have activity
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -225,7 +225,7 @@
 
     @Test
     fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
 
         wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
@@ -252,7 +252,7 @@
             .launchIn(this)
 
         // WHEN we update the repo to have activity
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -268,7 +268,7 @@
 
     @Test
     fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -293,7 +293,7 @@
             .onEach { latestQs = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -308,7 +308,7 @@
 
     @Test
     fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -319,7 +319,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -330,7 +330,7 @@
 
     @Test
     fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -341,7 +341,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -352,7 +352,7 @@
 
     @Test
     fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -363,7 +363,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -374,7 +374,7 @@
 
     @Test
     fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -385,7 +385,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -396,7 +396,7 @@
 
     @Test
     fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -407,7 +407,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -418,7 +418,7 @@
 
     @Test
     fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -429,7 +429,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -440,7 +440,7 @@
 
     @Test
     fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -451,7 +451,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+        val activity = DataActivityModel(hasActivityIn = true, hasActivityOut = true)
         wifiRepository.setWifiActivity(activity)
         yield()
 
@@ -462,7 +462,7 @@
 
     @Test
     fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
-        whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+        whenever(connectivityConstants.shouldShowActivityConfig).thenReturn(true)
         createAndSetViewModel()
         wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
 
@@ -473,7 +473,7 @@
             .onEach { latest = it }
             .launchIn(this)
 
-        val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+        val activity = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         wifiRepository.setWifiActivity(activity)
         yield()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 2c47204..4b32ee2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -55,6 +55,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.widget.EditText;
+import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.window.OnBackInvokedCallback;
 import android.window.OnBackInvokedDispatcher;
@@ -414,7 +415,9 @@
                 mDependency,
                 TestableLooper.get(this));
         ExpandableNotificationRow row = helper.createRow();
+        FrameLayout remoteInputViewParent = new FrameLayout(mContext);
         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        remoteInputViewParent.addView(view);
         bindController(view, row.getEntry());
 
         // Start defocus animation
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index 82153d5..99e2012 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -66,6 +67,8 @@
     @Mock
     private lateinit var configurationController: ConfigurationController
     @Mock
+    private lateinit var dumpManager: DumpManager
+    @Mock
     private lateinit var windowManager: WindowManager
     @Mock
     private lateinit var powerManager: PowerManager
@@ -91,6 +94,7 @@
             fakeExecutor,
             accessibilityManager,
             configurationController,
+            dumpManager,
             powerManager,
             fakeWakeLockBuilder,
             fakeClock,
@@ -989,6 +993,7 @@
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
         configurationController: ConfigurationController,
+        dumpManager: DumpManager,
         powerManager: PowerManager,
         wakeLockBuilder: WakeLock.Builder,
         systemClock: SystemClock,
@@ -999,6 +1004,7 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
+        dumpManager,
         powerManager,
         R.layout.chipbar,
         wakeLockBuilder,
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 2e4d8e7..d3411c2 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
@@ -36,6 +36,7 @@
 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.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -66,6 +67,7 @@
     @Mock private lateinit var logger: ChipbarLogger
     @Mock private lateinit var accessibilityManager: AccessibilityManager
     @Mock private lateinit var configurationController: ConfigurationController
+    @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var powerManager: PowerManager
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var falsingManager: FalsingManager
@@ -100,6 +102,7 @@
                 fakeExecutor,
                 accessibilityManager,
                 configurationController,
+                dumpManager,
                 powerManager,
                 falsingManager,
                 falsingCollector,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index d5167b3..4ef4e6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,6 +22,7 @@
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -38,6 +39,7 @@
     mainExecutor: DelayableExecutor,
     accessibilityManager: AccessibilityManager,
     configurationController: ConfigurationController,
+    dumpManager: DumpManager,
     powerManager: PowerManager,
     falsingManager: FalsingManager,
     falsingCollector: FalsingCollector,
@@ -53,6 +55,7 @@
         mainExecutor,
         accessibilityManager,
         configurationController,
+        dumpManager,
         powerManager,
         falsingManager,
         falsingCollector,
@@ -61,7 +64,7 @@
         wakeLockBuilder,
         systemClock,
     ) {
-    override fun animateViewOut(view: ViewGroup, onAnimationEnd: Runnable) {
+    override fun animateViewOut(view: ViewGroup, removalReason: String?, onAnimationEnd: Runnable) {
         // Just bypass the animation in tests
         onAnimationEnd.run()
     }
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 ffa4e2d..9534575 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
@@ -175,6 +175,8 @@
 
             underTest.onRecordSelected(UserRecord(info = userInfos[1]), dialogShower)
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.SWITCH_TO_USER_FROM_USER_SWITCHER)
             verify(dialogShower).dismiss()
             verify(activityManager).switchUser(userInfos[1].id)
             Unit
@@ -190,6 +192,33 @@
 
             underTest.onRecordSelected(UserRecord(info = userInfos.last()))
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.SWITCH_TO_GUEST_FROM_USER_SWITCHER)
+            verify(activityManager).switchUser(userInfos.last().id)
+            Unit
+        }
+
+    @Test
+    fun `onRecordSelected - switch to restricted user`() =
+        runBlocking(IMMEDIATE) {
+            var userInfos = createUserInfos(count = 2, includeGuest = false).toMutableList()
+            userInfos.add(
+                UserInfo(
+                    60,
+                    "Restricted user",
+                    /* iconPath= */ "",
+                    /* flags= */ UserInfo.FLAG_FULL,
+                    UserManager.USER_TYPE_FULL_RESTRICTED,
+                )
+            )
+            userRepository.setUserInfos(userInfos)
+            userRepository.setSelectedUserInfo(userInfos[0])
+            userRepository.setSettings(UserSwitcherSettingsModel(isUserSwitcherEnabled = true))
+
+            underTest.onRecordSelected(UserRecord(info = userInfos.last()))
+
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.SWITCH_TO_RESTRICTED_USER_FROM_USER_SWITCHER)
             verify(activityManager).switchUser(userInfos.last().id)
             Unit
         }
@@ -206,6 +235,8 @@
 
             underTest.onRecordSelected(UserRecord(isGuest = true), dialogShower)
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.CREATE_GUEST_FROM_USER_SWITCHER)
             verify(dialogShower).dismiss()
             verify(manager).createGuest(any())
             Unit
@@ -221,6 +252,8 @@
 
             underTest.onRecordSelected(UserRecord(isAddSupervisedUser = true), dialogShower)
 
+            verify(uiEventLogger, times(1))
+                .log(MultiUserActionsEvent.CREATE_RESTRICTED_USER_FROM_USER_SWITCHER)
             verify(dialogShower, never()).dismiss()
             verify(activityStarter).startActivity(any(), anyBoolean())
         }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 5501949..39d2eca 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -46,12 +46,18 @@
     private val _isKeyguardShowing = MutableStateFlow(false)
     override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
 
+    private val _isKeyguardOccluded = MutableStateFlow(false)
+    override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
+
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: Flow<Boolean> = _isDozing
 
     private val _isDreaming = MutableStateFlow(false)
     override val isDreaming: Flow<Boolean> = _isDreaming
 
+    private val _isDreamingWithOverlay = MutableStateFlow(false)
+    override val isDreamingWithOverlay: Flow<Boolean> = _isDreamingWithOverlay
+
     private val _dozeAmount = MutableStateFlow(0f)
     override val linearDozeAmount: Flow<Float> = _dozeAmount
 
@@ -112,10 +118,18 @@
         _isKeyguardShowing.value = isShowing
     }
 
+    fun setKeyguardOccluded(isOccluded: Boolean) {
+        _isKeyguardOccluded.value = isOccluded
+    }
+
     fun setDozing(isDozing: Boolean) {
         _isDozing.value = isDozing
     }
 
+    fun setDreamingWithOverlay(isDreaming: Boolean) {
+        _isDreamingWithOverlay.value = isDreaming
+    }
+
     fun setDozeAmount(dozeAmount: Float) {
         _dozeAmount.value = dozeAmount
     }
@@ -144,6 +158,10 @@
         _fingerprintSensorLocation.tryEmit(location)
     }
 
+    fun setDozeTransitionModel(model: DozeTransitionModel) {
+        _dozeTransitionModel.value = model
+    }
+
     override fun isUdfpsSupported(): Boolean {
         return _isUdfpsSupported.value
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index dd9f1d8..f4a62f9 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -545,6 +545,10 @@
                 sz.height = size.getHeight();
                 sizeList.sizes.add(sz);
             }
+
+            if (!sizeList.sizes.isEmpty()) {
+                ret.add(sizeList);
+            }
         }
 
         return ret;
@@ -740,6 +744,19 @@
         }
 
         @Override
+        public List<SizeList> getSupportedPostviewResolutions(
+                android.hardware.camera2.extension.Size captureSize) {
+            Size sz = new Size(captureSize.width, captureSize.height);
+            Map<Integer, List<Size>> supportedSizesMap =
+                    mAdvancedExtender.getSupportedPostviewResolutions(sz);
+            if (supportedSizesMap != null) {
+                return initializeParcelable(supportedSizesMap);
+            }
+
+            return null;
+        }
+
+        @Override
         public List<SizeList> getSupportedPreviewOutputResolutions(String cameraId) {
             Map<Integer, List<Size>> supportedSizesMap =
                     mAdvancedExtender.getSupportedPreviewOutputResolutions(cameraId);
@@ -840,6 +857,15 @@
 
             return false;
         }
+
+        @Override
+        public boolean isPostviewAvailable() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                return mAdvancedExtender.isPostviewAvailable();
+            }
+
+            return false;
+        }
     }
 
     private class CaptureCallbackStub implements SessionProcessorImpl.CaptureCallback {
@@ -1167,11 +1193,13 @@
 
         @Override
         public CameraSessionConfig initSession(String cameraId, OutputSurface previewSurface,
-                OutputSurface imageCaptureSurface) {
+                OutputSurface imageCaptureSurface, OutputSurface postviewSurface) {
             OutputSurfaceImplStub outputPreviewSurfaceImpl =
                     new OutputSurfaceImplStub(previewSurface);
             OutputSurfaceImplStub outputImageCaptureSurfaceImpl =
                     new OutputSurfaceImplStub(imageCaptureSurface);
+            OutputSurfaceImplStub outputPostviewSurfaceImpl =
+                    new OutputSurfaceImplStub(postviewSurface);
 
             Camera2SessionConfigImpl sessionConfig;
 
@@ -1179,7 +1207,8 @@
                 OutputSurfaceConfigurationImplStub outputSurfaceConfigs =
                         new OutputSurfaceConfigurationImplStub(outputPreviewSurfaceImpl,
                         // Image Analysis Output is currently only supported in CameraX
-                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/);
+                        outputImageCaptureSurfaceImpl, null /*imageAnalysisSurfaceConfig*/,
+                        outputPostviewSurfaceImpl);
 
                 sessionConfig = mSessionProcessor.initSession(cameraId,
                         mCharacteristicsHashMap, getApplicationContext(), outputSurfaceConfigs);
@@ -1264,7 +1293,14 @@
         }
 
         @Override
-        public int startCapture(ICaptureCallback callback) {
+        public int startCapture(ICaptureCallback callback, boolean isPostviewRequested) {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                return isPostviewRequested ? mSessionProcessor.startCaptureWithPostview(
+                        new CaptureCallbackStub(callback, mCameraId)) :
+                        mSessionProcessor.startCapture(new CaptureCallbackStub(callback,
+                        mCameraId));
+            }
+
             return mSessionProcessor.startCapture(new CaptureCallbackStub(callback, mCameraId));
         }
 
@@ -1288,12 +1324,15 @@
         private OutputSurfaceImpl mOutputPreviewSurfaceImpl;
         private OutputSurfaceImpl mOutputImageCaptureSurfaceImpl;
         private OutputSurfaceImpl mOutputImageAnalysisSurfaceImpl;
+        private OutputSurfaceImpl mOutputPostviewSurfaceImpl;
 
         public OutputSurfaceConfigurationImplStub(OutputSurfaceImpl previewOutput,
-                OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput) {
+                OutputSurfaceImpl imageCaptureOutput, OutputSurfaceImpl imageAnalysisOutput,
+                OutputSurfaceImpl postviewOutput) {
             mOutputPreviewSurfaceImpl = previewOutput;
             mOutputImageCaptureSurfaceImpl = imageCaptureOutput;
             mOutputImageAnalysisSurfaceImpl = imageAnalysisOutput;
+            mOutputPostviewSurfaceImpl = postviewOutput;
         }
 
         @Override
@@ -1310,6 +1349,11 @@
         public OutputSurfaceImpl getImageAnalysisOutputSurface() {
             return mOutputImageAnalysisSurfaceImpl;
         }
+
+        @Override
+        public OutputSurfaceImpl getPostviewOutputSurface() {
+            return mOutputPostviewSurfaceImpl;
+        }
     }
 
     private class OutputSurfaceImplStub implements OutputSurfaceImpl {
@@ -1498,6 +1542,15 @@
         }
 
         @Override
+        public boolean isPostviewAvailable() {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                return mImageExtender.isPostviewAvailable();
+            }
+
+            return false;
+        }
+
+        @Override
         public CaptureStageImpl onEnableSession() {
             return initializeParcelable(mImageExtender.onEnableSession(), mCameraId);
         }
@@ -1577,6 +1630,21 @@
         }
 
         @Override
+        public List<SizeList> getSupportedPostviewResolutions(
+                android.hardware.camera2.extension.Size captureSize) {
+            if (LATENCY_IMPROVEMENTS_SUPPORTED) {
+                Size sz = new Size(captureSize.width, captureSize.height);
+                List<Pair<Integer, android.util.Size[]>> sizes =
+                        mImageExtender.getSupportedPostviewResolutions(sz);
+                if ((sizes != null) && !sizes.isEmpty()) {
+                    return initializeParcelable(sizes);
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public LatencyRange getEstimatedCaptureLatencyRange(
                 android.hardware.camera2.extension.Size outputSize) {
             if (LATENCY_API_SUPPORTED) {
@@ -1715,8 +1783,21 @@
         }
 
         @Override
-        public void onResolutionUpdate(android.hardware.camera2.extension.Size size) {
-            mCaptureProcessor.onResolutionUpdate(new android.util.Size(size.width, size.height));
+        public void onPostviewOutputSurface(Surface surface) {
+            mCaptureProcessor.onPostviewOutputSurface(surface);
+        }
+
+        @Override
+        public void onResolutionUpdate(android.hardware.camera2.extension.Size size,
+                android.hardware.camera2.extension.Size postviewSize) {
+            if (postviewSize != null) {
+                mCaptureProcessor.onResolutionUpdate(
+                        new android.util.Size(size.width, size.height),
+                        new android.util.Size(postviewSize.width, postviewSize.height));
+            } else {
+                mCaptureProcessor.onResolutionUpdate(
+                        new android.util.Size(size.width, size.height));
+            }
         }
 
         @Override
@@ -1725,7 +1806,8 @@
         }
 
         @Override
-        public void process(List<CaptureBundle> captureList, IProcessResultImpl resultCallback) {
+        public void process(List<CaptureBundle> captureList, IProcessResultImpl resultCallback,
+                boolean isPostviewRequested) {
             HashMap<Integer, Pair<Image, TotalCaptureResult>> captureMap = new HashMap<>();
             for (CaptureBundle captureBundle : captureList) {
                 captureMap.put(captureBundle.stage, new Pair<> (
@@ -1734,7 +1816,12 @@
                                 captureBundle.sequenceId)));
             }
             if (!captureMap.isEmpty()) {
-                if ((resultCallback != null) && (RESULT_API_SUPPORTED)) {
+                if ((LATENCY_IMPROVEMENTS_SUPPORTED) && (isPostviewRequested)) {
+                    ProcessResultCallback processResultCallback = (resultCallback != null)
+                            ? new ProcessResultCallback(resultCallback, mCameraId) : null;
+                    mCaptureProcessor.processWithPostview(captureMap, processResultCallback,
+                            null /*executor*/);
+                } else if ((resultCallback != null) && (RESULT_API_SUPPORTED)) {
                     mCaptureProcessor.process(captureMap, new ProcessResultCallback(resultCallback,
                                     mCameraId), null /*executor*/);
                 } else if (resultCallback == null) {
diff --git a/proto/src/windowmanager.proto b/proto/src/windowmanager.proto
index f26404c6..da4dfa9 100644
--- a/proto/src/windowmanager.proto
+++ b/proto/src/windowmanager.proto
@@ -68,4 +68,8 @@
   LetterboxHorizontalReachability letterbox_position_for_horizontal_reachability = 1;
   // Represents the current vertical position for the letterboxed activity
   LetterboxVerticalReachability letterbox_position_for_vertical_reachability = 2;
+  // Represents the current horizontal position for the letterboxed activity in book mode
+  LetterboxHorizontalReachability letterbox_position_for_book_mode_reachability = 3;
+  // Represents the current vertical position for the letterboxed activity in tabletop mode
+  LetterboxVerticalReachability letterbox_position_for_tabletop_mode_reachability = 4;
 }
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index f0b5959..05e305c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -42,6 +42,7 @@
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.MagnificationConfig;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.PendingIntent;
@@ -107,6 +108,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -129,6 +132,11 @@
     private static final String TRACE_WM = "WindowManagerInternal";
     private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
 
+    /** Display type for displays associated with the default user of th device. */
+    public static final int DISPLAY_TYPE_DEFAULT = 1 << 0;
+    /** Display type for displays associated with an AccessibilityDisplayProxy user. */
+    public static final int DISPLAY_TYPE_PROXY = 1 << 1;
+
     protected static final String TAKE_SCREENSHOT = "takeScreenshot";
     protected final Context mContext;
     protected final SystemSupport mSystemSupport;
@@ -157,6 +165,8 @@
     // The attribution tag set by the service that is bound to this instance
     protected String mAttributionTag;
 
+    protected int mDisplayTypes = DISPLAY_TYPE_DEFAULT;
+
     // The service that's bound to this instance. Whenever this value is non-null, this
     // object is registered as a death recipient
     IBinder mService;
@@ -224,6 +234,14 @@
      */
     private SparseArray<Long> mRequestTakeScreenshotOfWindowTimestampMs = new SparseArray<>();
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "DISPLAY_TYPE_" }, value = {
+            DISPLAY_TYPE_DEFAULT,
+            DISPLAY_TYPE_PROXY
+    })
+    public @interface DisplayTypes {}
+
     public interface SystemSupport {
         /**
          * @return The current dispatcher for key events
@@ -520,7 +538,8 @@
             }
             final AccessibilityWindowInfo.WindowListSparseArray allWindows =
                     new AccessibilityWindowInfo.WindowListSparseArray();
-            final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked();
+            final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
+                    mDisplayTypes);
             final int displayListCounts = displayList.size();
             if (displayListCounts > 0) {
                 for (int i = 0; i < displayListCounts; i++) {
@@ -538,6 +557,10 @@
         }
     }
 
+    protected void setDisplayTypes(@DisplayTypes int displayTypes) {
+        mDisplayTypes = displayTypes;
+    }
+
     @Override
     public AccessibilityWindowInfo getWindow(int windowId) {
         if (svcConnTracingEnabled()) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 59c1c54..1183d6b 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -457,7 +457,7 @@
                 new MagnificationScaleProvider(mContext));
         mMagnificationProcessor = new MagnificationProcessor(mMagnificationController);
         mCaptioningManagerImpl = new CaptioningManagerImpl(mContext);
-        mProxyManager = new ProxyManager(mLock);
+        mProxyManager = new ProxyManager(mLock, mA11yWindowManager);
         init();
     }
 
@@ -1760,6 +1760,8 @@
             AccessibilityServiceConnection service = state.mBoundServices.get(i);
             service.notifyClearAccessibilityNodeInfoCache();
         }
+
+        mProxyManager.clearCacheLocked();
     }
 
     private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
@@ -3714,6 +3716,10 @@
         mProxyManager.registerProxy(client, displayId, mContext,
                 sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(),
                 mWindowManagerService, mA11yWindowManager);
+
+        synchronized (mLock) {
+            notifyClearAccessibilityCacheLocked();
+        }
         return true;
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index 7c68c8a..8af5e11 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -21,6 +21,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
+import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_PROXY;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -169,6 +171,7 @@
         private List<AccessibilityWindowInfo> mWindows;
         private boolean mTrackingWindows = false;
         private boolean mHasWatchOutsideTouchWindow;
+        private boolean mIsProxy;
 
         /**
          * Constructor for DisplayWindowsObserver.
@@ -1016,6 +1019,33 @@
     }
 
     /**
+     * Starts tracking a display as belonging to a proxy. Creates the window observer if necessary.
+     * @param displayId
+     */
+    public void startTrackingDisplayProxy(int displayId) {
+        startTrackingWindows(displayId);
+        synchronized (mLock) {
+            DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+            if (observer != null) {
+                observer.mIsProxy = true;
+            }
+        }
+    }
+
+    /**
+     * Stops tracking a display as belonging to a proxy.
+     * @param displayId
+     */
+    public void stopTrackingDisplayProxy(int displayId) {
+        synchronized (mLock) {
+            DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
+            if (observer != null) {
+                observer.mIsProxy = false;
+            }
+        }
+    }
+
+    /**
      * Checks if we are tracking windows on any display.
      *
      * @return {@code true} if the observer is tracking windows on any display,
@@ -1728,15 +1758,21 @@
     /**
      * Returns the display list including all displays which are tracking windows.
      *
+     * @param displayTypes the types of displays to retrieve
      * @return The display list.
      */
-    public ArrayList<Integer> getDisplayListLocked() {
+    public ArrayList<Integer> getDisplayListLocked(
+            @AbstractAccessibilityServiceConnection.DisplayTypes int displayTypes) {
         final ArrayList<Integer> displayList = new ArrayList<>();
         final int count = mDisplayWindowsObservers.size();
         for (int i = 0; i < count; i++) {
             final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
             if (observer != null) {
-                displayList.add(observer.mDisplayId);
+                if (!observer.mIsProxy && (displayTypes & DISPLAY_TYPE_DEFAULT) != 0) {
+                    displayList.add(observer.mDisplayId);
+                } else if (observer.mIsProxy && (displayTypes & DISPLAY_TYPE_PROXY) != 0) {
+                    displayList.add(observer.mDisplayId);
+                }
             }
         }
         return displayList;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index d7f9c12..d53a080 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -39,12 +39,14 @@
 import android.view.KeyEvent;
 import android.view.accessibility.AccessibilityDisplayProxy;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
 
 import androidx.annotation.Nullable;
 
 import com.android.server.wm.WindowManagerInternal;
 
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -74,6 +76,7 @@
                 mainHandler, lock, securityPolicy, systemSupport, trace, windowManagerInternal,
                 /* systemActionPerformer= */ null, awm, /* activityTaskManagerService= */ null);
         mDisplayId = displayId;
+        setDisplayTypes(DISPLAY_TYPE_PROXY);
     }
 
     /**
@@ -189,6 +192,17 @@
     }
 
     @Override
+    public AccessibilityWindowInfo.WindowListSparseArray getWindows() {
+        final AccessibilityWindowInfo.WindowListSparseArray allWindows = super.getWindows();
+        AccessibilityWindowInfo.WindowListSparseArray displayWindows = new
+                AccessibilityWindowInfo.WindowListSparseArray();
+        // Filter here so A11yInteractionClient will not cache all the windows belonging to other
+        // proxy connections.
+        displayWindows.put(mDisplayId, allWindows.get(mDisplayId, Collections.emptyList()));
+        return displayWindows;
+    }
+
+    @Override
     public void binderDied() {
     }
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index f28191f..fcbdc4e 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -55,8 +55,11 @@
     private SparseArray<ProxyAccessibilityServiceConnection> mProxyA11yServiceConnections =
             new SparseArray<>();
 
-    ProxyManager(Object lock) {
+    private AccessibilityWindowManager mA11yWindowManager;
+
+    ProxyManager(Object lock, AccessibilityWindowManager awm) {
         mLock = lock;
+        mA11yWindowManager = awm;
     }
 
     /**
@@ -99,6 +102,8 @@
                     }
                 };
         client.asBinder().linkToDeath(deathRecipient, 0);
+
+        mA11yWindowManager.startTrackingDisplayProxy(displayId);
         // Notify apps that the service state has changed.
         // A11yManager#A11yServicesStateChangeListener
         synchronized (mLock) {
@@ -122,6 +127,7 @@
                 return true;
             }
         }
+        mA11yWindowManager.stopTrackingDisplayProxy(displayId);
         return false;
     }
 
@@ -229,4 +235,15 @@
             }
         }
     }
+
+    /**
+     * Clears all proxy caches.
+     */
+    public void clearCacheLocked() {
+        for (int i = 0; i < mProxyA11yServiceConnections.size(); i++) {
+            final ProxyAccessibilityServiceConnection proxy =
+                    mProxyA11yServiceConnections.valueAt(i);
+            proxy.notifyClearAccessibilityNodeInfoCache();
+        }
+    }
 }
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 6cc2214..2188b99 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -253,6 +253,7 @@
                     securityPolicy, systemSupport, trace, windowManagerInternal,
                     systemActionPerformer, awm);
             mMainHandler = mainHandler;
+            setDisplayTypes(DISPLAY_TYPE_DEFAULT | DISPLAY_TYPE_PROXY);
         }
 
         void connectServiceUnknownThread() {
diff --git a/services/api/current.txt b/services/api/current.txt
index da5b1fc..090a449 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -72,16 +72,90 @@
 package com.android.server.pm.pkg {
 
   public interface AndroidPackage {
+    method @Nullable public String getAppComponentFactory();
+    method @Nullable public String getApplicationClassName();
+    method @Nullable public String getBackupAgentName();
+    method @DrawableRes public int getBannerRes();
+    method public int getBaseRevisionCode();
+    method public int getCategory();
+    method @Nullable public String getClassLoaderName();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getCompatibleWidthLimitDp();
+    method @XmlRes public int getDataExtractionRulesRes();
+    method @StringRes public int getDescriptionRes();
+    method @XmlRes public int getFullBackupContentRes();
+    method public int getGwpAsanMode();
+    method @DrawableRes public int getIconRes();
+    method @StringRes public int getLabelRes();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getLargestWidthLimitDp();
     method @NonNull public java.util.List<java.lang.String> getLibraryNames();
+    method @XmlRes public int getLocaleConfigRes();
+    method @DrawableRes public int getLogoRes();
+    method public long getLongVersionCode();
+    method public float getMaxAspectRatio();
+    method public float getMinAspectRatio();
+    method public int getNativeHeapZeroInitialized();
+    method @XmlRes public int getNetworkSecurityConfigRes();
+    method @Nullable public String getRequiredAccountType();
+    method @Dimension(unit=android.annotation.Dimension.DP) public int getRequiresSmallestWidthDp();
+    method @Nullable public String getRestrictedAccountType();
+    method @DrawableRes public int getRoundIconRes();
     method @Nullable public String getSdkLibraryName();
+    method @Nullable public String getSharedUserId();
+    method @StringRes public int getSharedUserLabelRes();
     method @NonNull public java.util.List<com.android.server.pm.pkg.AndroidPackageSplit> getSplits();
     method @Nullable public String getStaticSharedLibraryName();
     method @NonNull public java.util.UUID getStorageUuid();
     method public int getTargetSdkVersion();
+    method @StyleRes public int getThemeRes();
+    method public int getUiOptions();
+    method @Nullable public String getVersionName();
+    method @Nullable public String getZygotePreloadName();
+    method public boolean isAllowAudioPlaybackCapture();
+    method public boolean isAllowBackup();
+    method public boolean isAllowClearUserData();
+    method public boolean isAllowClearUserDataOnFailedRestore();
+    method public boolean isAllowNativeHeapPointerTagging();
+    method public boolean isAllowTaskReparenting();
+    method public boolean isAnyDensity();
+    method public boolean isAttributionsUserVisible();
+    method public boolean isBackupInForeground();
+    method public boolean isCantSaveState();
+    method public boolean isCoreApp();
+    method public boolean isCrossProfile();
     method public boolean isDebuggable();
+    method public boolean isDefaultToDeviceProtectedStorage();
+    method public boolean isDirectBootAware();
+    method public boolean isExtractNativeLibs();
+    method public boolean isFactoryTest();
+    method public boolean isForceQueryable();
+    method public boolean isFullBackupOnly();
+    method public boolean isHardwareAccelerated();
+    method public boolean isHasCode();
+    method public boolean isHasFragileUserData();
     method public boolean isIsolatedSplitLoading();
+    method public boolean isKillAfterRestore();
+    method public boolean isLargeHeap();
+    method public boolean isLeavingSharedUser();
+    method public boolean isMultiArch();
+    method public boolean isNativeLibraryRootRequiresIsa();
+    method public boolean isOnBackInvokedCallbackEnabled();
+    method public boolean isPersistent();
+    method public boolean isProfileable();
+    method public boolean isProfileableByShell();
+    method public boolean isRequestLegacyExternalStorage();
+    method public boolean isRequiredForAllUsers();
+    method public boolean isResetEnabledSettingsOnAppDataCleared();
+    method public boolean isRestoreAnyVersion();
     method public boolean isSignedWithPlatformKey();
+    method public boolean isSupportsExtraLargeScreens();
+    method public boolean isSupportsLargeScreens();
+    method public boolean isSupportsNormalScreens();
+    method public boolean isSupportsRtl();
+    method public boolean isSupportsSmallScreens();
+    method public boolean isTestOnly();
+    method public boolean isUse32BitAbi();
     method public boolean isUseEmbeddedDex();
+    method public boolean isUsesCleartextTraffic();
     method public boolean isUsesNonSdkApi();
     method public boolean isVmSafeMode();
   }
@@ -169,3 +243,52 @@
 
 }
 
+package com.android.server.wm {
+
+  public interface ActivityInterceptorCallback {
+    method public default void onActivityLaunched(@NonNull android.app.TaskInfo, @NonNull android.content.pm.ActivityInfo, @NonNull com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo);
+    method @Nullable public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult onInterceptActivityLaunch(@NonNull com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo);
+    field public static final int MAINLINE_SDK_SANDBOX_ORDER_ID = 1001; // 0x3e9
+  }
+
+  public static final class ActivityInterceptorCallback.ActivityInterceptResult {
+    ctor public ActivityInterceptorCallback.ActivityInterceptResult(@NonNull android.content.Intent, @NonNull android.app.ActivityOptions, boolean);
+    method @NonNull public android.app.ActivityOptions getActivityOptions();
+    method @NonNull public android.content.Intent getIntent();
+    method public boolean isActivityResolved();
+  }
+
+  public static final class ActivityInterceptorCallback.ActivityInterceptorInfo {
+    method @NonNull public android.content.pm.ActivityInfo getActivityInfo();
+    method @Nullable public String getCallingFeatureId();
+    method @Nullable public String getCallingPackage();
+    method public int getCallingPid();
+    method public int getCallingUid();
+    method @Nullable public android.app.ActivityOptions getCheckedOptions();
+    method @Nullable public Runnable getClearOptionsAnimationRunnable();
+    method @NonNull public android.content.Intent getIntent();
+    method public int getRealCallingPid();
+    method public int getRealCallingUid();
+    method @NonNull public android.content.pm.ResolveInfo getResolveInfo();
+    method @Nullable public String getResolvedType();
+    method public int getUserId();
+  }
+
+  public static final class ActivityInterceptorCallback.ActivityInterceptorInfo.Builder {
+    ctor public ActivityInterceptorCallback.ActivityInterceptorInfo.Builder(int, int, int, int, int, @NonNull android.content.Intent, @NonNull android.content.pm.ResolveInfo, @NonNull android.content.pm.ActivityInfo);
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo build();
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingFeatureId(@NonNull String);
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingPackage(@NonNull String);
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCheckedOptions(@NonNull android.app.ActivityOptions);
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setClearOptionsAnimationRunnable(@NonNull Runnable);
+    method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setResolvedType(@NonNull String);
+  }
+
+  public class ActivityInterceptorCallbackRegistry {
+    method @NonNull public static com.android.server.wm.ActivityInterceptorCallbackRegistry getInstance();
+    method public void registerActivityInterceptorCallback(int, @NonNull com.android.server.wm.ActivityInterceptorCallback);
+    method public void unregisterActivityInterceptorCallback(int);
+  }
+
+}
+
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 1c571a7..53f5fe1 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -25,6 +25,8 @@
 import android.app.ActivityManager;
 import android.app.admin.DevicePolicyManager;
 import android.app.backup.BackupManager;
+import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
@@ -1556,6 +1558,22 @@
         }
     }
 
+    public void reportDelayedRestoreResult(String packageName, List<DataTypeResult> results) {
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!isUserReadyForBackup(userId)) {
+            Slog.w(TAG, "Returning from reportDelayedRestoreResult as backup for user" + userId +
+                    " is not initialized yet");
+            return;
+        }
+        UserBackupManagerService userBackupManagerService =
+                getServiceForUserIfCallerHasPermission(userId,
+                        /* caller */ "reportDelayedRestoreResult()");
+
+        if (userBackupManagerService != null) {
+            userBackupManagerService.reportDelayedRestoreResult(packageName, results);
+        }
+    }
+
     /**
      * Returns the {@link UserBackupManagerService} instance for the specified user {@code userId}.
      * If the user is not registered with the service (either the user is locked or not eligible for
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ce3e628..6ba01d7 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -49,6 +49,7 @@
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
+import android.app.backup.BackupRestoreEventLogger;
 import android.app.backup.FullBackup;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupManagerMonitor;
@@ -505,13 +506,14 @@
 
     @VisibleForTesting
     UserBackupManagerService(Context context, PackageManager packageManager,
-            LifecycleOperationStorage operationStorage) {
+            LifecycleOperationStorage operationStorage, TransportManager transportManager) {
         mContext = context;
 
         mUserId = 0;
         mRegisterTransportsRequestedTime = 0;
         mPackageManager = packageManager;
         mOperationStorage = operationStorage;
+        mTransportManager = transportManager;
 
         mBaseStateDir = null;
         mDataDir = null;
@@ -521,7 +523,6 @@
         mRunInitReceiver = null;
         mRunInitIntent = null;
         mAgentTimeoutParameters = null;
-        mTransportManager = null;
         mActivityManagerInternal = null;
         mAlarmManager = null;
         mConstants = null;
@@ -3038,6 +3039,37 @@
         mBackupPreferences.addExcludedKeys(packageName, keys);
     }
 
+    public void reportDelayedRestoreResult(String packageName,
+            List<BackupRestoreEventLogger.DataTypeResult> results) {
+        String transport = mTransportManager.getCurrentTransportName();
+        if (transport == null) {
+            Slog.w(TAG, "Failed to send delayed restore logs as no transport selected");
+            return;
+        }
+
+        TransportConnection transportConnection = null;
+        try {
+            PackageInfo packageInfo = getPackageManager().getPackageInfoAsUser(packageName,
+                    PackageManager.PackageInfoFlags.of(/* value */ 0), getUserId());
+
+            transportConnection = mTransportManager.getTransportClientOrThrow(
+                    transport, /* caller */"BMS.reportDelayedRestoreResult");
+            BackupTransportClient transportClient = transportConnection.connectOrThrow(
+                    /* caller */ "BMS.reportDelayedRestoreResult");
+
+            IBackupManagerMonitor monitor = transportClient.getBackupManagerMonitor();
+            BackupManagerMonitorUtils.sendAgentLoggingResults(monitor, packageInfo, results);
+        } catch (NameNotFoundException | TransportNotAvailableException
+                | TransportNotRegisteredException | RemoteException e) {
+            Slog.w(TAG, "Failed to send delayed restore logs: " + e);
+        } finally {
+            if (transportConnection != null) {
+                mTransportManager.disposeOfTransportClient(transportConnection,
+                        /* caller */"BMS.reportDelayedRestoreResult");
+            }
+        }
+    }
+
     private boolean startConfirmationUi(int token, String action) {
         try {
             Intent confIntent = new Intent(action);
diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
index 8eda5b9..57ad89b 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorUtils.java
@@ -24,10 +24,11 @@
 import static com.android.server.backup.BackupManagerService.DEBUG;
 import static com.android.server.backup.BackupManagerService.TAG;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.backup.BackupManagerMonitor;
-import android.app.backup.BackupRestoreEventLogger;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.content.pm.PackageInfo;
 import android.os.Bundle;
@@ -119,19 +120,11 @@
         }
 
         try {
-            AndroidFuture<List<BackupRestoreEventLogger.DataTypeResult>> resultsFuture =
+            AndroidFuture<List<DataTypeResult>> resultsFuture =
                     new AndroidFuture<>();
             agent.getLoggerResults(resultsFuture);
-            Bundle loggerResultsBundle = new Bundle();
-            loggerResultsBundle.putParcelableList(
-                    EXTRA_LOG_AGENT_LOGGING_RESULTS,
+            return sendAgentLoggingResults(monitor, pkg,
                     resultsFuture.get(AGENT_LOGGER_RESULTS_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS));
-            return BackupManagerMonitorUtils.monitorEvent(
-                    monitor,
-                    LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
-                    pkg,
-                    LOG_EVENT_CATEGORY_AGENT,
-                    loggerResultsBundle);
         } catch (TimeoutException e) {
             Slog.w(TAG, "Timeout while waiting to retrieve logging results from agent", e);
         } catch (Exception e) {
@@ -140,6 +133,19 @@
         return monitor;
     }
 
+    public static IBackupManagerMonitor sendAgentLoggingResults(
+            @NonNull IBackupManagerMonitor monitor, PackageInfo pkg, List<DataTypeResult> results) {
+        Bundle loggerResultsBundle = new Bundle();
+        loggerResultsBundle.putParcelableList(
+                EXTRA_LOG_AGENT_LOGGING_RESULTS, results);
+        return monitorEvent(
+                monitor,
+                LOG_EVENT_ID_AGENT_LOGGING_RESULTS,
+                pkg,
+                LOG_EVENT_CATEGORY_AGENT,
+                loggerResultsBundle);
+    }
+
     /**
      * Adds given key-value pair in the bundle and returns the bundle. If bundle was null it will
      * be created.
diff --git a/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
new file mode 100644
index 0000000..7ea1e6c
--- /dev/null
+++ b/services/companion/java/com/android/server/companion/datatransfer/contextsync/CallMetadataSyncInCallService.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.companion.datatransfer.contextsync;
+
+import android.telecom.Call;
+import android.telecom.InCallService;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** In-call service to sync call metadata across a user's devices. */
+public class CallMetadataSyncInCallService extends InCallService {
+
+    @VisibleForTesting
+    final Set<CrossDeviceCall> mCurrentCalls = new HashSet<>();
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mCurrentCalls.addAll(getCalls().stream().map(CrossDeviceCall::new).toList());
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        onCallAdded(new CrossDeviceCall(call));
+    }
+
+    @VisibleForTesting
+    void onCallAdded(CrossDeviceCall call) {
+        mCurrentCalls.add(call);
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        mCurrentCalls.removeIf(crossDeviceCall -> crossDeviceCall.getCall().equals(call));
+    }
+
+    /** Data holder for a telecom call and additional metadata. */
+    public static final class CrossDeviceCall {
+        private static final AtomicLong sNextId = new AtomicLong(1);
+
+        private final Call mCall;
+        private final long mId;
+
+        public CrossDeviceCall(Call call) {
+            mCall = call;
+            mId = sNextId.getAndIncrement();
+        }
+
+        public Call getCall() {
+            return mCall;
+        }
+
+        public long getId() {
+            return mId;
+        }
+    }
+}
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 195fee1..be80e01 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -22,9 +22,9 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
 
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -60,7 +60,9 @@
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Looper;
+import android.os.PermissionEnforcer;
 import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -123,6 +125,9 @@
     // The default setting for showing the pointer on new displays.
     @GuardedBy("mVirtualDeviceLock")
     private boolean mDefaultShowPointerIcon = true;
+    @GuardedBy("mVirtualDeviceLock")
+    @Nullable
+    private LocaleList mLocaleList = null;
 
     private ActivityListener createListenerAdapter() {
         return new ActivityListener() {
@@ -194,6 +199,7 @@
             IVirtualDeviceActivityListener activityListener,
             Consumer<ArraySet<Integer>> runningAppsChangedCallback,
             VirtualDeviceParams params) {
+        super(PermissionEnforcer.fromContext(context));
         UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(ownerUid);
         mContext = context.createContextAsUser(ownerUserHandle, 0);
         mAssociationInfo = associationInfo;
@@ -247,6 +253,13 @@
         return mParams.getName();
     }
 
+    /** Returns the locale of the device. */
+    LocaleList getDeviceLocaleList() {
+        synchronized (mVirtualDeviceLock) {
+            return mLocaleList;
+        }
+    }
+
     /** Returns the policy specified for this policy type */
     public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
             @VirtualDeviceParams.PolicyType int policyType) {
@@ -326,11 +339,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void close() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to close the virtual device");
-
+        super.close_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mPerDisplayWakelocks.isEmpty()) {
                 mPerDisplayWakelocks.forEach((displayId, wakeLock) -> {
@@ -344,6 +355,7 @@
                 mVirtualAudioController.stopListening();
                 mVirtualAudioController = null;
             }
+            mLocaleList = null;
         }
         mOnDeviceCloseListener.onClose(mDeviceId);
         mAppToken.unlinkToDeath(this, 0);
@@ -377,14 +389,12 @@
         return mWindowPolicyControllers;
     }
 
-    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionStarting(int displayId,
             @NonNull IAudioRoutingCallback routingCallback,
             @Nullable IAudioConfigChangedCallback configChangedCallback) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to start audio session");
+        super.onAudioSessionStarting_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(displayId)) {
                 throw new SecurityException(
@@ -401,12 +411,10 @@
         }
     }
 
-    @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void onAudioSessionEnded() {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to stop audio session");
+        super.onAudioSessionEnded_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (mVirtualAudioController != null) {
                 mVirtualAudioController.stopListening();
@@ -416,9 +424,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualDpad(VirtualDpadConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual dpad");
+        super.createVirtualDpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -436,15 +444,16 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualKeyboard(VirtualKeyboardConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual keyboard");
+        super.createVirtualKeyboard_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
                         "Cannot create a virtual keyboard for a display not associated with "
                                 + "this virtual device");
             }
+            mLocaleList = LocaleList.forLanguageTags(config.getLanguageTag());
         }
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -457,9 +466,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualMouse(VirtualMouseConfig config, @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual mouse");
+        super.createVirtualMouse_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -477,10 +486,10 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualTouchscreen(VirtualTouchscreenConfig config,
             @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual touchscreen");
+        super.createVirtualTouchscreen_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -507,11 +516,10 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualNavigationTouchpad(VirtualNavigationTouchpadConfig config,
             @NonNull IBinder deviceToken) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual navigation touchpad");
+        super.createVirtualNavigationTouchpad_enforcePermission();
         synchronized (mVirtualDeviceLock) {
             if (!mVirtualDisplayIds.contains(config.getAssociatedDisplayId())) {
                 throw new SecurityException(
@@ -539,11 +547,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterInputDevice(IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister this input device");
-
+        super.unregisterInputDevice_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             mInputController.unregisterInputDevice(token);
@@ -564,7 +570,9 @@
 
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendDpadKeyEvent(IBinder token, VirtualKeyEvent event) {
+        super.sendDpadKeyEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendDpadKeyEvent(token, event);
@@ -574,7 +582,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendKeyEvent(IBinder token, VirtualKeyEvent event) {
+        super.sendKeyEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendKeyEvent(token, event);
@@ -584,7 +594,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendButtonEvent(IBinder token, VirtualMouseButtonEvent event) {
+        super.sendButtonEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendButtonEvent(token, event);
@@ -594,7 +606,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendTouchEvent(IBinder token, VirtualTouchEvent event) {
+        super.sendTouchEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendTouchEvent(token, event);
@@ -604,7 +618,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendRelativeEvent(IBinder token, VirtualMouseRelativeEvent event) {
+        super.sendRelativeEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendRelativeEvent(token, event);
@@ -614,7 +630,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendScrollEvent(IBinder token, VirtualMouseScrollEvent event) {
+        super.sendScrollEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mInputController.sendScrollEvent(token, event);
@@ -634,11 +652,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void setShowPointerIcon(boolean showPointerIcon) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister this input device");
-
+        super.setShowPointerIcon_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mVirtualDeviceLock) {
@@ -653,12 +669,11 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void createVirtualSensor(
             @NonNull IBinder deviceToken,
             @NonNull VirtualSensorConfig config) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to create a virtual sensor");
+        super.createVirtualSensor_enforcePermission();
         Objects.requireNonNull(config);
         Objects.requireNonNull(deviceToken);
         final long ident = Binder.clearCallingIdentity();
@@ -670,10 +685,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterSensor(@NonNull IBinder token) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister a virtual sensor");
+        super.unregisterSensor_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             mSensorController.unregisterSensor(token);
@@ -683,10 +697,9 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public boolean sendSensorEvent(@NonNull IBinder token, @NonNull VirtualSensorEvent event) {
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to send a virtual sensor event");
+        super.sendSensorEvent_enforcePermission();
         final long ident = Binder.clearCallingIdentity();
         try {
             return mSensorController.sendSensorEvent(token, event);
@@ -696,25 +709,23 @@
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void registerIntentInterceptor(IVirtualDeviceIntentInterceptor intentInterceptor,
             IntentFilter filter) {
+        super.registerIntentInterceptor_enforcePermission();
         Objects.requireNonNull(intentInterceptor);
         Objects.requireNonNull(filter);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to register intent interceptor");
         synchronized (mVirtualDeviceLock) {
             mIntentInterceptors.put(intentInterceptor.asBinder(), filter);
         }
     }
 
     @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
     public void unregisterIntentInterceptor(
             @NonNull IVirtualDeviceIntentInterceptor intentInterceptor) {
+        super.unregisterIntentInterceptor_enforcePermission();
         Objects.requireNonNull(intentInterceptor);
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.CREATE_VIRTUAL_DEVICE,
-                "Permission required to unregister intent interceptor");
         synchronized (mVirtualDeviceLock) {
             mIntentInterceptors.remove(intentInterceptor.asBinder());
         }
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 d446417..d317298 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -42,6 +42,7 @@
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Process;
@@ -137,21 +138,22 @@
 
         @Nullable
         @Override
-        public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
-            if (info.callingPackage == null) {
+        public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                ActivityInterceptorInfo info) {
+            if (info.getCallingPackage() == null) {
                 return null;
             }
-            PendingTrampoline pt = mPendingTrampolines.remove(info.callingPackage);
+            PendingTrampoline pt = mPendingTrampolines.remove(info.getCallingPackage());
             if (pt == null) {
                 return null;
             }
             pt.mResultReceiver.send(VirtualDeviceManager.LAUNCH_SUCCESS, null);
-            ActivityOptions options = info.checkedOptions;
+            ActivityOptions options = info.getCheckedOptions();
             if (options == null) {
                 options = ActivityOptions.makeBasic();
             }
             return new ActivityInterceptResult(
-                    info.intent, options.setLaunchDisplayId(pt.mDisplayId));
+                    info.getIntent(), options.setLaunchDisplayId(pt.mDisplayId));
         }
     };
 
@@ -591,6 +593,21 @@
         }
 
         @Override
+        @Nullable
+        public LocaleList getPreferredLocaleListForUid(int uid) {
+            // TODO: b/263188984 support the case where an app is running on multiple VDs
+            synchronized (mVirtualDeviceManagerLock) {
+                for (int i = 0; i < mAppsOnVirtualDevices.size(); i++) {
+                    if (mAppsOnVirtualDevices.valueAt(i).contains(uid)) {
+                        int deviceId = mAppsOnVirtualDevices.keyAt(i);
+                        return mVirtualDevices.get(deviceId).getDeviceLocaleList();
+                    }
+                }
+            }
+            return null;
+        }
+
+        @Override
         public boolean isAppRunningOnAnyVirtualDevice(int uid) {
             synchronized (mVirtualDeviceManagerLock) {
                 int size = mVirtualDevices.size();
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 9f3f761..5f5327d 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -582,7 +582,7 @@
     public abstract ResolveInfo resolveIntentExported(Intent intent, String resolvedType,
             @PackageManager.ResolveInfoFlagsBits long flags,
             @PrivateResolveFlags long privateResolveFlags, int userId, boolean resolveForStart,
-            int filterCallingUid);
+            int filterCallingUid, int callingPid);
 
     /**
     * Resolves a service intent, allowing instant apps to be resolved.
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 59db686..f149fda 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -45,6 +45,7 @@
 import android.util.Slog;
 
 import com.android.internal.telephony.IMms;
+import com.android.internal.telephony.TelephonyPermissions;
 import com.android.server.uri.NeededUriGrants;
 import com.android.server.uri.UriGrantsManagerInternal;
 
@@ -337,6 +338,14 @@
                 throws RemoteException {
             Slog.d(TAG, "sendMessage() by " + callingPkg);
             mContext.enforceCallingPermission(Manifest.permission.SEND_SMS, "Send MMS message");
+
+            // Check if user is associated with the subscription
+            if (!TelephonyPermissions.checkSubscriptionAssociatedWithUser(mContext, subId,
+                    Binder.getCallingUserHandle())) {
+                // TODO(b/258629881): Display error dialog.
+		return;
+            }
+
             if (getAppOpsManager().noteOp(AppOpsManager.OP_SEND_SMS, Binder.getCallingUid(),
                     callingPkg, attributionTag, null) != AppOpsManager.MODE_ALLOWED) {
                 Slog.e(TAG, callingPkg + " is not allowed to call sendMessage()");
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index b4ab254..d1e0f16 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -28,12 +28,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.RecoverySystem;
-import android.os.RemoteCallback;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -63,6 +61,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -188,9 +187,10 @@
     public static void onSettingsProviderPublished(Context context) {
         handleNativeRescuePartyResets();
         ContentResolver contentResolver = context.getContentResolver();
-        Settings.Config.registerMonitorCallback(contentResolver, new RemoteCallback(result -> {
-            handleMonitorCallback(context, result);
-        }));
+        DeviceConfig.setMonitorCallback(
+                contentResolver,
+                Executors.newSingleThreadExecutor(),
+                new RescuePartyMonitorCallback(context));
     }
 
 
@@ -278,27 +278,22 @@
         return SystemClock.elapsedRealtime();
     }
 
-    private static void handleMonitorCallback(Context context, Bundle result) {
-        String callbackType = result.getString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, "");
-        switch (callbackType) {
-            case Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK:
-                String updatedNamespace = result.getString(Settings.EXTRA_NAMESPACE);
-                if (updatedNamespace != null) {
-                    startObservingPackages(context, updatedNamespace);
-                }
-                break;
-            case Settings.EXTRA_ACCESS_CALLBACK:
-                String callingPackage = result.getString(Settings.EXTRA_CALLING_PACKAGE, null);
-                String namespace = result.getString(Settings.EXTRA_NAMESPACE, null);
-                if (namespace != null && callingPackage != null) {
-                    RescuePartyObserver.getInstance(context).recordDeviceConfigAccess(
+    private static class RescuePartyMonitorCallback implements DeviceConfig.MonitorCallback {
+        Context mContext;
+
+        RescuePartyMonitorCallback(Context context) {
+            this.mContext = context;
+        }
+
+        public void onNamespaceUpdate(@NonNull String updatedNamespace) {
+            startObservingPackages(mContext, updatedNamespace);
+        }
+
+        public void onDeviceConfigAccess(@NonNull String callingPackage,
+                @NonNull String namespace) {
+            RescuePartyObserver.getInstance(mContext).recordDeviceConfigAccess(
                             callingPackage,
                             namespace);
-                }
-                break;
-            default:
-                Slog.w(TAG, "Unrecognized DeviceConfig callback");
-                break;
         }
     }
 
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 9922818..7b8ca91 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -493,9 +493,7 @@
 
     private boolean isLocationPermissionRequired(Set<Integer> events) {
         return events.contains(TelephonyCallback.EVENT_CELL_LOCATION_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED)
-                || events.contains(TelephonyCallback.EVENT_REGISTRATION_FAILURE)
-                || events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED);
+                || events.contains(TelephonyCallback.EVENT_CELL_INFO_CHANGED);
     }
 
     private boolean isPhoneStatePermissionRequired(Set<Integer> events, String callingPackage,
@@ -1002,6 +1000,10 @@
     @Override
     public void notifySubscriptionInfoChanged() {
         if (VDBG) log("notifySubscriptionInfoChanged:");
+        if (!checkNotifyPermission("notifySubscriptionInfoChanged()")) {
+            return;
+        }
+
         synchronized (mRecords) {
             if (!mHasNotifySubscriptionInfoChangedOccurred) {
                 log("notifySubscriptionInfoChanged: first invocation mRecords.size="
@@ -1028,6 +1030,10 @@
     @Override
     public void notifyOpportunisticSubscriptionInfoChanged() {
         if (VDBG) log("notifyOpptSubscriptionInfoChanged:");
+        if (!checkNotifyPermission("notifyOpportunisticSubscriptionInfoChanged()")) {
+            return;
+        }
+
         synchronized (mRecords) {
             if (!mHasNotifyOpportunisticSubscriptionInfoChangedOccurred) {
                 log("notifyOpptSubscriptionInfoChanged: first invocation mRecords.size="
@@ -1359,15 +1365,19 @@
                 }
                 if (events.contains(TelephonyCallback.EVENT_BARRING_INFO_CHANGED)) {
                     BarringInfo barringInfo = mBarringInfo.get(r.phoneId);
-                    BarringInfo biNoLocation = barringInfo != null
-                            ? barringInfo.createLocationInfoSanitizedCopy() : null;
-                    if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
-                    try {
-                        r.callback.onBarringInfoChanged(
-                                checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
-                                        ? barringInfo : biNoLocation);
-                    } catch (RemoteException ex) {
-                        remove(r.binder);
+                    if (VDBG) {
+                        log("listen: call onBarringInfoChanged=" + barringInfo);
+                    }
+                    if (barringInfo != null) {
+                        BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+
+                        try {
+                            r.callback.onBarringInfoChanged(
+                                    checkFineLocationAccess(r, Build.VERSION_CODES.BASE)
+                                            ? barringInfo : biNoLocation);
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
                     }
                 }
                 if (events.contains(
@@ -3618,29 +3628,21 @@
 
     private boolean checkListenerPermission(Set<Integer> events, int subId, String callingPackage,
             @Nullable String callingFeatureId, String message) {
-        LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
-                new LocationAccessPolicy.LocationPermissionQuery.Builder()
-                        .setCallingPackage(callingPackage)
-                        .setCallingFeatureId(callingFeatureId)
-                        .setMethod(message + " events: " + events)
-                        .setCallingPid(Binder.getCallingPid())
-                        .setCallingUid(Binder.getCallingUid());
-
-        boolean shouldCheckLocationPermissions = false;
-
+        boolean isPermissionCheckSuccessful = true;
         if (isLocationPermissionRequired(events)) {
+            LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder =
+                    new LocationAccessPolicy.LocationPermissionQuery.Builder()
+                            .setCallingPackage(callingPackage)
+                            .setCallingFeatureId(callingFeatureId)
+                            .setMethod(message + " events: " + events)
+                            .setCallingPid(Binder.getCallingPid())
+                            .setCallingUid(Binder.getCallingUid());
             // Everything that requires fine location started in Q. So far...
             locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q);
             // If we're enforcing fine starting in Q, we also want to enforce coarse even for
             // older SDK versions.
             locationQueryBuilder.setMinSdkVersionForCoarse(0);
             locationQueryBuilder.setMinSdkVersionForEnforcement(0);
-            shouldCheckLocationPermissions = true;
-        }
-
-        boolean isPermissionCheckSuccessful = true;
-
-        if (shouldCheckLocationPermissions) {
             LocationAccessPolicy.LocationPermissionResult result =
                     LocationAccessPolicy.checkLocationPermission(
                             mContext, locationQueryBuilder.build());
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 87daa6d..4ebd714 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -704,6 +704,26 @@
         }
     }
 
+    static String getProcessNameForService(ServiceInfo sInfo, ComponentName name,
+            String callingPackage, String instanceName, boolean isSdkSandbox,
+            boolean inSharedIsolatedProcess) {
+        if (isSdkSandbox) {
+            // For SDK sandbox, the process name is passed in as the instanceName
+            return instanceName;
+        }
+        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+            // For regular processes, just the name in sInfo
+            return sInfo.processName;
+        }
+        // Isolated processes remain.
+        if (inSharedIsolatedProcess) {
+            // Shared isolated processes are scoped to the calling package
+            return callingPackage + ":ishared:" + instanceName;
+        } else {
+            return sInfo.processName + ":" + name.getClassName();
+        }
+    }
+
     ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType,
             int callingPid, int callingUid, boolean fgRequired, String callingPackage,
             @Nullable String callingFeatureId, final int userId)
@@ -736,7 +756,7 @@
 
         ServiceLookupResult res =
             retrieveServiceLocked(service, null, resolvedType, callingPackage,
-                    callingPid, callingUid, userId, true, callerFg, false, false);
+                    callingPid, callingUid, userId, true, callerFg, false, false, false);
         if (res == null) {
             return null;
         }
@@ -1338,7 +1358,8 @@
 
         // If this service is active, make sure it is stopped.
         ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, null,
-                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false);
+                Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false, false, false,
+                false);
         if (r != null) {
             if (r.record != null) {
                 final long origId = Binder.clearCallingIdentity();
@@ -1430,7 +1451,7 @@
     IBinder peekServiceLocked(Intent service, String resolvedType, String callingPackage) {
         ServiceLookupResult r = retrieveServiceLocked(service, null, resolvedType, callingPackage,
                 Binder.getCallingPid(), Binder.getCallingUid(),
-                UserHandle.getCallingUserId(), false, false, false, false);
+                UserHandle.getCallingUserId(), false, false, false, false, false);
 
         IBinder ret = null;
         if (r != null) {
@@ -3237,11 +3258,13 @@
                 != ProcessList.SCHED_GROUP_BACKGROUND;
         final boolean isBindExternal = (flags & Context.BIND_EXTERNAL_SERVICE) != 0;
         final boolean allowInstant = (flags & Context.BIND_ALLOW_INSTANT) != 0;
+        final boolean inSharedIsolatedProcess = (flags & Context.BIND_SHARED_ISOLATED_PROCESS) != 0;
 
         ServiceLookupResult res = retrieveServiceLocked(service, instanceName,
                 isSdkSandboxService, sdkSandboxClientAppUid, sdkSandboxClientAppPackage,
                 resolvedType, callingPackage, callingPid, callingUid, userId, true, callerFg,
-                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */,
+                inSharedIsolatedProcess);
         if (res == null) {
             return 0;
         }
@@ -3697,10 +3720,11 @@
             String instanceName, String resolvedType, String callingPackage,
             int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
-            boolean allowInstant) {
-        return retrieveServiceLocked(service, instanceName, false, 0, null, resolvedType,
+            boolean allowInstant, boolean inSharedIsolatedProcess) {
+        return retrieveServiceLocked(service, instanceName, false, INVALID_UID, null, resolvedType,
                 callingPackage, callingPid, callingUid, userId, createIfNeeded, callingFromFg,
-                isBindExternal, allowInstant, null /* fgsDelegateOptions */);
+                isBindExternal, allowInstant, null /* fgsDelegateOptions */,
+                inSharedIsolatedProcess);
     }
 
     private ServiceLookupResult retrieveServiceLocked(Intent service,
@@ -3708,7 +3732,8 @@
             String sdkSandboxClientAppPackage, String resolvedType,
             String callingPackage, int callingPid, int callingUid, int userId,
             boolean createIfNeeded, boolean callingFromFg, boolean isBindExternal,
-            boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions) {
+            boolean allowInstant, ForegroundServiceDelegationOptions fgsDelegateOptions,
+            boolean inSharedIsolatedProcess) {
         if (isSdkSandboxService && instanceName == null) {
             throw new IllegalArgumentException("No instanceName provided for sdk sandbox process");
         }
@@ -3803,11 +3828,15 @@
                 final Intent.FilterComparison filter =
                         new Intent.FilterComparison(service.cloneFilter());
                 final ServiceRestarter res = new ServiceRestarter();
+                final String processName = getProcessNameForService(sInfo, cn, callingPackage,
+                        null /* instanceName */, false /* isSdkSandbox */,
+                        false /* inSharedIsolatedProcess */);
                 r = new ServiceRecord(mAm, cn /* name */, cn /* instanceName */,
                         sInfo.applicationInfo.packageName, sInfo.applicationInfo.uid, filter, sInfo,
-                        callingFromFg, res, null /* sdkSandboxProcessName */,
+                        callingFromFg, res, processName,
                         INVALID_UID /* sdkSandboxClientAppUid */,
-                        null /* sdkSandboxClientAppPackage */);
+                        null /* sdkSandboxClientAppPackage */,
+                        false /* inSharedIsolatedProcess */);
                 res.setService(r);
                 smap.mServicesByInstanceName.put(cn, r);
                 smap.mServicesByIntent.put(filter, r);
@@ -3901,6 +3930,21 @@
                     throw new SecurityException("BIND_EXTERNAL_SERVICE failed, " + name +
                             " is not an externalService");
                 }
+                if (inSharedIsolatedProcess) {
+                    if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) == 0) {
+                        throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
+                                + className + " is not an isolatedProcess");
+                    }
+                    if ((sInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) == 0) {
+                        throw new SecurityException("BIND_SHARED_ISOLATED_PROCESS failed, "
+                                + className + " has not set the allowSharedIsolatedProcess "
+                                + " attribute.");
+                    }
+                    if (instanceName == null) {
+                        throw new IllegalArgumentException("instanceName must be provided for "
+                                + "binding a service into a shared isolated process.");
+                    }
+                }
                 if (userId > 0) {
                     if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo,
                             sInfo.name, sInfo.flags)
@@ -3934,12 +3978,12 @@
                     final Intent.FilterComparison filter
                             = new Intent.FilterComparison(service.cloneFilter());
                     final ServiceRestarter res = new ServiceRestarter();
-                    String sdkSandboxProcessName = isSdkSandboxService ? instanceName
-                                                                                  : null;
+                    String processName = getProcessNameForService(sInfo, name, callingPackage,
+                            instanceName, isSdkSandboxService, inSharedIsolatedProcess);
                     r = new ServiceRecord(mAm, className, name, definingPackageName,
                             definingUid, filter, sInfo, callingFromFg, res,
-                            sdkSandboxProcessName, sdkSandboxClientAppUid,
-                            sdkSandboxClientAppPackage);
+                            processName, sdkSandboxClientAppUid,
+                            sdkSandboxClientAppPackage, inSharedIsolatedProcess);
                     res.setService(r);
                     smap.mServicesByInstanceName.put(name, r);
                     smap.mServicesByIntent.put(filter, r);
@@ -4728,21 +4772,52 @@
                 }
             }
         } else {
-            // If this service runs in an isolated process, then each time
-            // we call startProcessLocked() we will get a new isolated
-            // process, starting another process if we are currently waiting
-            // for a previous process to come up.  To deal with this, we store
-            // in the service any current isolated process it is running in or
-            // waiting to have come up.
-            app = r.isolationHostProc;
-            if (WebViewZygote.isMultiprocessEnabled()
-                    && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
-                hostingRecord = HostingRecord.byWebviewZygote(r.instanceName, r.definingPackageName,
-                        r.definingUid, r.serviceInfo.processName);
-            }
-            if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
-                hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
-                        r.definingUid, r.serviceInfo.processName);
+            if (r.inSharedIsolatedProcess) {
+                app = mAm.mProcessList.getSharedIsolatedProcess(procName, r.appInfo.uid,
+                        r.appInfo.packageName);
+                if (app != null) {
+                    final IApplicationThread thread = app.getThread();
+                    final int pid = app.getPid();
+                    final UidRecord uidRecord = app.getUidRecord();
+                    if (thread != null) {
+                        try {
+                            if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+                                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                                        "realStartServiceLocked: " + r.shortInstanceName);
+                            }
+                            realStartServiceLocked(r, app, thread, pid, uidRecord, execInFg,
+                                    enqueueOomAdj);
+                            return null;
+                        } catch (TransactionTooLargeException e) {
+                            throw e;
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "Exception when starting service " + r.shortInstanceName,
+                                    e);
+                        } finally {
+                            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                        }
+                        // If a dead object exception was thrown -- fall through to
+                        // restart the application.
+                    }
+                }
+            } else {
+                // If this service runs in an isolated process, then each time
+                // we call startProcessLocked() we will get a new isolated
+                // process, starting another process if we are currently waiting
+                // for a previous process to come up.  To deal with this, we store
+                // in the service any current isolated process it is running in or
+                // waiting to have come up.
+                app = r.isolationHostProc;
+                if (WebViewZygote.isMultiprocessEnabled()
+                        && r.serviceInfo.packageName.equals(WebViewZygote.getPackageName())) {
+                    hostingRecord = HostingRecord.byWebviewZygote(r.instanceName,
+                            r.definingPackageName,
+                            r.definingUid, r.serviceInfo.processName);
+                }
+                if ((r.serviceInfo.flags & ServiceInfo.FLAG_USE_APP_ZYGOTE) != 0) {
+                    hostingRecord = HostingRecord.byAppZygote(r.instanceName, r.definingPackageName,
+                            r.definingUid, r.serviceInfo.processName);
+                }
             }
         }
 
@@ -7649,7 +7724,7 @@
                 null /* sdkSandboxClientAppPackage */, null /* resolvedType */, callingPackage,
                 callingPid, callingUid, userId, true /* createIfNeeded */,
                 false /* callingFromFg */, false /* isBindExternal */, false /* allowInstant */ ,
-                options);
+                options, false /* inSharedIsolatedProcess */);
         if (res == null || res.record == null) {
             Slog.d(TAG,
                     "startForegroundServiceDelegateLocked retrieveServiceLocked returns null");
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 26f8999..191460c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -197,6 +197,7 @@
 import android.app.ITaskStackListener;
 import android.app.IUiAutomationConnection;
 import android.app.IUidObserver;
+import android.app.IUnsafeIntentStrictModeCallback;
 import android.app.IUserSwitchObserver;
 import android.app.Instrumentation;
 import android.app.Notification;
@@ -695,6 +696,10 @@
     static final int BROADCAST_QUEUE_BG_OFFLOAD = 2;
     static final int BROADCAST_QUEUE_FG_OFFLOAD = 3;
 
+    @GuardedBy("this")
+    private final SparseArray<IUnsafeIntentStrictModeCallback>
+            mStrictModeCallbacks = new SparseArray<>();
+
     // Convenient for easy iteration over the queues. Foreground is first
     // so that dispatch of foreground broadcasts gets precedence.
     final BroadcastQueue[] mBroadcastQueues;
@@ -8796,6 +8801,27 @@
         }
     }
 
+    /**
+     * Register a callback to raise strict mode violations.
+     * @param callback The binder used to communicate the violations.
+     */
+    @Override
+    public void registerStrictModeCallback(IBinder callback) {
+        int callingPid = Binder.getCallingPid();
+        mStrictModeCallbacks.put(callingPid,
+                IUnsafeIntentStrictModeCallback.Stub.asInterface(callback));
+        try {
+            callback.linkToDeath(new DeathRecipient() {
+                @Override
+                public void binderDied() {
+                    mStrictModeCallbacks.remove(callingPid);
+                }
+            }, 0);
+        } catch (RemoteException e) {
+            mStrictModeCallbacks.remove(callingPid);
+        }
+    }
+
     // Depending on the policy in effect, there could be a bunch of
     // these in quick succession so we try to batch these together to
     // minimize disk writes, number of dropbox entries, and maximize
@@ -12657,7 +12683,7 @@
      * @param query the list of broadcast filters
      * @param platformCompat the instance of platform compat
      */
-    private static void filterNonExportedComponents(Intent intent, int callingUid,
+    private void filterNonExportedComponents(Intent intent, int callingUid, int callingPid,
             List query, PlatformCompat platformCompat, String callerPackage, String resolvedType) {
         if (query == null
                 || intent.getPackage() != null
@@ -12665,6 +12691,7 @@
                 || ActivityManager.canAccessUnexportedComponents(callingUid)) {
             return;
         }
+        IUnsafeIntentStrictModeCallback callback = mStrictModeCallbacks.get(callingPid);
         for (int i = query.size() - 1; i >= 0; i--) {
             String componentInfo;
             ResolveInfo resolveInfo;
@@ -12685,6 +12712,15 @@
             } else {
                 continue;
             }
+            if (callback != null) {
+                mHandler.post(() -> {
+                    try {
+                        callback.onImplicitIntentMatchedInternalComponent(intent.cloneFilter());
+                    } catch (RemoteException e) {
+                        mStrictModeCallbacks.remove(callingPid);
+                    }
+                });
+            }
             boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
                     ActivityManagerService.IMPLICIT_INTENTS_ONLY_MATCH_EXPORTED_COMPONENTS,
                     callingUid);
@@ -13054,7 +13090,7 @@
             String resolvedType, IServiceConnection connection, int flags, String instanceName,
             String callingPackage, int userId) throws TransactionTooLargeException {
         return bindServiceInstance(caller, token, service, resolvedType, connection, flags,
-                instanceName, false, 0, null, callingPackage, userId);
+                instanceName, false, INVALID_UID, null, callingPackage, userId);
     }
 
     private int bindServiceInstance(IApplicationThread caller, IBinder token, Intent service,
@@ -14616,7 +14652,7 @@
             }
         }
 
-        filterNonExportedComponents(intent, callingUid, registeredReceivers,
+        filterNonExportedComponents(intent, callingUid, callingPid, registeredReceivers,
                 mPlatformCompat, callerPackage, resolvedType);
         int NR = registeredReceivers != null ? registeredReceivers.size() : 0;
         if (!ordered && NR > 0 && !mEnableModernQueue) {
@@ -14722,7 +14758,7 @@
         if ((receivers != null && receivers.size() > 0)
                 || resultTo != null) {
             BroadcastQueue queue = broadcastQueueForIntent(intent);
-            filterNonExportedComponents(intent, callingUid, receivers,
+            filterNonExportedComponents(intent, callingUid, callingPid, receivers,
                     mPlatformCompat, callerPackage, resolvedType);
             BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, callerPackage,
                     callerFeatureId, callingPid, callingUid, callerInstantApp, resolvedType,
@@ -16574,8 +16610,8 @@
     }
 
     @Override
-    public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
-        int[] displayIds = getSecondaryDisplayIdsForStartingBackgroundUsers();
+    public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) {
+        int[] displayIds = getDisplayIdsForStartingVisibleBackgroundUsers();
         boolean validDisplay = false;
         if (displayIds != null) {
             for (int i = 0; i < displayIds.length; i++) {
@@ -16595,14 +16631,14 @@
                     displayId, mInjector);
         }
         // Permission check done inside UserController.
-        return mInjector.startUserOnSecondaryDisplay(userId, displayId);
+        return mInjector.startUserInBackgroundVisibleOnDisplay(userId, displayId);
     }
 
     @Override
-    public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
-        enforceCallingHasAtLeastOnePermission("getSecondaryDisplayIdsForStartingBackgroundUsers()",
+    public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
+        enforceCallingHasAtLeastOnePermission("getDisplayIdsForStartingVisibleBackgroundUsers()",
                 MANAGE_USERS, INTERACT_ACROSS_USERS);
-        return mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        return mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
     }
 
     /** @deprecated see the AIDL documentation {@inheritDoc} */
@@ -18195,6 +18231,16 @@
                 return mServices.getClientPackagesLocked(servicePackageName);
             }
         }
+
+        @Override
+        public IUnsafeIntentStrictModeCallback getRegisteredStrictModeCallback(int callingPid) {
+            return mStrictModeCallbacks.get(callingPid);
+        }
+
+        @Override
+        public void unregisterStrictModeCallback(int callingPid) {
+            mStrictModeCallbacks.remove(callingPid);
+        }
     }
 
     long inputDispatchingTimedOut(int pid, final boolean aboveSystem, TimeoutRecord timeoutRecord) {
@@ -18791,7 +18837,7 @@
         }
 
         /**
-         * Called by {@code AMS.getSecondaryDisplayIdsForStartingBackgroundUsers()}.
+         * Called by {@code AMS.getDisplayIdsForStartingVisibleBackgroundUsers()}.
          */
         // NOTE: ideally Injector should have no complex logic, but if this logic was moved to AMS,
         // it could not be tested with the existing ActivityManagerServiceTest (as DisplayManager,
@@ -18801,9 +18847,9 @@
         // was added on FrameworksMockingServicesTests and hence uses Extended Mockito to mock
         // final and static stuff)
         @Nullable
-        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
-            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
-                Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): not supported");
+        public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
+            if (!UserManager.isVisibleBackgroundUsersEnabled()) {
+                Slogf.w(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): not supported");
                 return null;
             }
 
@@ -18849,7 +18895,7 @@
                 // KitchenSink (or other app) can be used while running CTS tests on devices that
                 // don't have a real display.
                 // STOPSHIP: if not removed, it should at least be unit tested
-                String testingProp = "fw.secondary_display_for_starting_users_for_testing_purposes";
+                String testingProp = "fw.display_ids_for_starting_users_for_testing_purposes";
                 int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
                 if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
                     Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
@@ -18857,8 +18903,8 @@
                             testingProp);
                     return new int[] { displayId };
                 }
-                Slogf.e(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid display"
-                        + " on %s", Arrays.toString(allDisplays));
+                Slogf.e(TAG, "getDisplayIdsForStartingBackgroundUsers(): no valid display on %s",
+                        Arrays.toString(allDisplays));
                 return null;
             }
 
@@ -18866,7 +18912,7 @@
                 int[] validDisplayIds = new int[numberValidDisplays];
                 System.arraycopy(displayIds, 0, validDisplayIds, 0, numberValidDisplays);
                 if (DEBUG_MU) {
-                    Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning "
+                    Slogf.d(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): returning "
                             + "only valid displays (%d instead of %d): %s", numberValidDisplays,
                             displayIds.length, Arrays.toString(validDisplayIds));
                 }
@@ -18874,17 +18920,17 @@
             }
 
             if (DEBUG_MU) {
-                Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning all "
-                        + "(but DEFAULT_DISPLAY) displays : %s", Arrays.toString(displayIds));
+                Slogf.d(TAG, "getDisplayIdsForStartingVisibleBackgroundUsers(): returning all (but "
+                        + "DEFAULT_DISPLAY) displays : %s", Arrays.toString(displayIds));
             }
             return displayIds;
         }
 
         /**
-         * Called by {@code AMS.startUserOnSecondaryDisplay()}.
+         * Called by {@code AMS.startUserInBackgroundVisibleOnDisplay()}.
          */
-        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
-            return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+        public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) {
+            return mUserController.startUserVisibleOnDisplay(userId, displayId);
         }
 
         /**
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 7f2e5fb..80684bf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -373,8 +373,8 @@
                     return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
                 case "reset-dropbox-rate-limiter":
                     return runResetDropboxRateLimiter();
-                case "list-secondary-displays-for-starting-users":
-                    return runListSecondaryDisplaysForStartingUsers(pw);
+                case "list-displays-for-starting-users":
+                    return runListDisplaysForStartingUsers(pw);
                 case "set-foreground-service-delegate":
                     return runSetForegroundServiceDelegate(pw);
                 default:
@@ -2157,11 +2157,11 @@
             success = mInterface.startUserInBackgroundWithListener(userId, waiter);
             displaySuffix = "";
         } else {
-            if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+            if (!UserManager.isVisibleBackgroundUsersEnabled()) {
                 pw.println("Not supported");
                 return -1;
             }
-            success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
+            success = mInterface.startUserInBackgroundVisibleOnDisplay(userId, displayId);
             displaySuffix = " on display " + displayId;
         }
         if (wait && success) {
@@ -3745,8 +3745,8 @@
         return 0;
     }
 
-    int runListSecondaryDisplaysForStartingUsers(PrintWriter pw) throws RemoteException {
-        int[] displayIds = mInterface.getSecondaryDisplayIdsForStartingBackgroundUsers();
+    int runListDisplaysForStartingUsers(PrintWriter pw) throws RemoteException {
+        int[] displayIds = mInterface.getDisplayIdsForStartingVisibleBackgroundUsers();
         pw.println(displayIds == null || displayIds.length == 0
                 ? "none"
                 : Arrays.toString(displayIds));
@@ -4116,7 +4116,7 @@
             pw.println("         Set an app's background restriction level which in turn map to a app standby bucket.");
             pw.println("  get-bg-restriction-level [--user <USER_ID>] <PACKAGE>");
             pw.println("         Get an app's background restriction level.");
-            pw.println("  list-secondary-displays-for-starting-users");
+            pw.println("  list-displays-for-starting-users");
             pw.println("         Lists the id of displays that can be used to start users on "
                     + "background.");
             pw.println("  set-foreground-service-delegate [--user <USER_ID>] <PACKAGE> start|stop");
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index 45b11e1..e06ce2c9 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -2314,7 +2314,8 @@
 
     void printCurrentCpuState(StringBuilder report, long time) {
         synchronized (mProcessCpuTracker) {
-            report.append(mProcessCpuTracker.printCurrentState(time));
+            // Only print the first 10 processes
+            report.append(mProcessCpuTracker.printCurrentState(time, /* maxProcesses= */10));
         }
     }
 
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index d1bcf87..5b453b2 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -136,7 +136,7 @@
 public final class BatteryStatsService extends IBatteryStats.Stub
         implements PowerManagerInternal.LowPowerModeListener,
         BatteryStatsImpl.PlatformIdleStateCallback,
-        BatteryStatsImpl.MeasuredEnergyRetriever,
+        BatteryStatsImpl.EnergyStatsRetriever,
         Watchdog.Monitor {
     static final String TAG = "BatteryStatsService";
     static final boolean DBG = false;
@@ -153,6 +153,7 @@
     private final Context mContext;
     private final BatteryExternalStatsWorker mWorker;
     private final BatteryUsageStatsProvider mBatteryUsageStatsProvider;
+    private volatile boolean mMonitorEnabled = true;
 
     private native void getRailEnergyPowerStats(RailStats railStats);
     private CharsetDecoder mDecoderStat = StandardCharsets.UTF_8
@@ -481,6 +482,9 @@
 
     @Override
     public void monitor() {
+        if (!mMonitorEnabled) {
+            return;
+        }
         synchronized (mLock) {
         }
         synchronized (mStats) {
@@ -2531,7 +2535,7 @@
         awaitCompletion();
         syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
         synchronized (mStats) {
-            mStats.dumpMeasuredEnergyStatsLocked(pw);
+            mStats.dumpEnergyConsumerStatsLocked(pw);
         }
     }
 
@@ -2602,6 +2606,19 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        // If the monitor() method is already holding a lock on mStats, no harm done: we will
+        // just wait for mStats in the dumpUnmonitored method below.  In fact, we would want
+        // Watchdog to catch the service in the act in that situation.  We just don't want the
+        // dump method itself to be blamed for holding the lock for too long.
+        mMonitorEnabled = false;
+        try {
+            dumpUnmonitored(fd, pw, args);
+        } finally {
+            mMonitorEnabled = true;
+        }
+    }
+
+    private void dumpUnmonitored(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
         int flags = 0;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 81e249c..80f1321 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3004,6 +3004,16 @@
         }
     }
 
+    ProcessRecord getSharedIsolatedProcess(String processName, int uid, String packageName) {
+        for (int i = 0, size = mIsolatedProcesses.size(); i < size; i++) {
+            final ProcessRecord app = mIsolatedProcesses.valueAt(i);
+            if (app.info.uid == uid && app.info.packageName.equals(packageName)
+                    && app.processName.equals(processName)) {
+                return app;
+            }
+        }
+        return null;
+    }
     @Nullable
     @GuardedBy("mService")
     List<Integer> getIsolatedProcessesLocked(int uid) {
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 05726f4..8c242743 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -19,6 +19,7 @@
 import static android.app.PendingIntent.FLAG_IMMUTABLE;
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.os.PowerExemptionManager.REASON_DENIED;
+import static android.os.Process.INVALID_UID;
 
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_FOREGROUND_SERVICE;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -118,6 +119,7 @@
     boolean fgWaiting;      // is a timeout for going foreground already scheduled?
     boolean isNotAppComponentUsage; // is service binding not considered component/package usage?
     boolean isForeground;   // is service currently in foreground mode?
+    boolean inSharedIsolatedProcess; // is the service in a shared isolated process
     int foregroundId;       // Notification ID of last foreground req.
     Notification foregroundNoti; // Notification record of foreground state.
     long fgDisplayTime;     // time at which the FGS notification should become visible
@@ -723,6 +725,7 @@
         isSdkSandbox = false;
         sdkSandboxClientAppUid = 0;
         sdkSandboxClientAppPackage = null;
+        inSharedIsolatedProcess = false;
     }
 
     public static ServiceRecord newEmptyInstanceForTest(ActivityManagerService ams) {
@@ -734,14 +737,14 @@
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
             Runnable restarter) {
         this(ams, name, instanceName, definingPackageName, definingUid, intent, sInfo, callerIsFg,
-                restarter, null, 0, null);
+                restarter, sInfo.processName, INVALID_UID, null, false);
     }
 
     ServiceRecord(ActivityManagerService ams, ComponentName name,
             ComponentName instanceName, String definingPackageName, int definingUid,
             Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg,
-            Runnable restarter, String sdkSandboxProcessName, int sdkSandboxClientAppUid,
-            String sdkSandboxClientAppPackage) {
+            Runnable restarter, String processName, int sdkSandboxClientAppUid,
+            String sdkSandboxClientAppPackage, boolean inSharedIsolatedProcess) {
         this.ams = ams;
         this.name = name;
         this.instanceName = instanceName;
@@ -752,16 +755,11 @@
         serviceInfo = sInfo;
         appInfo = sInfo.applicationInfo;
         packageName = sInfo.applicationInfo.packageName;
-        this.isSdkSandbox = sdkSandboxProcessName != null;
+        this.isSdkSandbox = sdkSandboxClientAppUid != INVALID_UID;
         this.sdkSandboxClientAppUid = sdkSandboxClientAppUid;
         this.sdkSandboxClientAppPackage = sdkSandboxClientAppPackage;
-        if ((sInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0) {
-            processName = sInfo.processName + ":" + instanceName.getClassName();
-        } else if (sdkSandboxProcessName != null) {
-            processName = sdkSandboxProcessName;
-        } else {
-            processName = sInfo.processName;
-        }
+        this.inSharedIsolatedProcess = inSharedIsolatedProcess;
+        this.processName = processName;
         permission = sInfo.permission;
         exported = sInfo.exported;
         this.restarter = restarter;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index e5123ef..234eec3 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1567,8 +1567,8 @@
      *
      * @return whether the user was started
      */
-    boolean startUserOnSecondaryDisplay(@UserIdInt int userId, int displayId) {
-        checkCallingHasOneOfThosePermissions("startUserOnSecondaryDisplay",
+    boolean startUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
+        checkCallingHasOneOfThosePermissions("startUserOnDisplay",
                 MANAGE_USERS, INTERACT_ACROSS_USERS);
 
         try {
@@ -3713,7 +3713,7 @@
         }
 
         boolean isUsersOnSecondaryDisplaysEnabled() {
-            return UserManager.isUsersOnSecondaryDisplaysEnabled();
+            return UserManager.isVisibleBackgroundUsersEnabled();
         }
 
         void onUserStarting(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
index dcadd5f..a8066c1 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerPerUserService.java
@@ -16,9 +16,7 @@
 
 package com.android.server.ambientcontext;
 
-import android.Manifest;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -58,187 +56,105 @@
 import java.util.function.Consumer;
 
 /**
- * Per-user manager service for {@link AmbientContextEvent}s.
+ * Base per-user manager service for {@link AmbientContextEvent}s.
  */
-final class AmbientContextManagerPerUserService extends
+abstract class AmbientContextManagerPerUserService extends
         AbstractPerUserSystemService<AmbientContextManagerPerUserService,
                 AmbientContextManagerService> {
-    private static final String TAG = AmbientContextManagerPerUserService.class.getSimpleName();
+    private static final String TAG =
+            AmbientContextManagerPerUserService.class.getSimpleName();
 
-    @Nullable
-    @VisibleForTesting
-    RemoteAmbientContextDetectionService mRemoteService;
-
-    private ComponentName mComponentName;
+    /**
+     * The type of service.
+     */
+    enum ServiceType {
+        DEFAULT,
+        WEARABLE
+    }
 
     AmbientContextManagerPerUserService(
             @NonNull AmbientContextManagerService master, Object lock, @UserIdInt int userId) {
         super(master, lock, userId);
     }
 
-    void destroyLocked() {
-        Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
-        if (mRemoteService != null) {
-            synchronized (mLock) {
-                mRemoteService.unbind();
-                mRemoteService = null;
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void ensureRemoteServiceInitiated() {
-        if (mRemoteService == null) {
-            mRemoteService = new RemoteAmbientContextDetectionService(
-                    getContext(), mComponentName, getUserId());
-        }
-    }
+    /**
+     * Returns the current bound AmbientContextManagerPerUserService component for this user.
+     */
+    abstract ComponentName getComponentName();
 
     /**
-     * get the currently bound component name.
+     * Sets the component name for the per user service.
      */
-    @VisibleForTesting
-    ComponentName getComponentName() {
-        return mComponentName;
-    }
-
+    abstract void setComponentName(ComponentName componentName);
 
     /**
-     * Resolves and sets up the service if it had not been done yet. Returns true if the service
-     * is available.
+     * Ensures that the remote service is initiated.
      */
-    @GuardedBy("mLock")
-    @VisibleForTesting
-    boolean setUpServiceIfNeeded() {
-        if (mComponentName == null) {
-            mComponentName = updateServiceInfoLocked();
-        }
-        if (mComponentName == null) {
-            return false;
-        }
-
-        ServiceInfo serviceInfo;
-        try {
-            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
-                    mComponentName, 0, mUserId);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "RemoteException while setting up service");
-            return false;
-        }
-        return serviceInfo != null;
-    }
-
-    @Override
-    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
-            throws PackageManager.NameNotFoundException {
-        ServiceInfo serviceInfo;
-        try {
-            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
-                    0, mUserId);
-            if (serviceInfo != null) {
-                final String permission = serviceInfo.permission;
-                if (!Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE.equals(
-                        permission)) {
-                    throw new SecurityException(String.format(
-                            "Service %s requires %s permission. Found %s permission",
-                            serviceInfo.getComponentName(),
-                            Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE,
-                            serviceInfo.permission));
-                }
-            }
-        } catch (RemoteException e) {
-            throw new PackageManager.NameNotFoundException(
-                    "Could not get service for " + serviceComponent);
-        }
-        return serviceInfo;
-    }
-
-    @Override
-    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
-        synchronized (super.mLock) {
-            super.dumpLocked(prefix, pw);
-        }
-        if (mRemoteService != null) {
-            mRemoteService.dump("", new IndentingPrintWriter(pw, "  "));
-        }
-    }
+    abstract void ensureRemoteServiceInitiated();
 
     /**
-     * Handles client registering as an observer. Only one registration is supported per app
-     * package. A new registration from the same package will overwrite the previous registration.
+     * Returns the AmbientContextManagerPerUserService {@link ServiceType} for this user.
      */
-    public void onRegisterObserver(AmbientContextEventRequest request,
-            String packageName, IAmbientContextObserver observer) {
+    abstract ServiceType getServiceType();
+
+    /**
+     * Returns the int config for the consent component for the
+     * specific AmbientContextManagerPerUserService type
+     */
+    abstract int getConsentComponentConfig();
+
+    /**
+     * Returns the int config for the intent extra key for the
+     * caller's package name while requesting ambient context consent.
+     */
+    abstract int getAmbientContextPackageNameExtraKeyConfig();
+
+    /**
+     * Returns the int config for the Intent extra key for the event code int array while
+     * requesting ambient context consent.
+     */
+    abstract int getAmbientContextEventArrayExtraKeyConfig();
+
+    /**
+     * Returns the permission that is required to bind to this service.
+     */
+    abstract String getProtectedBindPermission();
+
+    /**
+     * Returns the remote service implementation for this user.
+     */
+    abstract RemoteAmbientDetectionService getRemoteService();
+
+    /**
+     * Clears the remote service.
+     */
+    abstract void clearRemoteService();
+
+    /**
+     * Called when there's an application with the callingPackage name is requesting for
+     * the AmbientContextDetection's service status.
+     *
+     * @param eventTypes the event types to query for
+     * @param callingPackage the package query for information
+     * @param statusCallback the callback to deliver the status on
+     */
+    public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
+            RemoteCallback statusCallback) {
+        Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
+                + " for " + callingPackage);
         synchronized (mLock) {
             if (!setUpServiceIfNeeded()) {
                 Slog.w(TAG, "Detection service is not available at this moment.");
-                completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                sendStatusCallback(statusCallback,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-
-            // Register package and add to existing ClientRequests cache
-            startDetection(request, packageName, observer);
-            mMaster.newClientAdded(mUserId, request, packageName, observer);
-        }
-    }
-
-    /**
-     * Returns a RemoteCallback that handles the status from the detection service, and
-     * sends results to the client callback.
-     */
-    private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) {
-        return new RemoteCallback(result -> {
-            AmbientContextDetectionServiceStatus serviceStatus =
-                    (AmbientContextDetectionServiceStatus) result.get(
-                            AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
-            final long token = Binder.clearCallingIdentity();
-            try {
-                int statusCode = serviceStatus.getStatusCode();
-                statusConsumer.accept(statusCode);
-                Slog.i(TAG, "Got detection status of " + statusCode
-                        + " for " + serviceStatus.getPackageName());
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        });
-    }
-
-    void startDetection(AmbientContextEventRequest request, String callingPackage,
-            IAmbientContextObserver observer) {
-        Slog.d(TAG, "Requested detection of " + request.getEventTypes());
-        synchronized (mLock) {
-            if (setUpServiceIfNeeded()) {
-                ensureRemoteServiceInitiated();
-                mRemoteService.startDetection(request, callingPackage,
-                        createDetectionResultRemoteCallback(),
-                        getServerStatusCallback(
-                                statusCode -> completeRegistration(observer, statusCode)));
-            } else {
-                Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
-                completeRegistration(observer,
-                        AmbientContextManager.STATUS_NOT_SUPPORTED);
-            }
-        }
-    }
-
-    /**
-     * Sends the result response with the specified status to the callback.
-     */
-    static void sendStatusCallback(RemoteCallback statusCallback,
-            @AmbientContextManager.StatusCode int statusCode) {
-        Bundle bundle = new Bundle();
-        bundle.putInt(
-                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
-                statusCode);
-        statusCallback.sendResult(bundle);
-    }
-
-    static void completeRegistration(IAmbientContextObserver observer, int statusCode) {
-        try {
-            observer.onRegistrationComplete(statusCode);
-        } catch (RemoteException e) {
-            Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: "
-                    + e.getMessage());
+            ensureRemoteServiceInitiated();
+            getRemoteService().queryServiceStatus(
+                    eventTypes,
+                    callingPackage,
+                    getServerStatusCallback(
+                            statusCode -> sendStatusCallback(statusCallback, statusCode)));
         }
     }
 
@@ -254,26 +170,9 @@
         }
     }
 
-    public void onQueryServiceStatus(int[] eventTypes, String callingPackage,
-            RemoteCallback statusCallback) {
-        Slog.d(TAG, "Query event status of " + Arrays.toString(eventTypes)
-                + " for " + callingPackage);
-        synchronized (mLock) {
-            if (!setUpServiceIfNeeded()) {
-                Slog.w(TAG, "Detection service is not available at this moment.");
-                sendStatusCallback(statusCallback,
-                        AmbientContextManager.STATUS_NOT_SUPPORTED);
-                return;
-            }
-            ensureRemoteServiceInitiated();
-            mRemoteService.queryServiceStatus(
-                    eventTypes,
-                    callingPackage,
-                    getServerStatusCallback(
-                            statusCode -> sendStatusCallback(statusCallback, statusCode)));
-        }
-    }
-
+    /**
+     * Starts the consent activity for the calling package and event types.
+     */
     public void onStartConsentActivity(int[] eventTypes, String callingPackage) {
         Slog.d(TAG, "Opening consent activity of " + Arrays.toString(eventTypes)
                 + " for " + callingPackage);
@@ -315,9 +214,9 @@
         try {
             Context context = getContext();
             String packageNameExtraKey = context.getResources().getString(
-                    com.android.internal.R.string.config_ambientContextPackageNameExtraKey);
+                    getAmbientContextPackageNameExtraKeyConfig());
             String eventArrayExtraKey = context.getResources().getString(
-                    com.android.internal.R.string.config_ambientContextEventArrayExtraKey);
+                    getAmbientContextEventArrayExtraKeyConfig());
 
             // Create consent activity intent with the calling package name and requested events
             intent.setComponent(consentComponent);
@@ -344,37 +243,163 @@
     }
 
     /**
-     * Returns the consent activity component from config lookup.
+     * Handles client registering as an observer. Only one registration is supported per app
+     * package. A new registration from the same package will overwrite the previous registration.
      */
-    private ComponentName getConsentComponent() {
-        Context context = getContext();
-        String consentComponent = context.getResources().getString(
-                    com.android.internal.R.string.config_defaultAmbientContextConsentComponent);
-        if (TextUtils.isEmpty(consentComponent)) {
-            return null;
+    public void onRegisterObserver(AmbientContextEventRequest request,
+            String packageName, IAmbientContextObserver observer) {
+        synchronized (mLock) {
+            if (!setUpServiceIfNeeded()) {
+                Slog.w(TAG, "Detection service is not available at this moment.");
+                completeRegistration(observer, AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+
+            // Register package and add to existing ClientRequests cache
+            startDetection(request, packageName, observer);
+            mMaster.newClientAdded(mUserId, request, packageName, observer);
         }
-        Slog.i(TAG, "Consent component name: " + consentComponent);
-        return ComponentName.unflattenFromString(consentComponent);
     }
 
+    @Override
+    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
+            throws PackageManager.NameNotFoundException {
+        Slog.d(TAG, "newServiceInfoLocked with component name: "
+                + serviceComponent.getClassName());
+
+        if (getComponentName() == null
+                || !serviceComponent.getClassName().equals(getComponentName().getClassName())) {
+            Slog.d(TAG, "service name does not match this per user, returning...");
+            return null;
+        }
+
+        ServiceInfo serviceInfo;
+        try {
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
+                    0, mUserId);
+            if (serviceInfo != null) {
+                final String permission = serviceInfo.permission;
+                if (!getProtectedBindPermission().equals(
+                        permission)) {
+                    throw new SecurityException(String.format(
+                            "Service %s requires %s permission. Found %s permission",
+                            serviceInfo.getComponentName(),
+                            getProtectedBindPermission(),
+                            serviceInfo.permission));
+                }
+            }
+        } catch (RemoteException e) {
+            throw new PackageManager.NameNotFoundException(
+                    "Could not get service for " + serviceComponent);
+        }
+        return serviceInfo;
+    }
+
+    /**
+     * Dumps the remote service.
+     */
+    protected void dumpLocked(@NonNull String prefix, @NonNull PrintWriter pw) {
+        synchronized (super.mLock) {
+            super.dumpLocked(prefix, pw);
+        }
+        RemoteAmbientDetectionService remoteService = getRemoteService();
+        if (remoteService != null) {
+            remoteService.dump("", new IndentingPrintWriter(pw, "  "));
+        }
+    }
+
+    /**
+     * Send request to the remote AmbientContextDetectionService impl to stop detecting the
+     * specified events. Intended for use by shell command for testing.
+     * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
+     */
     @VisibleForTesting
-    void stopDetection(String packageName) {
+    protected void stopDetection(String packageName) {
         Slog.d(TAG, "Stop detection for " + packageName);
         synchronized (mLock) {
-            if (mComponentName != null) {
+            if (getComponentName() != null) {
                 ensureRemoteServiceInitiated();
-                mRemoteService.stopDetection(packageName);
+                RemoteAmbientDetectionService remoteService = getRemoteService();
+                remoteService.stopDetection(packageName);
             }
         }
     }
 
     /**
+     * Destroys this service and unbinds from the remote service.
+     */
+    protected void destroyLocked() {
+        Slog.d(TAG, "Trying to cancel the remote request. Reason: Service destroyed.");
+        RemoteAmbientDetectionService remoteService = getRemoteService();
+        if (remoteService != null) {
+            synchronized (mLock) {
+                remoteService.unbind();
+                clearRemoteService();
+            }
+        }
+    }
+
+    /**
+     * Send request to the remote AmbientContextDetectionService impl to start detecting the
+     * specified events. Intended for use by shell command for testing.
+     * Requires ACCESS_AMBIENT_CONTEXT_EVENT permission.
+     */
+    protected void startDetection(AmbientContextEventRequest request, String callingPackage,
+            IAmbientContextObserver observer) {
+        Slog.d(TAG, "Requested detection of " + request.getEventTypes());
+        synchronized (mLock) {
+            if (setUpServiceIfNeeded()) {
+                ensureRemoteServiceInitiated();
+                RemoteAmbientDetectionService remoteService = getRemoteService();
+                remoteService.startDetection(request, callingPackage,
+                        createDetectionResultRemoteCallback(),
+                        getServerStatusCallback(
+                                statusCode -> completeRegistration(observer, statusCode)));
+            } else {
+                Slog.w(TAG, "No valid component found for AmbientContextDetectionService");
+                completeRegistration(observer,
+                        AmbientContextManager.STATUS_NOT_SUPPORTED);
+            }
+        }
+    }
+
+    /**
+     * Notifies the observer the status of the registration.
+     *
+     * @param observer the observer to notify
+     * @param statusCode the status to notify
+     */
+    protected void completeRegistration(IAmbientContextObserver observer, int statusCode) {
+        try {
+            observer.onRegistrationComplete(statusCode);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Failed to call IAmbientContextObserver.onRegistrationComplete: "
+                    + e.getMessage());
+        }
+    }
+
+    /**
+     * Sends the status on the {@link RemoteCallback}.
+     *
+     * @param statusCallback the callback to send the status on
+     * @param statusCode the status to send
+     */
+    protected void sendStatusCallback(RemoteCallback statusCallback,
+            @AmbientContextManager.StatusCode int statusCode) {
+        Bundle bundle = new Bundle();
+        bundle.putInt(
+                AmbientContextManager.STATUS_RESPONSE_BUNDLE_KEY,
+                statusCode);
+        statusCallback.sendResult(bundle);
+    }
+
+    /**
      * Sends out the Intent to the client after the event is detected.
      *
      * @param pendingIntent Client's PendingIntent for callback
      * @param events detected events from the detection service
      */
-    void sendDetectionResultIntent(PendingIntent pendingIntent,
+    protected void sendDetectionResultIntent(PendingIntent pendingIntent,
             List<AmbientContextEvent> events) {
         Intent intent = new Intent();
         intent.putExtra(AmbientContextManager.EXTRA_AMBIENT_CONTEXT_EVENTS,
@@ -384,8 +409,8 @@
         BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setPendingIntentBackgroundActivityLaunchAllowed(false);
         try {
-            pendingIntent.send(getContext(), 0, intent, null, null, null,
-                    options.toBundle());
+            pendingIntent.send(getContext(), 0, intent, null,
+                    null, null, options.toBundle());
             Slog.i(TAG, "Sending PendingIntent to " + pendingIntent.getCreatorPackage() + ": "
                     + events);
         } catch (PendingIntent.CanceledException e) {
@@ -394,7 +419,7 @@
     }
 
     @NonNull
-    RemoteCallback createDetectionResultRemoteCallback() {
+    protected RemoteCallback createDetectionResultRemoteCallback() {
         return new RemoteCallback(result -> {
             AmbientContextDetectionResult detectionResult =
                     (AmbientContextDetectionResult) result.get(
@@ -418,4 +443,80 @@
             }
         });
     }
+
+    /**
+     * Resolves and sets up the service if it had not been done yet. Returns true if the service
+     * is available.
+     */
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    private boolean setUpServiceIfNeeded() {
+        if (getComponentName() == null) {
+            ComponentName[] componentNames = updateServiceInfoListLocked();
+            if (componentNames == null || componentNames.length != 2) {
+                Slog.d(TAG, "updateServiceInfoListLocked returned incorrect componentNames");
+                return false;
+            }
+
+            switch (getServiceType()) {
+                case DEFAULT:
+                    setComponentName(componentNames[0]);
+                    break;
+                case WEARABLE:
+                    setComponentName(componentNames[1]);
+                    break;
+                default:
+                    Slog.d(TAG, "updateServiceInfoListLocked returned unknown service types.");
+                    return false;
+            }
+        }
+
+        if (getComponentName() == null) {
+            return false;
+        }
+
+        ServiceInfo serviceInfo;
+        try {
+            serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+                    getComponentName(), 0, mUserId);
+        } catch (RemoteException e) {
+            Slog.w(TAG, "RemoteException while setting up service");
+            return false;
+        }
+        return serviceInfo != null;
+    }
+
+    /**
+     * Returns a RemoteCallback that handles the status from the detection service, and
+     * sends results to the client callback.
+     */
+    private RemoteCallback getServerStatusCallback(Consumer<Integer> statusConsumer) {
+        return new RemoteCallback(result -> {
+            AmbientContextDetectionServiceStatus serviceStatus =
+                    (AmbientContextDetectionServiceStatus) result.get(
+                            AmbientContextDetectionServiceStatus.STATUS_RESPONSE_BUNDLE_KEY);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                int statusCode = serviceStatus.getStatusCode();
+                statusConsumer.accept(statusCode);
+                Slog.i(TAG, "Got detection status of " + statusCode
+                        + " for " + serviceStatus.getPackageName());
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        });
+    }
+
+    /**
+     * Returns the consent activity component from config lookup.
+     */
+    private ComponentName getConsentComponent() {
+        Context context = getContext();
+        String consentComponent = context.getResources().getString(getConsentComponentConfig());
+        if (TextUtils.isEmpty(consentComponent)) {
+            return null;
+        }
+        Slog.i(TAG, "Consent component name: " + consentComponent);
+        return ComponentName.unflattenFromString(consentComponent);
+    }
 }
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
index e205e84..5c18827 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextManagerService.java
@@ -17,6 +17,7 @@
 package com.android.server.ambientcontext;
 
 import static android.provider.DeviceConfig.NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE;
+import static android.provider.DeviceConfig.NAMESPACE_WEARABLE_SENSING;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -44,12 +45,16 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.ambientcontext.AmbientContextManagerPerUserService.ServiceType;
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.FrameworkResourcesServiceNameResolver;
 import com.android.server.pm.KnownPackages;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -62,6 +67,12 @@
                 AmbientContextManagerPerUserService> {
     private static final String TAG = AmbientContextManagerService.class.getSimpleName();
     private static final String KEY_SERVICE_ENABLED = "service_enabled";
+    private static final Set<Integer> DEFAULT_EVENT_SET = new HashSet<>(){{
+            add(AmbientContextEvent.EVENT_COUGH);
+            add(AmbientContextEvent.EVENT_SNORE);
+            add(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP);
+        }
+    };
 
     /** Default value in absence of {@link DeviceConfig} override. */
     private static final boolean DEFAULT_SERVICE_ENABLED = true;
@@ -104,14 +115,16 @@
 
     private final Context mContext;
     boolean mIsServiceEnabled;
+    boolean mIsWearableServiceEnabled;
     private Set<ClientRequest> mExistingClientRequests;
 
     public AmbientContextManagerService(Context context) {
         super(context,
                 new FrameworkResourcesServiceNameResolver(
                         context,
-                        R.string.config_defaultAmbientContextDetectionService),
-                        /*disallowProperty=*/null,
+                        R.array.config_defaultAmbientContextServices,
+                        /*isMultiple=*/ true),
+                /*disallowProperty=*/null,
                 PACKAGE_UPDATE_POLICY_REFRESH_EAGER
                         | /*To avoid high latency*/ PACKAGE_RESTART_POLICY_REFRESH_EAGER);
         mContext = context;
@@ -134,6 +147,9 @@
             mIsServiceEnabled = DeviceConfig.getBoolean(
                     NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
                     KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+            mIsWearableServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_WEARABLE_SENSING,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
         }
     }
 
@@ -180,13 +196,62 @@
             mIsServiceEnabled = DeviceConfig.getBoolean(
                     NAMESPACE_AMBIENT_CONTEXT_MANAGER_SERVICE,
                     KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
+            mIsWearableServiceEnabled = DeviceConfig.getBoolean(
+                    NAMESPACE_WEARABLE_SENSING,
+                    KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED);
         }
     }
 
     @Override
     protected AmbientContextManagerPerUserService newServiceLocked(int resolvedUserId,
             boolean disabled) {
-        return new AmbientContextManagerPerUserService(this, mLock, resolvedUserId);
+        // This service uses newServiceListLocked, it is configured in multiple mode.
+        return null;
+    }
+
+    @Override // from AbstractMasterSystemService
+    protected List<AmbientContextManagerPerUserService> newServiceListLocked(int resolvedUserId,
+            boolean disabled, String[] serviceNames) {
+        if (serviceNames == null || serviceNames.length == 0) {
+            Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
+            return new ArrayList<>();
+        }
+
+        List<AmbientContextManagerPerUserService> serviceList =
+                new ArrayList<>(serviceNames.length);
+        if (serviceNames.length == 2) {
+            Slog.i(TAG, "Not using default services, "
+                    + "services provided for testing should be exactly two services.");
+            if (!isDefaultService(serviceNames[0]) && !isDefaultWearableService(serviceNames[1])) {
+                serviceList.add(new DefaultAmbientContextManagerPerUserService(
+                        this, mLock, resolvedUserId,
+                        AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceNames[0]));
+                serviceList.add(new WearableAmbientContextManagerPerUserService(
+                        this, mLock, resolvedUserId,
+                        AmbientContextManagerPerUserService.ServiceType.WEARABLE,
+                        serviceNames[1]));
+            }
+            return serviceList;
+        } else {
+            Slog.i(TAG, "Incorrect number of services provided for testing.");
+        }
+
+        for (String serviceName : serviceNames) {
+            Slog.d(TAG, "newServicesListLocked with service name: " + serviceName);
+            if (getServiceType(serviceName)
+                    == AmbientContextManagerPerUserService.ServiceType.WEARABLE) {
+                serviceList.add(new
+                        WearableAmbientContextManagerPerUserService(
+                        this, mLock, resolvedUserId,
+                        AmbientContextManagerPerUserService.ServiceType.WEARABLE, serviceName));
+            } else {
+                serviceList.add(new DefaultAmbientContextManagerPerUserService(
+                        this, mLock, resolvedUserId,
+                        AmbientContextManagerPerUserService.ServiceType.DEFAULT, serviceName));
+            }
+
+        }
+        return serviceList;
     }
 
     @Override
@@ -239,7 +304,10 @@
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
         synchronized (mLock) {
-            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            AmbientContextManagerPerUserService service =
+                    getAmbientContextManagerPerUserServiceForEventTypes(
+                            userId,
+                            request.getEventTypes());
             if (service != null) {
                 service.startDetection(request, packageName, observer);
             } else {
@@ -257,11 +325,19 @@
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
         synchronized (mLock) {
-            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
-            if (service != null) {
-                service.stopDetection(packageName);
-            } else {
-                Slog.i(TAG, "service not available for user_id: " + userId);
+            for (ClientRequest cr : mExistingClientRequests) {
+                Slog.i(TAG, "Looping through clients");
+                if (cr.hasUserIdAndPackageName(userId, packageName)) {
+                    Slog.i(TAG, "we have an existing client");
+                    AmbientContextManagerPerUserService service =
+                            getAmbientContextManagerPerUserServiceForEventTypes(
+                                    userId, cr.getRequest().getEventTypes());
+                    if (service != null) {
+                        service.stopDetection(packageName);
+                    } else {
+                        Slog.i(TAG, "service not available for user_id: " + userId);
+                    }
+                }
             }
         }
     }
@@ -276,7 +352,9 @@
         mContext.enforceCallingOrSelfPermission(
                 Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
         synchronized (mLock) {
-            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            AmbientContextManagerPerUserService service =
+                    getAmbientContextManagerPerUserServiceForEventTypes(
+                            userId, intArrayToIntegerSet(eventTypes));
             if (service != null) {
                 service.onQueryServiceStatus(eventTypes, packageName, callback);
             } else {
@@ -287,14 +365,18 @@
 
     private void restorePreviouslyEnabledClients(int userId) {
         synchronized (mLock) {
-            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
-            for (ClientRequest clientRequest : mExistingClientRequests) {
-                // Start detection for previously enabled clients
-                if (clientRequest.hasUserId(userId)) {
-                    Slog.d(TAG, "Restoring detection for " + clientRequest.getPackageName());
-                    service.startDetection(clientRequest.getRequest(),
-                            clientRequest.getPackageName(),
-                            clientRequest.getObserver());
+            final List<AmbientContextManagerPerUserService> services =
+                    getServiceListForUserLocked(userId);
+            for (AmbientContextManagerPerUserService service : services) {
+                for (ClientRequest clientRequest : mExistingClientRequests) {
+                    // Start detection for previously enabled clients
+                    if (clientRequest.hasUserId(userId)) {
+                        Slog.d(TAG, "Restoring detection for "
+                                + clientRequest.getPackageName());
+                        service.startDetection(clientRequest.getRequest(),
+                                clientRequest.getPackageName(),
+                                clientRequest.getObserver());
+                    }
                 }
             }
         }
@@ -303,9 +385,12 @@
     /**
      * Returns the AmbientContextManagerPerUserService component for this user.
      */
-    public ComponentName getComponentName(@UserIdInt int userId) {
+    public ComponentName getComponentName(
+            @UserIdInt int userId,
+            AmbientContextManagerPerUserService.ServiceType serviceType) {
         synchronized (mLock) {
-            final AmbientContextManagerPerUserService service = getServiceForUserLocked(userId);
+            final AmbientContextManagerPerUserService service =
+                    getServiceForType(userId, serviceType);
             if (service != null) {
                 return service.getComponentName();
             }
@@ -313,10 +398,132 @@
         return null;
     }
 
-    private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub {
-        final AmbientContextManagerPerUserService mService = getServiceForUserLocked(
-                UserHandle.getCallingUserId());
+    private AmbientContextManagerPerUserService getAmbientContextManagerPerUserServiceForEventTypes(
+              @UserIdInt int userId, Set<Integer> eventTypes) {
+        if (isWearableEventTypesOnly(eventTypes)) {
+            return getServiceForType(userId,
+                    AmbientContextManagerPerUserService.ServiceType.WEARABLE);
+        } else {
+            return getServiceForType(userId,
+                    AmbientContextManagerPerUserService.ServiceType.DEFAULT);
+        }
+    }
 
+    private Set<Integer> intArrayToIntegerSet(int[] eventTypes) {
+        Set<Integer> types = new HashSet<>();
+        for (Integer i : eventTypes) {
+            types.add(i);
+        }
+        return types;
+    }
+
+    private AmbientContextManagerPerUserService.ServiceType getServiceType(String serviceName) {
+        final String wearableService = mContext.getResources()
+                .getString(R.string.config_defaultWearableSensingService);
+        if (wearableService != null && wearableService.equals(serviceName)) {
+            return AmbientContextManagerPerUserService.ServiceType.WEARABLE;
+        }
+
+        return AmbientContextManagerPerUserService.ServiceType.DEFAULT;
+    }
+
+    private boolean isDefaultService(String serviceName) {
+        final String defaultService = mContext.getResources()
+                .getString(R.string.config_defaultAmbientContextDetectionService);
+        if (defaultService != null && defaultService.equals(serviceName)) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isDefaultWearableService(String serviceName) {
+        final String wearableService = mContext.getResources()
+                .getString(R.string.config_defaultWearableSensingService);
+        if (wearableService != null && wearableService.equals(serviceName)) {
+            return true;
+        }
+        return false;
+    }
+
+    private AmbientContextManagerPerUserService getServiceForType(int userId,
+            AmbientContextManagerPerUserService.ServiceType serviceType) {
+        Slog.d(TAG, "getServiceForType with userid: "
+                + userId + " service type: " + serviceType.name());
+        synchronized (mLock) {
+            final List<AmbientContextManagerPerUserService> services =
+                    getServiceListForUserLocked(userId);
+            Slog.d(TAG, "Services that are available: "
+                    + (services == null ? "null services" : services.size()
+                    + " number of services"));
+            if (services == null) {
+                return null;
+            }
+
+            for (AmbientContextManagerPerUserService service : services) {
+                if (service.getServiceType() == serviceType) {
+                    return service;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean isWearableEventTypesOnly(Set<Integer> eventTypes) {
+        if (eventTypes.isEmpty()) {
+            Slog.d(TAG, "empty event types.");
+            return false;
+        }
+        for (Integer eventType : eventTypes) {
+            if (eventType < AmbientContextEvent.EVENT_VENDOR_WEARABLE_START) {
+                Slog.d(TAG, "Not all events types are wearable events.");
+                return false;
+            }
+        }
+        Slog.d(TAG, "only wearable events.");
+        return true;
+    }
+
+    private boolean isWearableEventTypesOnly(int[] eventTypes) {
+        Integer[] events = intArrayToIntegerArray(eventTypes);
+        return isWearableEventTypesOnly(new HashSet<>(Arrays.asList(events)));
+    }
+
+    private boolean containsMixedEvents(int[] eventTypes) {
+        if (isWearableEventTypesOnly(eventTypes)) {
+            return false;
+        }
+        // It's not only wearable events so check if it's only default events.
+        for (Integer event : eventTypes) {
+            if (!DEFAULT_EVENT_SET.contains(event)) {
+                // mixed events.
+                Slog.w(TAG, "Received mixed event types, this is not supported.");
+                return true;
+            }
+        }
+        // Only default events.
+        return false;
+    }
+
+    private static int[] integerSetToIntArray(@NonNull Set<Integer> integerSet) {
+        int[] intArray = new int[integerSet.size()];
+        int i = 0;
+        for (Integer type : integerSet) {
+            intArray[i++] = type;
+        }
+        return intArray;
+    }
+
+    @NonNull
+    private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) {
+        Integer[] intArray = new Integer[integerSet.length];
+        int i = 0;
+        for (Integer type : integerSet) {
+            intArray[i++] = type;
+        }
+        return intArray;
+    }
+
+    private final class AmbientContextManagerInternal extends IAmbientContextManager.Stub {
         @Override
         public void registerObserver(
                 AmbientContextEventRequest request, PendingIntent resultPendingIntent,
@@ -324,17 +531,21 @@
             Objects.requireNonNull(request);
             Objects.requireNonNull(resultPendingIntent);
             Objects.requireNonNull(statusCallback);
+            AmbientContextManagerPerUserService service =
+                    getAmbientContextManagerPerUserServiceForEventTypes(
+                            UserHandle.getCallingUserId(),
+                            request.getEventTypes());
             // Wrap the PendingIntent and statusCallback in a IAmbientContextObserver to make the
             // code unified
             IAmbientContextObserver observer = new IAmbientContextObserver.Stub() {
                 @Override
                 public void onEvents(List<AmbientContextEvent> events) throws RemoteException {
-                    mService.sendDetectionResultIntent(resultPendingIntent, events);
+                    service.sendDetectionResultIntent(resultPendingIntent, events);
                 }
 
                 @Override
                 public void onRegistrationComplete(int statusCode) throws RemoteException {
-                    AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
+                    service.sendStatusCallback(statusCallback,
                             statusCode);
                 }
             };
@@ -356,13 +567,37 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
             assertCalledByPackageOwner(packageName);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Service not available.");
-                AmbientContextManagerPerUserService.completeRegistration(observer,
+            AmbientContextManagerPerUserService service =
+                    getAmbientContextManagerPerUserServiceForEventTypes(
+                            UserHandle.getCallingUserId(),
+                            request.getEventTypes());
+
+            if (service == null) {
+                Slog.w(TAG, "onRegisterObserver unavailable user_id: "
+                        + UserHandle.getCallingUserId());
+            }
+
+            if (service.getServiceType() == ServiceType.DEFAULT && !mIsServiceEnabled) {
+                Slog.d(TAG, "Service not available.");
+                service.completeRegistration(observer,
                         AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
                 return;
             }
-            mService.onRegisterObserver(request, packageName, observer);
+            if (service.getServiceType() == ServiceType.WEARABLE && !mIsWearableServiceEnabled) {
+                Slog.d(TAG, "Wearable Service not available.");
+                service.completeRegistration(observer,
+                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                return;
+            }
+            if (containsMixedEvents(integerSetToIntArray(request.getEventTypes()))) {
+                Slog.d(TAG, "AmbientContextEventRequest contains mixed events,"
+                        + " this is not supported.");
+                service.completeRegistration(observer,
+                        AmbientContextManager.STATUS_NOT_SUPPORTED);
+                return;
+            }
+
+            service.onRegisterObserver(request, packageName, observer);
         }
 
         @Override
@@ -370,7 +605,20 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
             assertCalledByPackageOwner(callingPackage);
-            mService.onUnregisterObserver(callingPackage);
+
+            AmbientContextManagerPerUserService service = null;
+            for (ClientRequest cr : mExistingClientRequests) {
+                if (cr.getPackageName().equals(callingPackage)) {
+                    service = getAmbientContextManagerPerUserServiceForEventTypes(
+                            UserHandle.getCallingUserId(), cr.getRequest().getEventTypes());
+                    if (service != null) {
+                        service.onUnregisterObserver(callingPackage);
+                    } else {
+                        Slog.w(TAG, "onUnregisterObserver unavailable user_id: "
+                                + UserHandle.getCallingUserId());
+                    }
+                }
+            }
         }
 
         @Override
@@ -382,14 +630,40 @@
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
             assertCalledByPackageOwner(callingPackage);
-            if (!mIsServiceEnabled) {
-                Slog.w(TAG, "Detection service not available.");
-                AmbientContextManagerPerUserService.sendStatusCallback(statusCallback,
-                        AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
-                return;
+            synchronized (mLock) {
+                AmbientContextManagerPerUserService service =
+                        getAmbientContextManagerPerUserServiceForEventTypes(
+                                UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes));
+                if (service == null) {
+                    Slog.w(TAG, "onQueryServiceStatus unavailable user_id: "
+                            + UserHandle.getCallingUserId());
+                }
+
+                if (service.getServiceType() == ServiceType.DEFAULT && !mIsServiceEnabled) {
+                    Slog.d(TAG, "Service not available.");
+                    service.sendStatusCallback(statusCallback,
+                            AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                    return;
+                }
+                if (service.getServiceType() == ServiceType.WEARABLE
+                        && !mIsWearableServiceEnabled) {
+                    Slog.d(TAG, "Wearable Service not available.");
+                    service.sendStatusCallback(statusCallback,
+                            AmbientContextManager.STATUS_SERVICE_UNAVAILABLE);
+                    return;
+                }
+
+                if (containsMixedEvents(eventTypes)) {
+                    Slog.d(TAG, "AmbientContextEventRequest contains mixed events,"
+                            + " this is not supported.");
+                    service.sendStatusCallback(statusCallback,
+                            AmbientContextManager.STATUS_NOT_SUPPORTED);
+                    return;
+                }
+
+                service.onQueryServiceStatus(eventTypes, callingPackage,
+                        statusCallback);
             }
-            mService.onQueryServiceStatus(eventTypes, callingPackage,
-                    statusCallback);
         }
 
         @Override
@@ -399,7 +673,23 @@
             assertCalledByPackageOwner(callingPackage);
             mContext.enforceCallingOrSelfPermission(
                     Manifest.permission.ACCESS_AMBIENT_CONTEXT_EVENT, TAG);
-            mService.onStartConsentActivity(eventTypes, callingPackage);
+
+            if (containsMixedEvents(eventTypes)) {
+                Slog.d(TAG, "AmbientContextEventRequest contains mixed events,"
+                        + " this is not supported.");
+                return;
+            }
+
+            AmbientContextManagerPerUserService service =
+                    getAmbientContextManagerPerUserServiceForEventTypes(
+                            UserHandle.getCallingUserId(), intArrayToIntegerSet(eventTypes));
+
+            if (service != null) {
+                service.onStartConsentActivity(eventTypes, callingPackage);
+            } else {
+                Slog.w(TAG, "startConsentActivity unavailable user_id: "
+                        + UserHandle.getCallingUserId());
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
index a3ffcde8..8808854 100644
--- a/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
+++ b/services/core/java/com/android/server/ambientcontext/AmbientContextShellCommand.java
@@ -28,6 +28,7 @@
 import android.os.RemoteCallback;
 import android.os.RemoteException;
 import android.os.ShellCommand;
+import android.util.Slog;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -36,6 +37,7 @@
  * Shell command for {@link AmbientContextManagerService}.
  */
 final class AmbientContextShellCommand extends ShellCommand {
+    private static final String TAG = AmbientContextShellCommand.class.getSimpleName();
 
     private static final AmbientContextEventRequest REQUEST =
             new AmbientContextEventRequest.Builder()
@@ -44,6 +46,20 @@
                     .addEventType(AmbientContextEvent.EVENT_BACK_DOUBLE_TAP)
                     .build();
 
+    private static final int WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING =
+            AmbientContextEvent.EVENT_VENDOR_WEARABLE_START + 1;
+
+    private static final AmbientContextEventRequest WEARABLE_REQUEST =
+            new AmbientContextEventRequest.Builder()
+                    .addEventType(WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING)
+                    .build();
+
+    private static final AmbientContextEventRequest MIXED_REQUEST =
+            new AmbientContextEventRequest.Builder()
+                    .addEventType(AmbientContextEvent.EVENT_COUGH)
+                    .addEventType(WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING)
+                    .build();
+
     @NonNull
     private final AmbientContextManagerService mService;
 
@@ -106,16 +122,26 @@
         switch (cmd) {
             case "start-detection":
                 return runStartDetection();
+            case "start-detection-wearable":
+                return runWearableStartDetection();
+            case "start-detection-mixed":
+                return runMixedStartDetection();
             case "stop-detection":
                 return runStopDetection();
             case "get-last-status-code":
                 return getLastStatusCode();
             case "query-service-status":
                 return runQueryServiceStatus();
+            case "query-wearable-service-status":
+                return runQueryWearableServiceStatus();
+            case "query-mixed-service-status":
+                return runQueryMixedServiceStatus();
             case "get-bound-package":
                 return getBoundPackageName();
             case "set-temporary-service":
                 return setTemporaryService();
+            case "set-temporary-services":
+                return setTemporaryServices();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -127,6 +153,30 @@
         mService.startDetection(
                 userId, REQUEST, packageName,
                 sTestableCallbackInternal.createAmbientContextObserver());
+        mService.newClientAdded(userId, REQUEST, packageName,
+                sTestableCallbackInternal.createAmbientContextObserver());
+        return 0;
+    }
+
+    private int runWearableStartDetection() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        mService.startDetection(
+                userId, WEARABLE_REQUEST, packageName,
+                sTestableCallbackInternal.createAmbientContextObserver());
+        mService.newClientAdded(userId, WEARABLE_REQUEST, packageName,
+                sTestableCallbackInternal.createAmbientContextObserver());
+        return 0;
+    }
+
+    private int runMixedStartDetection() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        mService.startDetection(
+                userId, MIXED_REQUEST, packageName,
+                sTestableCallbackInternal.createAmbientContextObserver());
+        mService.newClientAdded(userId, MIXED_REQUEST, packageName,
+                sTestableCallbackInternal.createAmbientContextObserver());
         return 0;
     }
 
@@ -148,6 +198,26 @@
         return 0;
     }
 
+    private int runQueryWearableServiceStatus() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        int[] types = new int[] {WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING};
+        mService.queryServiceStatus(userId, packageName, types,
+                sTestableCallbackInternal.createRemoteStatusCallback());
+        return 0;
+    }
+
+    private int runQueryMixedServiceStatus() {
+        final int userId = Integer.parseInt(getNextArgRequired());
+        final String packageName = getNextArgRequired();
+        int[] types = new int[] {
+                AmbientContextEvent.EVENT_COUGH,
+                WEARABLE_AMBIENT_CONTEXT_EVENT_FOR_TESTING};
+        mService.queryServiceStatus(userId, packageName, types,
+                sTestableCallbackInternal.createRemoteStatusCallback());
+        return 0;
+    }
+
     private int getLastStatusCode() {
         final PrintWriter resultPrinter = getOutPrintWriter();
         int lastStatus = sTestableCallbackInternal.getLastStatus();
@@ -163,20 +233,33 @@
         pw.println("    Print this help text.");
         pw.println();
         pw.println("  start-detection USER_ID PACKAGE_NAME: Starts AmbientContextEvent detection.");
+        pw.println("  start-detection-wearable USER_ID PACKAGE_NAME: "
+                + "Starts AmbientContextEvent detection for wearable.");
+        pw.println("  start-detection-mixed USER_ID PACKAGE_NAME: "
+                + " Starts AmbientContextEvent detection for mixed events.");
         pw.println("  stop-detection USER_ID PACKAGE_NAME: Stops AmbientContextEvent detection.");
         pw.println("  get-last-status-code: Prints the latest request status code.");
         pw.println("  query-service-status USER_ID PACKAGE_NAME: Prints the service status code.");
+        pw.println("  query-wearable-service-status USER_ID PACKAGE_NAME: "
+                + "Prints the service status code for wearable.");
+        pw.println("  query-mixed-service-status USER_ID PACKAGE_NAME: "
+                + "Prints the service status code for mixed events.");
         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.");
+        pw.println("  set-temporary-services USER_ID "
+                + "[FIRST_PACKAGE_NAME] [SECOND_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 getBoundPackageName() {
         final PrintWriter resultPrinter = getOutPrintWriter();
         final int userId = Integer.parseInt(getNextArgRequired());
-        final ComponentName componentName = mService.getComponentName(userId);
+        final ComponentName componentName = mService.getComponentName(userId,
+                AmbientContextManagerPerUserService.ServiceType.DEFAULT);
         resultPrinter.println(componentName == null ? "" : componentName.getPackageName());
         return 0;
     }
@@ -188,6 +271,7 @@
         if (serviceName == null) {
             mService.resetTemporaryService(userId);
             out.println("AmbientContextDetectionService temporary reset. ");
+            mService.setDefaultServiceEnabled(userId, true);
             return 0;
         }
 
@@ -197,4 +281,30 @@
                 + " for " + duration + "ms");
         return 0;
     }
+
+    private int setTemporaryServices() {
+        String[] serviceNames = new String[2];
+        final PrintWriter out = getOutPrintWriter();
+        final int userId = Integer.parseInt(getNextArgRequired());
+        mService.setDefaultServiceEnabled(userId, false);
+        final String firstServiceName = getNextArg();
+        final String secondServiceName = getNextArg();
+        if (firstServiceName == null || secondServiceName == null) {
+            mService.resetTemporaryService(userId);
+            mService.setDefaultServiceEnabled(userId, true);
+            out.println("AmbientContextDetectionService temporary reset.");
+            return 0;
+        }
+        serviceNames[0] = firstServiceName;
+        serviceNames[1] = secondServiceName;
+        final int duration = Integer.parseInt(getNextArgRequired());
+        mService.setTemporaryServices(userId, serviceNames, duration);
+        Slog.w(TAG, "AmbientContextDetectionService temporarily set to " + serviceNames[0]
+                + " and " + serviceNames[1]
+                + " for " + duration + "ms");
+        out.println("AmbientContextDetectionService temporarily set to " + serviceNames[0]
+                + " and " + serviceNames[1]
+                + " for " + duration + "ms");
+        return 0;
+    }
 }
diff --git a/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java
new file mode 100644
index 0000000..3fb29a0
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/DefaultAmbientContextManagerPerUserService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ambientcontext;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.content.ComponentName;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Per-user manager service for {@link AmbientContextEvent}s.
+ */
+public class DefaultAmbientContextManagerPerUserService extends
+        AmbientContextManagerPerUserService {
+    private static final String TAG =
+            DefaultAmbientContextManagerPerUserService.class.getSimpleName();
+
+    @Nullable
+    @VisibleForTesting
+    DefaultRemoteAmbientContextDetectionService mRemoteService;
+
+    private ComponentName mComponentName;
+    private final ServiceType mServiceType;
+    private final String mServiceName;
+
+    DefaultAmbientContextManagerPerUserService(
+            @NonNull AmbientContextManagerService master, Object lock,
+            @UserIdInt int userId, ServiceType serviceType, String serviceName) {
+        super(master, lock, userId);
+        this.mServiceType = serviceType;
+        this.mServiceName = serviceName;
+        this.mComponentName = ComponentName.unflattenFromString(mServiceName);
+        Slog.d(TAG, "Created DefaultAmbientContextManagerPerUserService"
+                + "and service type: " + mServiceType.name() + " and service name: " + serviceName);
+    }
+
+
+    @GuardedBy("mLock")
+    @Override
+    protected void ensureRemoteServiceInitiated() {
+        if (mRemoteService == null) {
+            mRemoteService = new DefaultRemoteAmbientContextDetectionService(
+                    getContext(), mComponentName, getUserId());
+        }
+    }
+
+    @VisibleForTesting
+    @Override
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    protected void setComponentName(ComponentName componentName) {
+        this.mComponentName = componentName;
+    }
+
+    @Override
+    protected RemoteAmbientDetectionService getRemoteService() {
+        return mRemoteService;
+    }
+
+    @Override
+    protected String getProtectedBindPermission() {
+        return Manifest.permission.BIND_AMBIENT_CONTEXT_DETECTION_SERVICE;
+    }
+
+    @Override
+    public ServiceType getServiceType() {
+        return mServiceType;
+    }
+
+    @Override
+    protected int getAmbientContextPackageNameExtraKeyConfig() {
+        return com.android.internal.R.string.config_ambientContextPackageNameExtraKey;
+    }
+
+    @Override
+    protected int getAmbientContextEventArrayExtraKeyConfig() {
+        return com.android.internal.R.string.config_ambientContextEventArrayExtraKey;
+    }
+
+    @Override
+    protected int getConsentComponentConfig() {
+        return com.android.internal.R.string.config_defaultAmbientContextConsentComponent;
+    }
+
+    @Override
+    protected void clearRemoteService() {
+        mRemoteService = null;
+    }
+}
diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java
similarity index 78%
rename from services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
rename to services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java
index 8aec752..ebbc4d1 100644
--- a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
+++ b/services/core/java/com/android/server/ambientcontext/DefaultRemoteAmbientContextDetectionService.java
@@ -32,13 +32,16 @@
 
 import com.android.internal.infra.ServiceConnector;
 
-/** Manages the connection to the remote service. */
-final class RemoteAmbientContextDetectionService
-        extends ServiceConnector.Impl<IAmbientContextDetectionService> {
-    private static final String TAG =
-            RemoteAmbientContextDetectionService.class.getSimpleName();
+import java.io.PrintWriter;
 
-    RemoteAmbientContextDetectionService(Context context, ComponentName serviceName,
+/** Manages the connection to the remote service. */
+final class DefaultRemoteAmbientContextDetectionService
+        extends ServiceConnector.Impl<IAmbientContextDetectionService>
+        implements RemoteAmbientDetectionService {
+    private static final String TAG =
+            DefaultRemoteAmbientContextDetectionService.class.getSimpleName();
+
+    DefaultRemoteAmbientContextDetectionService(Context context, ComponentName serviceName,
             int userId) {
         super(context, new Intent(
                 AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName),
@@ -55,14 +58,7 @@
         return -1;
     }
 
-    /**
-     * Asks the implementation to start detection.
-     *
-     * @param request The request with events to detect, and optional detection options.
-     * @param packageName The app package that requested the detection
-     * @param detectionResultCallback callback for detection results
-     * @param statusCallback callback for service status
-     */
+    @Override
     public void startDetection(
             @NonNull AmbientContextEventRequest request, String packageName,
             RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
@@ -71,19 +67,13 @@
                 statusCallback));
     }
 
-    /**
-     * Asks the implementation to stop detection.
-     *
-     * @param packageName stop detection for the given package
-     */
+    @Override
     public void stopDetection(String packageName) {
         Slog.i(TAG, "Stop detection for " + packageName);
         post(service -> service.stopDetection(packageName));
     }
 
-    /**
-     * Asks the implementation to return the event status for the package.
-     */
+    @Override
     public void queryServiceStatus(
             @AmbientContextEvent.EventCode int[] eventTypes,
             String packageName,
@@ -91,4 +81,14 @@
         Slog.i(TAG, "Query status for " + packageName);
         post(service -> service.queryServiceStatus(eventTypes, packageName, callback));
     }
+
+    @Override
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        super.dump(prefix, pw);
+    }
+
+    @Override
+    public void unbind() {
+        super.unbind();
+    }
 }
diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java
new file mode 100644
index 0000000..802718d
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/RemoteAmbientDetectionService.java
@@ -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.server.ambientcontext;
+
+import android.annotation.NonNull;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.app.ambientcontext.AmbientContextEventRequest;
+import android.os.RemoteCallback;
+
+import java.io.PrintWriter;
+
+/**
+ * Interface for a remote service implementing Ambient Context Detection Service capabilities.
+ */
+interface RemoteAmbientDetectionService {
+    /**
+     * Asks the implementation to start detection.
+     *
+     * @param request The request with events to detect, and optional detection options.
+     * @param packageName The app package that requested the detection
+     * @param detectionResultCallback callback for detection results
+     * @param statusCallback callback for service status
+     */
+    void startDetection(
+            @NonNull AmbientContextEventRequest request, String packageName,
+            RemoteCallback detectionResultCallback, RemoteCallback statusCallback);
+
+    /**
+     * Asks the implementation to stop detection.
+     *
+     * @param packageName stop detection for the given package
+     */
+    void stopDetection(String packageName);
+
+    /**
+     * Asks the implementation to return the event status for the package.
+     */
+    void queryServiceStatus(
+            @AmbientContextEvent.EventCode int[] eventTypes,
+            String packageName,
+            RemoteCallback callback);
+
+    /**
+     * Dumps the RemoteAmbientDetectionService.
+     */
+    void dump(@NonNull String prefix, @NonNull PrintWriter pw);
+
+    /**
+     * Unbinds from the remote service.
+     */
+    void unbind();
+}
diff --git a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java b/services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.java
similarity index 65%
copy from services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
copy to services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.java
index 8aec752..3c6ff98 100644
--- a/services/core/java/com/android/server/ambientcontext/RemoteAmbientContextDetectionService.java
+++ b/services/core/java/com/android/server/ambientcontext/RemoteWearableSensingService.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.
@@ -26,24 +26,27 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.RemoteCallback;
-import android.service.ambientcontext.AmbientContextDetectionService;
-import android.service.ambientcontext.IAmbientContextDetectionService;
+import android.service.wearable.IWearableSensingService;
+import android.service.wearable.WearableSensingService;
 import android.util.Slog;
 
 import com.android.internal.infra.ServiceConnector;
 
-/** Manages the connection to the remote service. */
-final class RemoteAmbientContextDetectionService
-        extends ServiceConnector.Impl<IAmbientContextDetectionService> {
-    private static final String TAG =
-            RemoteAmbientContextDetectionService.class.getSimpleName();
+import java.io.PrintWriter;
 
-    RemoteAmbientContextDetectionService(Context context, ComponentName serviceName,
+/** Manages the connection to the remote wearable sensing service. */
+final class RemoteWearableSensingService
+        extends ServiceConnector.Impl<IWearableSensingService>
+        implements RemoteAmbientDetectionService {
+    private static final String TAG =
+            RemoteWearableSensingService.class.getSimpleName();
+
+    RemoteWearableSensingService(Context context, ComponentName serviceName,
             int userId) {
         super(context, new Intent(
-                AmbientContextDetectionService.SERVICE_INTERFACE).setComponent(serviceName),
+                        WearableSensingService.SERVICE_INTERFACE).setComponent(serviceName),
                 BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId,
-                IAmbientContextDetectionService.Stub::asInterface);
+                IWearableSensingService.Stub::asInterface);
 
         // Bind right away
         connect();
@@ -55,14 +58,7 @@
         return -1;
     }
 
-    /**
-     * Asks the implementation to start detection.
-     *
-     * @param request The request with events to detect, and optional detection options.
-     * @param packageName The app package that requested the detection
-     * @param detectionResultCallback callback for detection results
-     * @param statusCallback callback for service status
-     */
+    @Override
     public void startDetection(
             @NonNull AmbientContextEventRequest request, String packageName,
             RemoteCallback detectionResultCallback, RemoteCallback statusCallback) {
@@ -71,19 +67,13 @@
                 statusCallback));
     }
 
-    /**
-     * Asks the implementation to stop detection.
-     *
-     * @param packageName stop detection for the given package
-     */
+    @Override
     public void stopDetection(String packageName) {
         Slog.i(TAG, "Stop detection for " + packageName);
         post(service -> service.stopDetection(packageName));
     }
 
-    /**
-     * Asks the implementation to return the event status for the package.
-     */
+    @Override
     public void queryServiceStatus(
             @AmbientContextEvent.EventCode int[] eventTypes,
             String packageName,
@@ -91,4 +81,14 @@
         Slog.i(TAG, "Query status for " + packageName);
         post(service -> service.queryServiceStatus(eventTypes, packageName, callback));
     }
+
+    @Override
+    public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
+        super.dump(prefix, pw);
+    }
+
+    @Override
+    public void unbind() {
+        super.unbind();
+    }
 }
diff --git a/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java b/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java
new file mode 100644
index 0000000..36abd26
--- /dev/null
+++ b/services/core/java/com/android/server/ambientcontext/WearableAmbientContextManagerPerUserService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ambientcontext;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ambientcontext.AmbientContextEvent;
+import android.content.ComponentName;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Per-user manager service for {@link AmbientContextEvent}s for the Wearable Sensing.
+ */
+public class WearableAmbientContextManagerPerUserService extends
+        AmbientContextManagerPerUserService {
+    private static final String TAG =
+            WearableAmbientContextManagerPerUserService.class.getSimpleName();
+
+    @Nullable
+    @VisibleForTesting
+    RemoteWearableSensingService mRemoteService;
+
+    private ComponentName mComponentName;
+    private final ServiceType mServiceType;
+    private final String mServiceName;
+
+    WearableAmbientContextManagerPerUserService(
+            @NonNull AmbientContextManagerService master, Object lock,
+            @UserIdInt int userId, ServiceType serviceType, String serviceName) {
+        super(master, lock, userId);
+        this.mServiceType = serviceType;
+        this.mServiceName = serviceName;
+        this.mComponentName = ComponentName.unflattenFromString(mServiceName);
+        Slog.d(TAG, "Created WearableAmbientContextManagerPerUserService"
+                + "and service type: " + mServiceType.name() + " and service name: " + serviceName);
+    }
+
+    @GuardedBy("mLock")
+    @Override
+    protected void ensureRemoteServiceInitiated() {
+        if (mRemoteService == null) {
+            mRemoteService = new RemoteWearableSensingService(
+                    getContext(), mComponentName, getUserId());
+        }
+    }
+
+    @VisibleForTesting
+    @Override
+    ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    @Override
+    protected void setComponentName(ComponentName componentName) {
+        this.mComponentName = componentName;
+    }
+
+
+    @Override
+    protected RemoteAmbientDetectionService getRemoteService() {
+        return mRemoteService;
+    }
+
+    @Override
+    protected String getProtectedBindPermission() {
+        return Manifest.permission.BIND_WEARABLE_SENSING_SERVICE;
+    }
+
+    @Override
+    public ServiceType getServiceType() {
+        return mServiceType;
+    }
+
+    @Override
+    protected int getAmbientContextPackageNameExtraKeyConfig() {
+        return com.android.internal.R.string.config_wearableAmbientContextPackageNameExtraKey;
+    }
+
+    @Override
+    protected int getAmbientContextEventArrayExtraKeyConfig() {
+        return com.android.internal.R.string.config_wearableAmbientContextEventArrayExtraKey;
+    }
+
+    @Override
+    protected int getConsentComponentConfig() {
+        return com.android.internal.R.string.config_defaultWearableSensingConsentComponent;
+    }
+
+    @Override
+    protected void clearRemoteService() {
+        mRemoteService = null;
+    }
+}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 9cf4082..18839a8 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -161,6 +161,7 @@
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.component.ParsedAttribution;
 import com.android.server.policy.AppOpsPolicy;
 
@@ -3590,11 +3591,11 @@
      *
      * @return The restriction matching the package
      */
-    private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) {
-        return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(),
-                mContext.checkPermission(android.Manifest.permission
-                        .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid())
-                == PackageManager.PERMISSION_GRANTED);
+    private RestrictionBypass getBypassforPackage(@NonNull PackageState packageState) {
+        return new RestrictionBypass(packageState.getAppId() == Process.SYSTEM_UID,
+                packageState.isPrivileged(), mContext.checkPermission(
+                android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1,
+                packageState.getAppId()) == PackageManager.PERMISSION_GRANTED);
     }
 
     /**
@@ -3690,11 +3691,12 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-            AndroidPackage pkg = pmInt.getPackage(packageName);
+            var pkgState = pmInt.getPackageStateInternal(packageName);
+            var pkg = pkgState == null ? null : pkgState.getAndroidPackage();
             if (pkg != null) {
                 isAttributionTagValid = isAttributionInPackage(pkg, attributionTag);
-                pkgUid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.getUid()));
-                bypass = getBypassforPackage(pkg);
+                pkgUid = UserHandle.getUid(userId, pkgState.getAppId());
+                bypass = getBypassforPackage(pkgState);
             }
             if (!isAttributionTagValid) {
                 AndroidPackage proxyPkg = proxyPackageName != null
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 9b433cf..43c8032d 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -21,6 +21,7 @@
 import static android.media.AudioManager.RINGER_MODE_NORMAL;
 import static android.media.AudioManager.RINGER_MODE_SILENT;
 import static android.media.AudioManager.RINGER_MODE_VIBRATE;
+import static android.media.AudioManager.STREAM_MUSIC;
 import static android.media.AudioManager.STREAM_SYSTEM;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.INVALID_UID;
@@ -130,6 +131,8 @@
 import android.media.audiopolicy.AudioProductStrategy;
 import android.media.audiopolicy.AudioVolumeGroup;
 import android.media.audiopolicy.IAudioPolicyCallback;
+import android.media.permission.ClearCallingIdentityContext;
+import android.media.permission.SafeCloseable;
 import android.media.projection.IMediaProjection;
 import android.media.projection.IMediaProjectionCallback;
 import android.media.projection.IMediaProjectionManager;
@@ -382,6 +385,7 @@
     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;
+    private static final int MSG_LOWER_VOLUME_TO_RS1 = 53;
 
     /** Messages handled by the {@link SoundDoseHelper}. */
     /*package*/ static final int SAFE_MEDIA_VOLUME_MSG_START = 1000;
@@ -8580,6 +8584,10 @@
                     onDispatchPreferredMixerAttributesChanged(msg.getData(), msg.arg1);
                     break;
 
+                case MSG_LOWER_VOLUME_TO_RS1:
+                    onLowerVolumeToRs1();
+                    break;
+
                 default:
                     if (msg.what >= SAFE_MEDIA_VOLUME_MSG_START) {
                         // msg could be for the SoundDoseHelper
@@ -9787,6 +9795,38 @@
         mSoundDoseHelper.disableSafeMediaVolume(callingPackage);
     }
 
+    @Override
+    public void lowerVolumeToRs1(String callingPackage) {
+        enforceVolumeController("lowerVolumeToRs1");
+        postLowerVolumeToRs1();
+    }
+
+    /*package*/ void postLowerVolumeToRs1() {
+        sendMsg(mAudioHandler, MSG_LOWER_VOLUME_TO_RS1, SENDMSG_QUEUE,
+                // no params, no delay
+                0, 0, null, 0);
+    }
+
+    /**
+     * Called when handling MSG_LOWER_VOLUME_TO_RS1
+     */
+    private void onLowerVolumeToRs1() {
+        final ArrayList<AudioDeviceAttributes> devices = getDevicesForAttributesInt(
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build(), true);
+        final int nativeDeviceType;
+        final AudioDeviceAttributes ada;
+        if (devices.isEmpty()) {
+            ada = devices.get(0);
+            nativeDeviceType = ada.getInternalType();
+        } else {
+            nativeDeviceType = AudioSystem.DEVICE_OUT_USB_HEADSET;
+            ada = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_USB_HEADSET, "");
+        }
+        final int index = mSoundDoseHelper.safeMediaVolumeIndex(nativeDeviceType);
+        setStreamVolumeWithAttributionInt(STREAM_MUSIC, index, /*flags*/ 0, ada,
+                "com.android.server.audio", "AudioService");
+    }
+
     //==========================================================================================
     // Hdmi CEC:
     // - System audio mode:
@@ -10298,6 +10338,9 @@
     public interface ISafeHearingVolumeController {
         /** Displays an instructional safeguard as required by the safe hearing standard. */
         void postDisplaySafeVolumeWarning(int flags);
+
+        /** Displays a warning about transient exposure to high level playback */
+        void postDisplayCsdWarning(@AudioManager.CsdWarning int csdEvent, int displayDurationMs);
     }
 
     /** Wrapper which encapsulates the {@link IVolumeController} functionality. */
@@ -10400,6 +10443,20 @@
             }
         }
 
+        @Override
+        public void postDisplayCsdWarning(
+                @AudioManager.CsdWarning int csdWarning, int displayDurationMs) {
+            if (mController == null) {
+                Log.e(TAG, "Unable to display CSD warning, no controller");
+                return;
+            }
+            try {
+                mController.displayCsdWarning(csdWarning, displayDurationMs);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling displayCsdWarning for warning " + csdWarning, e);
+            }
+        }
+
         public void postVolumeChanged(int streamType, int flags) {
             if (mController == null)
                 return;
@@ -11164,6 +11221,34 @@
         mPrefMixerAttrDispatcher.finishBroadcast();
     }
 
+
+    /** @see AudioManager#supportsBluetoothVariableLatency() */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean supportsBluetoothVariableLatency() {
+        super.supportsBluetoothVariableLatency_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            return AudioSystem.supportsBluetoothVariableLatency();
+        }
+    }
+
+    /** @see AudioManager#setBluetoothVariableLatencyEnabled(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public void setBluetoothVariableLatencyEnabled(boolean enabled) {
+        super.setBluetoothVariableLatencyEnabled_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            AudioSystem.setBluetoothVariableLatencyEnabled(enabled);
+        }
+    }
+
+    /** @see AudioManager#isBluetoothVariableLatencyEnabled(boolean) */
+    @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isBluetoothVariableLatencyEnabled() {
+        super.isBluetoothVariableLatencyEnabled_enforcePermission();
+        try (SafeCloseable ignored = ClearCallingIdentityContext.create()) {
+            return AudioSystem.isBluetoothVariableLatencyEnabled();
+        }
+    }
+
     private final Object mExtVolumeControllerLock = new Object();
     private IAudioPolicyCallback mExtVolumeController;
     private void setExtVolumeController(IAudioPolicyCallback apc) {
@@ -11425,6 +11510,11 @@
             public void onCapturedContentResize(int width, int height) {
                 // Ignore resize of the captured content.
             }
+
+            @Override
+            public void onCapturedContentVisibilityChanged(boolean isVisible) {
+                // Ignore visibility changes of the captured content.
+            }
         };
         UnregisterOnStopCallback mProjectionCallback;
 
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index f35931ca..99d6228 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -35,6 +35,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
+import android.media.AudioPlaybackConfiguration.FormatInfo;
 import android.media.AudioPlaybackConfiguration.PlayerMuteEvent;
 import android.media.AudioSystem;
 import android.media.IPlaybackConfigDispatcher;
@@ -75,7 +76,7 @@
 public final class PlaybackActivityMonitor
         implements AudioPlaybackConfiguration.PlayerDeathMonitor, PlayerFocusEnforcer {
 
-    public static final String TAG = "AudioService.PlaybackActivityMonitor";
+    public static final String TAG = "AS.PlaybackActivityMon";
 
     /*package*/ static final boolean DEBUG = false;
     /*package*/ static final int VOLUME_SHAPER_SYSTEM_DUCK_ID = 1;
@@ -343,7 +344,7 @@
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
                 mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_L_UPDATE_PORT_EVENT, eventValue, piid));
+                        mEventHandler.obtainMessage(MSG_II_UPDATE_PORT_EVENT, eventValue, piid));
                 return;
             } else if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                 for (Integer uidInteger: mBannedUids) {
@@ -399,7 +400,7 @@
         }
 
         if (DEBUG) {
-            Log.v(TAG, TextUtils.formatSimple("portEvent(portId=%d, event=%s, extras=%s)",
+            Log.v(TAG, TextUtils.formatSimple("BLA portEvent(portId=%d, event=%s, extras=%s)",
                     portId, AudioPlaybackConfiguration.playerStateToString(event), extras));
         }
 
@@ -427,7 +428,12 @@
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED) {
                 mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_L_UPDATE_PLAYER_MUTED_EVENT, piid,
+                        mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_MUTED_EVENT, piid,
+                                portId,
+                                extras));
+            } else if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_FORMAT) {
+                mEventHandler.sendMessage(
+                        mEventHandler.obtainMessage(MSG_IIL_UPDATE_PLAYER_FORMAT, piid,
                                 portId,
                                 extras));
             }
@@ -457,7 +463,7 @@
 
                 // remove all port ids mapped to the released player
                 mEventHandler.sendMessage(
-                        mEventHandler.obtainMessage(MSG_L_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
+                        mEventHandler.obtainMessage(MSG_I_CLEAR_PORTS_FOR_PIID, piid, /*arg2=*/0));
 
                 if (change && mDoNotLogPiidList.contains(piid)) {
                     // do not dispatch a change for a "do not log" player
@@ -1352,6 +1358,21 @@
         }
     }
 
+    private static final class PlayerFormatEvent extends EventLogger.Event {
+        private final int mPlayerIId;
+        private final AudioPlaybackConfiguration.FormatInfo mFormat;
+
+        PlayerFormatEvent(int piid, AudioPlaybackConfiguration.FormatInfo format) {
+            mPlayerIId = piid;
+            mFormat = format;
+        }
+
+        @Override
+        public String eventToString() {
+            return new String("player piid:" + mPlayerIId + " format update:" + mFormat);
+        }
+    }
+
     static final EventLogger
             sEventLogger = new EventLogger(100,
             "playback activity as reported through PlayerBase");
@@ -1478,7 +1499,7 @@
      *     msg.arg1: port id
      *     msg.arg2: piid
      */
-    private static final int MSG_L_UPDATE_PORT_EVENT = 2;
+    private static final int MSG_II_UPDATE_PORT_EVENT = 2;
 
     /**
      * event for player getting muted
@@ -1488,14 +1509,24 @@
      *     msg.obj: extras describing the mute reason
      *         type: PersistableBundle
      */
-    private static final int MSG_L_UPDATE_PLAYER_MUTED_EVENT = 3;
+    private static final int MSG_IIL_UPDATE_PLAYER_MUTED_EVENT = 3;
 
     /**
      * clear all ports assigned to a given piid
      * args:
      *     msg.arg1: the piid
      */
-    private static final int MSG_L_CLEAR_PORTS_FOR_PIID = 4;
+    private static final int MSG_I_CLEAR_PORTS_FOR_PIID = 4;
+
+    /**
+     * event for player reporting playback format and spatialization status
+     * args:
+     *     msg.arg1: piid
+     *     msg.arg2: port id
+     *     msg.obj: extras describing the sample rate, channel mask, spatialized
+     *         type: PersistableBundle
+     */
+    private static final int MSG_IIL_UPDATE_PLAYER_FORMAT = 5;
 
     private void initEventHandler() {
         mEventThread = new HandlerThread(TAG);
@@ -1513,12 +1544,13 @@
                         }
                         mMuteAwaitConnectionTimeoutCb.accept((AudioDeviceAttributes) msg.obj);
                         break;
-                    case MSG_L_UPDATE_PORT_EVENT:
+
+                    case MSG_II_UPDATE_PORT_EVENT:
                         synchronized (mPlayerLock) {
                             mPortIdToPiid.put(/*portId*/msg.arg1, /*piid*/msg.arg2);
                         }
                         break;
-                    case MSG_L_UPDATE_PLAYER_MUTED_EVENT:
+                    case MSG_IIL_UPDATE_PLAYER_MUTED_EVENT:
                         // TODO: replace PersistableBundle with own struct
                         PersistableBundle extras = (PersistableBundle) msg.obj;
                         if (extras == null) {
@@ -1533,14 +1565,18 @@
                             sEventLogger.enqueue(
                                     new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
 
-                            final AudioPlaybackConfiguration apc = mPlayers.get(piid);
+                            final AudioPlaybackConfiguration apc;
+                            synchronized (mPlayerLock) {
+                                apc = mPlayers.get(piid);
+                            }
                             if (apc == null || !apc.handleMutedEvent(eventValue)) {
                                 break;  // do not dispatch
                             }
                             dispatchPlaybackChange(/* iplayerReleased= */false);
                         }
                         break;
-                    case MSG_L_CLEAR_PORTS_FOR_PIID:
+
+                    case MSG_I_CLEAR_PORTS_FOR_PIID:
                         int piid = msg.arg1;
                         if (piid == AudioPlaybackConfiguration.PLAYER_PIID_INVALID) {
                             Log.w(TAG, "Received clear ports with invalid piid");
@@ -1554,6 +1590,34 @@
                             }
                         }
                         break;
+
+                    case MSG_IIL_UPDATE_PLAYER_FORMAT:
+                        final PersistableBundle formatExtras = (PersistableBundle) msg.obj;
+                        if (formatExtras == null) {
+                            Log.w(TAG, "Received format event with no extras");
+                            break;
+                        }
+                        final boolean spatialized = formatExtras.getBoolean(
+                                AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SPATIALIZED, false);
+                        final int sampleRate = formatExtras.getInt(
+                                AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_SAMPLE_RATE, 0);
+                        final int nativeChannelMask = formatExtras.getInt(
+                                AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_CHANNEL_MASK, 0);
+                        final FormatInfo format =
+                                new FormatInfo(spatialized, nativeChannelMask, sampleRate);
+
+                        sEventLogger.enqueue(new PlayerFormatEvent(msg.arg1, format));
+
+                        final AudioPlaybackConfiguration apc;
+                        synchronized (mPlayerLock) {
+                            apc = mPlayers.get(msg.arg1);
+                        }
+                        if (apc == null || !apc.handleFormatEvent(format)) {
+                            break;  // do not dispatch
+                        }
+                        // TODO optimize for no dispatch to non-privileged listeners
+                        dispatchPlaybackChange(/* iplayerReleased= */false);
+                        break;
                     default:
                         break;
                 }
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 6279a4d..7edd911 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -26,6 +26,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.ISoundDose;
 import android.media.ISoundDoseCallback;
@@ -40,6 +41,7 @@
 import android.util.Log;
 import android.util.MathUtils;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.audio.AudioService.AudioHandler;
 import com.android.server.audio.AudioService.ISafeHearingVolumeController;
@@ -96,11 +98,19 @@
 
     private static final float CUSTOM_RS2_VALUE = 90;
 
+    // timeouts for the CSD warnings, -1 means no timeout (dialog must be ack'd by user)
+    private static final int CSD_WARNING_TIMEOUT_MS_DOSE_1X = 7000;
+    private static final int CSD_WARNING_TIMEOUT_MS_DOSE_5X = 5000;
+    private static final int CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START = -1;
+    private static final int CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE = 5000;
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
     private int mMcc = 0;
 
+    private final boolean mEnableCsd;
+
     final Object mSafeMediaVolumeStateLock = new Object();
     private int mSafeMediaVolumeState;
 
@@ -144,6 +154,8 @@
 
     private ISoundDose mSoundDose;
     private float mCurrentCsd = 0.f;
+    // dose at which the next dose reached warning occurs
+    private float mNextCsdWarning = 1.0f;
     private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
 
     private final Context mContext;
@@ -153,10 +165,42 @@
             Log.w(TAG, "DeviceId " + deviceId + " triggered momentary exposure with value: "
                     + currentMel);
             mLogger.enqueue(SoundDoseEvent.getMomentaryExposureEvent(currentMel));
+            if (mEnableCsd) {
+                mVolumeController.postDisplayCsdWarning(
+                        AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE,
+                        getTimeoutMsForWarning(AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE));
+            }
         }
 
         public void onNewCsdValue(float currentCsd, SoundDoseRecord[] records) {
             Log.i(TAG, "onNewCsdValue: " + currentCsd);
+            if (mCurrentCsd < currentCsd) {
+                // dose increase: going over next threshold?
+                if ((mCurrentCsd < mNextCsdWarning) && (currentCsd >= mNextCsdWarning)) {
+                    if (mEnableCsd) {
+                        if (mNextCsdWarning == 5.0f) {
+                            // 500% dose repeat
+                            mVolumeController.postDisplayCsdWarning(
+                                    AudioManager.CSD_WARNING_DOSE_REPEATED_5X,
+                                    getTimeoutMsForWarning(
+                                            AudioManager.CSD_WARNING_DOSE_REPEATED_5X));
+                            // on the 5x dose warning, the volume reduction happens right away
+                            mAudioService.postLowerVolumeToRs1();
+                        } else {
+                            mVolumeController.postDisplayCsdWarning(
+                                    AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+                                    getTimeoutMsForWarning(
+                                            AudioManager.CSD_WARNING_DOSE_REACHED_1X));
+                        }
+                    }
+                    mNextCsdWarning += 1.0f;
+                }
+            } else {
+                // dose decrease: dropping below previous threshold of warning?
+                if ((currentCsd < mNextCsdWarning - 1.0f) && (mNextCsdWarning >= 2.0f)) {
+                    mNextCsdWarning -= 1.0f;
+                }
+            }
             mCurrentCsd = currentCsd;
             mDoseRecords.addAll(Arrays.asList(records));
             long totalDuration = 0;
@@ -184,10 +228,12 @@
         mSafeMediaVolumeState = mSettings.getGlobalInt(audioService.getContentResolver(),
                 Settings.Global.AUDIO_SAFE_VOLUME_STATE, 0);
 
+        mEnableCsd = mContext.getResources().getBoolean(R.bool.config_audio_csd_enabled_default);
+
         // 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;
+                R.integer.config_safe_media_volume_index) * 10;
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
@@ -401,6 +447,7 @@
     }
 
     /*package*/ void dump(PrintWriter pw) {
+        pw.print("  mEnableCsd="); pw.println(mEnableCsd);
         pw.print("  mSafeMediaVolumeState=");
         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
@@ -491,6 +538,21 @@
         }
     }
 
+    private int getTimeoutMsForWarning(@AudioManager.CsdWarning int csdWarning) {
+        switch (csdWarning) {
+            case AudioManager.CSD_WARNING_DOSE_REACHED_1X:
+                return CSD_WARNING_TIMEOUT_MS_DOSE_1X;
+            case AudioManager.CSD_WARNING_DOSE_REPEATED_5X:
+                return CSD_WARNING_TIMEOUT_MS_DOSE_5X;
+            case AudioManager.CSD_WARNING_MOMENTARY_EXPOSURE:
+                return CSD_WARNING_TIMEOUT_MS_MOMENTARY_EXPOSURE;
+            case AudioManager.CSD_WARNING_ACCUMULATION_START:
+                return CSD_WARNING_TIMEOUT_MS_ACCUMULATION_START;
+        }
+        Log.e(TAG, "Invalid CSD warning " + csdWarning, new Exception());
+        return -1;
+    }
+
     @GuardedBy("mSafeMediaVolumeStateLock")
     private void setSafeMediaVolumeEnabled(boolean on, String caller) {
         if ((mSafeMediaVolumeState != SAFE_MEDIA_VOLUME_NOT_CONFIGURED) && (mSafeMediaVolumeState
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
index 3c0fda8..c0a238f 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java
@@ -307,6 +307,9 @@
     static ProgramIdentifier identifierToHalProgramIdentifier(ProgramSelector.Identifier id) {
         ProgramIdentifier hwId = new ProgramIdentifier();
         hwId.type = id.getType();
+        if (hwId.type == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+            hwId.type = IdentifierType.DAB_SID_EXT;
+        }
         hwId.value = id.getValue();
         return hwId;
     }
@@ -317,9 +320,49 @@
         if (id.type == IdentifierType.INVALID) {
             return null;
         }
-        return new ProgramSelector.Identifier(id.type, id.value);
+        int idType;
+        if (id.type == IdentifierType.DAB_SID_EXT) {
+            idType = ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT;
+        } else {
+            idType = id.type;
+        }
+        return new ProgramSelector.Identifier(idType, id.value);
     }
 
+    private static boolean isVendorIdentifierType(int idType) {
+        return idType >= IdentifierType.VENDOR_START && idType <= IdentifierType.VENDOR_END;
+    }
+
+    private static boolean isValidHalProgramSelector(
+            android.hardware.broadcastradio.ProgramSelector sel) {
+        if (sel.primaryId.type != IdentifierType.AMFM_FREQUENCY_KHZ
+                && sel.primaryId.type != IdentifierType.RDS_PI
+                && sel.primaryId.type != IdentifierType.HD_STATION_ID_EXT
+                && sel.primaryId.type != IdentifierType.DAB_SID_EXT
+                && sel.primaryId.type != IdentifierType.DRMO_SERVICE_ID
+                && sel.primaryId.type != IdentifierType.SXM_SERVICE_ID
+                && !isVendorIdentifierType(sel.primaryId.type)) {
+            return false;
+        }
+        if (sel.primaryId.type == IdentifierType.DAB_SID_EXT) {
+            boolean hasEnsemble = false;
+            boolean hasFrequency = false;
+            for (int i = 0; i < sel.secondaryIds.length; i++) {
+                if (sel.secondaryIds[i].type == IdentifierType.DAB_ENSEMBLE) {
+                    hasEnsemble = true;
+                } else if (sel.secondaryIds[i].type == IdentifierType.DAB_FREQUENCY_KHZ) {
+                    hasFrequency = true;
+                }
+                if (hasEnsemble && hasFrequency) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return true;
+    }
+
+    @Nullable
     static android.hardware.broadcastradio.ProgramSelector programSelectorToHalProgramSelector(
             ProgramSelector sel) {
         android.hardware.broadcastradio.ProgramSelector hwSel =
@@ -332,6 +375,9 @@
             secondaryIdList.add(identifierToHalProgramIdentifier(secondaryIds[i]));
         }
         hwSel.secondaryIds = secondaryIdList.toArray(ProgramIdentifier[]::new);
+        if (!isValidHalProgramSelector(hwSel)) {
+            return null;
+        }
         return hwSel;
     }
 
@@ -344,7 +390,7 @@
     @Nullable
     static ProgramSelector programSelectorFromHalProgramSelector(
             android.hardware.broadcastradio.ProgramSelector sel) {
-        if (isEmpty(sel)) {
+        if (isEmpty(sel) || !isValidHalProgramSelector(sel)) {
             return null;
         }
 
@@ -432,7 +478,34 @@
         return builder.build();
     }
 
+    private static boolean isValidLogicallyTunedTo(ProgramIdentifier id) {
+        return id.type == IdentifierType.AMFM_FREQUENCY_KHZ || id.type == IdentifierType.RDS_PI
+                || id.type == IdentifierType.HD_STATION_ID_EXT
+                || id.type == IdentifierType.DAB_SID_EXT
+                || id.type == IdentifierType.DRMO_SERVICE_ID
+                || id.type == IdentifierType.SXM_SERVICE_ID
+                || isVendorIdentifierType(id.type);
+    }
+
+    private static boolean isValidPhysicallyTunedTo(ProgramIdentifier id) {
+        return id.type == IdentifierType.AMFM_FREQUENCY_KHZ
+                || id.type == IdentifierType.DAB_FREQUENCY_KHZ
+                || id.type == IdentifierType.DRMO_FREQUENCY_KHZ
+                || id.type == IdentifierType.SXM_CHANNEL
+                || isVendorIdentifierType(id.type);
+    }
+
+    private static boolean isValidHalProgramInfo(ProgramInfo info) {
+        return isValidHalProgramSelector(info.selector)
+                && isValidLogicallyTunedTo(info.logicallyTunedTo)
+                && isValidPhysicallyTunedTo(info.physicallyTunedTo);
+    }
+
+    @Nullable
     static RadioManager.ProgramInfo programInfoFromHalProgramInfo(ProgramInfo info) {
+        if (!isValidHalProgramInfo(info)) {
+            return null;
+        }
         Collection<ProgramSelector.Identifier> relatedContent = new ArrayList<>();
         if (info.relatedContent != null) {
             for (int i = 0; i < info.relatedContent.length; i++) {
@@ -485,7 +558,14 @@
     static ProgramList.Chunk chunkFromHalProgramListChunk(ProgramListChunk chunk) {
         Set<RadioManager.ProgramInfo> modified = new ArraySet<>(chunk.modified.length);
         for (int i = 0; i < chunk.modified.length; i++) {
-            modified.add(programInfoFromHalProgramInfo(chunk.modified[i]));
+            RadioManager.ProgramInfo modifiedInfo =
+                    programInfoFromHalProgramInfo(chunk.modified[i]);
+            if (modifiedInfo == null) {
+                Slogf.w(TAG, "Program info %s in program list chunk is not valid",
+                        chunk.modified[i]);
+                continue;
+            }
+            modified.add(modifiedInfo);
         }
         Set<ProgramSelector.Identifier> removed = new ArraySet<>();
         if (chunk.removed != null) {
@@ -547,10 +627,22 @@
         if (isAtLeastU(targetSdkVersion)) {
             return chunk;
         }
-        Set<RadioManager.ProgramInfo> modified = chunk.getModified();
-        modified.removeIf(info -> !programInfoMeetsSdkVersionRequirement(info, targetSdkVersion));
-        Set<ProgramSelector.Identifier> removed = chunk.getRemoved();
-        removed.removeIf(id -> isNewIdentifierInU(id));
+        Set<RadioManager.ProgramInfo> modified = new ArraySet<>();
+        Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator();
+        while (modifiedIterator.hasNext()) {
+            RadioManager.ProgramInfo info = modifiedIterator.next();
+            if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) {
+                modified.add(info);
+            }
+        }
+        Set<ProgramSelector.Identifier> removed = new ArraySet<>();
+        Iterator<ProgramSelector.Identifier> removedIterator = chunk.getRemoved().iterator();
+        while (removedIterator.hasNext()) {
+            ProgramSelector.Identifier id = removedIterator.next();
+            if (!isNewIdentifierInU(id)) {
+                removed.add(id);
+            }
+        }
         return new ProgramList.Chunk(chunk.isPurge(), chunk.isComplete(), modified, removed);
     }
 
@@ -558,7 +650,7 @@
             Announcement hwAnnouncement) {
         return new android.hardware.radio.Announcement(
                 Objects.requireNonNull(programSelectorFromHalProgramSelector(
-                        hwAnnouncement.selector)),
+                        hwAnnouncement.selector), "Program selector can not be null"),
                 hwAnnouncement.type,
                 vendorInfoFromHalVendorKeyValues(hwAnnouncement.vendorInfo)
         );
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
index 095a5fa..39b1354 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/ProgramInfoCache.java
@@ -24,6 +24,7 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -37,6 +38,7 @@
  */
 final class ProgramInfoCache {
 
+    private static final String TAG = "BcRadioAidlSrv.cache";
     /**
      * Maximum number of {@link RadioManager#ProgramInfo} elements that will be put into a
      * ProgramList.Chunk.mModified array. Used to try to ensure a single ProgramList.Chunk
@@ -124,6 +126,10 @@
         for (int i = 0; i < chunk.modified.length; i++) {
             RadioManager.ProgramInfo programInfo =
                     ConversionUtils.programInfoFromHalProgramInfo(chunk.modified[i]);
+            if (programInfo == null) {
+                Slogf.e(TAG, "Program info in program info %s in chunk is not valid",
+                        chunk.modified[i]);
+            }
             mProgramInfoMap.put(programInfo.getSelector().getPrimaryId(), programInfo);
         }
         if (chunk.removed != null) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
index 6193f23..f8c19ae 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java
@@ -97,10 +97,10 @@
 
         public void onTuneFailed(int result, ProgramSelector programSelector) {
             fireLater(() -> {
+                android.hardware.radio.ProgramSelector csel =
+                        ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
+                int tunerResult = ConversionUtils.halResultToTunerResult(result);
                 synchronized (mLock) {
-                    android.hardware.radio.ProgramSelector csel =
-                            ConversionUtils.programSelectorFromHalProgramSelector(programSelector);
-                    int tunerResult = ConversionUtils.halResultToTunerResult(result);
                     fanoutAidlCallbackLocked((cb, sdkVersion) -> {
                         if (csel != null && !ConversionUtils
                                 .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) {
@@ -117,10 +117,12 @@
         @Override
         public void onCurrentProgramInfoChanged(ProgramInfo halProgramInfo) {
             fireLater(() -> {
+                RadioManager.ProgramInfo currentProgramInfo =
+                        ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
+                Objects.requireNonNull(currentProgramInfo,
+                        "Program info from AIDL HAL is invalid");
                 synchronized (mLock) {
-                    mCurrentProgramInfo =
-                            ConversionUtils.programInfoFromHalProgramInfo(halProgramInfo);
-                    RadioManager.ProgramInfo currentProgramInfo = mCurrentProgramInfo;
+                    mCurrentProgramInfo = currentProgramInfo;
                     fanoutAidlCallbackLocked((cb, sdkVersion) -> {
                         if (!ConversionUtils.programInfoMeetsSdkVersionRequirement(
                                 currentProgramInfo, sdkVersion)) {
diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
index d700ed0..fe8c238 100644
--- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
+++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java
@@ -203,10 +203,15 @@
             Slogf.w(TAG, "Cannot tune on AIDL HAL client from non-current user");
             return;
         }
+        android.hardware.broadcastradio.ProgramSelector hwSel =
+                ConversionUtils.programSelectorToHalProgramSelector(selector);
+        if (hwSel == null) {
+            throw new IllegalArgumentException("tune: INVALID_ARGUMENTS for program selector");
+        }
         synchronized (mLock) {
             checkNotClosedLocked();
             try {
-                mService.tune(ConversionUtils.programSelectorToHalProgramSelector(selector));
+                mService.tune(hwSel);
             } catch (RuntimeException ex) {
                 throw ConversionUtils.throwOnError(ex, /* action= */ "tune");
             }
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index e16ca0b..0b03005 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -74,6 +74,7 @@
 import android.view.WindowManagerGlobal;
 
 import com.android.framework.protobuf.nano.MessageNano;
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -389,6 +390,16 @@
             return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
         }
 
+        // When config_isWindowManagerCameraCompatTreatmentEnabled is true,
+        // DisplayRotationCompatPolicy in WindowManager force rotates fullscreen activities with
+        // fixed orientation to align them with the natural orientation of the device.
+        if (ctx.getResources().getBoolean(
+                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled)) {
+            Slog.v(TAG, "Disable Rotate and Crop to avoid conflicts with"
+                    + " WM force rotation treatment.");
+            return CaptureRequest.SCALER_ROTATE_AND_CROP_NONE;
+        }
+
         // External cameras do not need crop-rotate-scale.
         if (lensFacing != CameraMetadata.LENS_FACING_FRONT
                 && lensFacing != CameraMetadata.LENS_FACING_BACK) {
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 09bec5e..e3ea1a6 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -17,7 +17,9 @@
 package com.android.server.companion.virtual;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.companion.virtual.IVirtualDevice;
+import android.os.LocaleList;
 
 import java.util.Set;
 
@@ -111,6 +113,20 @@
     public abstract int getBaseVirtualDisplayFlags(IVirtualDevice virtualDevice);
 
     /**
+     * Returns the preferred locale hints of the Virtual Device on which the given app is running,
+     * or {@code null} if the hosting virtual device doesn't have a virtual keyboard or the app is
+     * not on any virtual device.
+     *
+     * If an app is on multiple virtual devices, the locale of the virtual device created the
+     * earliest will be returned.
+     *
+     * See {@link android.hardware.input.VirtualKeyboardConfig#setLanguageTag() for how the locale
+     * is specified for virtual keyboard.
+     */
+    @Nullable
+    public abstract LocaleList getPreferredLocaleListForUid(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.
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 19dbee7..c15e419 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -140,6 +140,7 @@
 import com.android.internal.net.VpnConfig;
 import com.android.internal.net.VpnProfile;
 import com.android.modules.utils.build.SdkLevel;
+import com.android.net.module.util.BinderUtils;
 import com.android.net.module.util.NetdUtils;
 import com.android.net.module.util.NetworkStackConstants;
 import com.android.server.DeviceIdleInternal;
@@ -2783,6 +2784,16 @@
         return hasIPV6 && !hasIPV4;
     }
 
+    private void setVpnNetworkPreference(String session, Set<Range<Integer>> ranges) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, ranges));
+    }
+
+    private void clearVpnNetworkPreference(String session) {
+        BinderUtils.withCleanCallingIdentity(
+                () -> mConnectivityManager.setVpnDefaultForUids(session, Collections.EMPTY_LIST));
+    }
+
     /**
      * Internal class managing IKEv2/IPsec VPN connectivity
      *
@@ -2894,6 +2905,9 @@
                     (r, exe) -> {
                         Log.d(TAG, "Runnable " + r + " rejected by the mExecutor");
                     });
+            setVpnNetworkPreference(mSessionKey,
+                    createUserAndRestrictedProfilesRanges(mUserId,
+                            mConfig.allowedApplications, mConfig.disallowedApplications));
         }
 
         @Override
@@ -3047,7 +3061,6 @@
                     mConfig.dnsServers.addAll(dnsAddrStrings);
 
                     mConfig.underlyingNetworks = new Network[] {network};
-                    mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
                     networkAgent = mNetworkAgent;
 
@@ -3750,6 +3763,7 @@
             mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
             mConnectivityDiagnosticsManager.unregisterConnectivityDiagnosticsCallback(
                     mDiagnosticsCallback);
+            clearVpnNetworkPreference(mSessionKey);
 
             mExecutor.shutdown();
         }
@@ -4310,6 +4324,7 @@
             mConfig.requiresInternetValidation = profile.requiresInternetValidation;
             mConfig.excludeLocalRoutes = profile.excludeLocalRoutes;
             mConfig.allowBypass = profile.isBypassable;
+            mConfig.disallowedApplications = getAppExclusionList(mPackage);
 
             switch (profile.type) {
                 case VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS:
@@ -4462,6 +4477,9 @@
                         .setUids(createUserAndRestrictedProfilesRanges(
                                 mUserId, null /* allowedApplications */, excludedApps))
                         .build();
+                setVpnNetworkPreference(getSessionKeyLocked(),
+                        createUserAndRestrictedProfilesRanges(mUserId,
+                                mConfig.allowedApplications, mConfig.disallowedApplications));
                 doSendNetworkCapabilities(mNetworkAgent, mNetworkCapabilities);
             }
         }
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 1217e74..e557b50 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -2630,6 +2630,9 @@
         }
     }
 
+    /**
+     * Uniquely identifies a Sensor, with the combination of Type and Name.
+     */
     static class SensorData {
         public String type;
         public String name;
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index fe1d1a6..b7b7031 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -180,6 +180,15 @@
     public static final int FLAG_DEVICE_DISPLAY_GROUP = 1 << 18;
 
     /**
+     * Flag: Indicates that the display should not become the top focused display by stealing the
+     * top focus from another display.
+     *
+     * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+     * @hide
+     */
+    public static final int FLAG_STEAL_TOP_FOCUS_DISABLED = 1 << 19;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -649,6 +658,9 @@
         if ((flags & FLAG_OWN_FOCUS) != 0) {
             msg.append(", FLAG_OWN_FOCUS");
         }
+        if ((flags & FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+            msg.append(", FLAG_STEAL_TOP_FOCUS_DISABLED");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index 5dba015..f8d6c5f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -50,7 +50,6 @@
 import android.provider.DeviceConfigInterface;
 import android.provider.Settings;
 import android.sysprop.SurfaceFlingerProperties;
-import android.text.TextUtils;
 import android.util.IndentingPrintWriter;
 import android.util.Pair;
 import android.util.Slog;
@@ -70,6 +69,7 @@
 import com.android.server.LocalServices;
 import com.android.server.display.utils.AmbientFilter;
 import com.android.server.display.utils.AmbientFilterFactory;
+import com.android.server.display.utils.SensorUtils;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.sensors.SensorManagerInternal.ProximityActiveListener;
 import com.android.server.statusbar.StatusBarManagerInternal;
@@ -683,14 +683,20 @@
     }
 
     /**
-     * A utility to make this class aware of the new display configs whenever the default display is
-     * changed
+     * Called when the underlying display device of the default display is changed.
+     * Some data in this class relates to the physical display of the device, and so we need to
+     * reload the configurations based on this.
+     * E.g. the brightness sensors and refresh rate capabilities depend on the physical display
+     * device that is being used, so will be reloaded.
+     *
+     * @param displayDeviceConfig configurations relating to the underlying display device.
      */
     public void defaultDisplayDeviceUpdated(DisplayDeviceConfig displayDeviceConfig) {
         mSettingsObserver.setRefreshRates(displayDeviceConfig,
             /* attemptLoadingFromDeviceConfig= */ true);
         mBrightnessObserver.updateBlockingZoneThresholds(displayDeviceConfig,
             /* attemptLoadingFromDeviceConfig= */ true);
+        mBrightnessObserver.reloadLightSensor(displayDeviceConfig);
     }
 
     /**
@@ -1739,6 +1745,9 @@
 
         private SensorManager mSensorManager;
         private Sensor mLightSensor;
+        private Sensor mRegisteredLightSensor;
+        private String mLightSensorType;
+        private String mLightSensorName;
         private final LightSensorEventListener mLightSensorListener =
                 new LightSensorEventListener();
         // Take it as low brightness before valid sensor data comes
@@ -1899,17 +1908,8 @@
             return mLowAmbientBrightnessThresholds;
         }
 
-        public void registerLightSensor(SensorManager sensorManager, Sensor lightSensor) {
-            mSensorManager = sensorManager;
-            mLightSensor = lightSensor;
-
-            mSensorManager.registerListener(mLightSensorListener,
-                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
-        }
-
         public void observe(SensorManager sensorManager) {
             mSensorManager = sensorManager;
-            final ContentResolver cr = mContext.getContentResolver();
             mBrightness = getBrightness(Display.DEFAULT_DISPLAY);
 
             // DeviceConfig is accessible after system ready.
@@ -2053,6 +2053,10 @@
                 pw.println("    mAmbientHighBrightnessThresholds: " + d);
             }
 
+            pw.println("    mRegisteredLightSensor: " + mRegisteredLightSensor);
+            pw.println("    mLightSensor: " + mLightSensor);
+            pw.println("    mLightSensorName: " + mLightSensorName);
+            pw.println("    mLightSensorType: " + mLightSensorType);
             mLightSensorListener.dumpLocked(pw);
 
             if (mAmbientFilter != null) {
@@ -2106,27 +2110,9 @@
             }
 
             if (mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange) {
-                Resources resources = mContext.getResources();
-                String lightSensorType = resources.getString(
-                        com.android.internal.R.string.config_displayLightSensorType);
+                Sensor lightSensor = getLightSensor();
 
-                Sensor lightSensor = null;
-                if (!TextUtils.isEmpty(lightSensorType)) {
-                    List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
-                    for (int i = 0; i < sensors.size(); i++) {
-                        Sensor sensor = sensors.get(i);
-                        if (lightSensorType.equals(sensor.getStringType())) {
-                            lightSensor = sensor;
-                            break;
-                        }
-                    }
-                }
-
-                if (lightSensor == null) {
-                    lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
-                }
-
-                if (lightSensor != null) {
+                if (lightSensor != null && lightSensor != mLightSensor) {
                     final Resources res = mContext.getResources();
 
                     mAmbientFilter = AmbientFilterFactory.createBrightnessFilter(TAG, res);
@@ -2137,14 +2123,40 @@
                 mLightSensor = null;
             }
 
+            updateSensorStatus();
             if (mRefreshRateChangeable) {
-                updateSensorStatus();
                 synchronized (mLock) {
                     onBrightnessChangedLocked();
                 }
             }
         }
 
+        private void reloadLightSensor(DisplayDeviceConfig displayDeviceConfig) {
+            reloadLightSensorData(displayDeviceConfig);
+            restartObserver();
+        }
+
+        private void reloadLightSensorData(DisplayDeviceConfig displayDeviceConfig) {
+            // The displayDeviceConfig (ddc) contains display specific preferences. When loaded,
+            // it naturally falls back to the global config.xml.
+            if (displayDeviceConfig != null
+                    && displayDeviceConfig.getAmbientLightSensor() != null) {
+                // This covers both the ddc and the config.xml fallback
+                mLightSensorType = displayDeviceConfig.getAmbientLightSensor().type;
+                mLightSensorName = displayDeviceConfig.getAmbientLightSensor().name;
+            } else if (mLightSensorName == null && mLightSensorType == null) {
+                Resources resources = mContext.getResources();
+                mLightSensorType = resources.getString(
+                        com.android.internal.R.string.config_displayLightSensorType);
+                mLightSensorName = "";
+            }
+        }
+
+        private Sensor getLightSensor() {
+            return SensorUtils.findSensor(mSensorManager, mLightSensorType,
+                    mLightSensorName, Sensor.TYPE_LIGHT);
+        }
+
         /**
          * Checks to see if at least one value is positive, in which case it is necessary to listen
          * to value changes.
@@ -2288,17 +2300,36 @@
 
             if ((mShouldObserveAmbientLowChange || mShouldObserveAmbientHighChange)
                      && isDeviceActive() && !mLowPowerModeEnabled && mRefreshRateChangeable) {
-                mSensorManager.registerListener(mLightSensorListener,
-                        mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
-                if (mLoggingEnabled) {
-                    Slog.d(TAG, "updateSensorStatus: registerListener");
-                }
+                registerLightSensor();
+
             } else {
-                mLightSensorListener.removeCallbacks();
-                mSensorManager.unregisterListener(mLightSensorListener);
-                if (mLoggingEnabled) {
-                    Slog.d(TAG, "updateSensorStatus: unregisterListener");
-                }
+                unregisterSensorListener();
+            }
+        }
+
+        private void registerLightSensor() {
+            if (mRegisteredLightSensor == mLightSensor) {
+                return;
+            }
+
+            if (mRegisteredLightSensor != null) {
+                unregisterSensorListener();
+            }
+
+            mSensorManager.registerListener(mLightSensorListener,
+                    mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
+            mRegisteredLightSensor = mLightSensor;
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: registerListener");
+            }
+        }
+
+        private void unregisterSensorListener() {
+            mLightSensorListener.removeCallbacks();
+            mSensorManager.unregisterListener(mLightSensorListener);
+            mRegisteredLightSensor = null;
+            if (mLoggingEnabled) {
+                Slog.d(TAG, "updateSensorStatus: unregisterListener");
             }
         }
 
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index ad426b5..dfdbce5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -361,6 +361,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_OWN_FOCUS) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_OWN_FOCUS;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_STEAL_TOP_FOCUS_DISABLED;
+            }
             Rect maskingInsets = getMaskingInsets(deviceInfo);
             int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
             int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 7c647cf..ddeaa1b 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -28,6 +28,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED;
@@ -35,6 +36,7 @@
 import static com.android.server.display.DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_DEVICE_DISPLAY_GROUP;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP;
+import static com.android.server.display.DisplayDeviceInfo.FLAG_STEAL_TOP_FOCUS_DISABLED;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED;
 import static com.android.server.display.DisplayDeviceInfo.FLAG_TRUSTED;
 
@@ -526,6 +528,17 @@
                                 + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
                     }
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED) != 0) {
+                    if ((mFlags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0
+                            && (mFlags & VIRTUAL_DISPLAY_FLAG_OWN_FOCUS) != 0) {
+                        mInfo.flags |= FLAG_STEAL_TOP_FOCUS_DISABLED;
+                    } else {
+                        Slog.w(TAG,
+                                "Ignoring VIRTUAL_DISPLAY_FLAG_STEAL_TOP_FOCUS_DISABLED as it "
+                                        + "requires VIRTUAL_DISPLAY_FLAG_OWN_FOCUS which requires "
+                                        + "VIRTUAL_DISPLAY_FLAG_TRUSTED.");
+                    }
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
@@ -608,6 +621,13 @@
             // expect), and there will still be letterboxing on the output content since the
             // Surface and VirtualDisplay would then have different aspect ratios.
         }
+
+        @Override
+        public void onCapturedContentVisibilityChanged(boolean isVisible) {
+            // Do nothing when we tell the client that the content has a visibility change - it is
+            // up to them to decide to pause recording, and update their own UI, depending on their
+            // use case.
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/utils/SensorUtils.java b/services/core/java/com/android/server/display/utils/SensorUtils.java
index 4924ad5..48bc46c 100644
--- a/services/core/java/com/android/server/display/utils/SensorUtils.java
+++ b/services/core/java/com/android/server/display/utils/SensorUtils.java
@@ -33,6 +33,10 @@
      */
     public static Sensor findSensor(SensorManager sensorManager, String sensorType,
             String sensorName, int fallbackType) {
+        if (sensorManager == null) {
+            return null;
+        }
+
         if ("".equals(sensorName) && "".equals(sensorType)) {
             return null;
         }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d510523..c47d749 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -149,7 +149,8 @@
             new ActivityInterceptorCallback() {
                 @Nullable
                 @Override
-                public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                        ActivityInterceptorInfo info) {
                     return null;
                 }
 
diff --git a/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
new file mode 100644
index 0000000..11a4294
--- /dev/null
+++ b/services/core/java/com/android/server/grammaticalinflection/GrammaticalInflectionService.java
@@ -0,0 +1,76 @@
+/*
+ * 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.grammaticalinflection;
+
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
+import android.app.IGrammaticalInflectionManager;
+import android.content.Context;
+import android.os.IBinder;
+
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * The implementation of IGrammaticalInflectionManager.aidl.
+ *
+ * <p>This service is API entry point for storing app-specific grammatical inflection.
+ */
+public class GrammaticalInflectionService extends SystemService {
+
+    private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
+
+    /**
+     * Initializes the system service.
+     * <p>
+     * Subclasses must define a single argument constructor that accepts the context
+     * and passes it to super.
+     * </p>
+     *
+     * @param context The system server context.
+     *
+     * @hide
+     */
+    public GrammaticalInflectionService(Context context) {
+        super(context);
+        mActivityTaskManagerInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
+    }
+
+    @Override
+    public void onStart() {
+        publishBinderService(Context.GRAMMATICAL_INFLECTION_SERVICE, mService);
+    }
+
+    private final IBinder mService = new IGrammaticalInflectionManager.Stub() {
+        @Override
+        public void setRequestedApplicationGrammaticalGender(
+                String appPackageName, int userId, int gender) {
+            GrammaticalInflectionService.this.setRequestedApplicationGrammaticalGender(
+                    appPackageName, userId, gender);
+        }
+    };
+
+    private void setRequestedApplicationGrammaticalGender(
+            String appPackageName, int userId, int gender) {
+        final ActivityTaskManagerInternal.PackageConfigurationUpdater updater =
+                mActivityTaskManagerInternal.createPackageConfigurationUpdater(appPackageName,
+                        userId);
+
+        updater.setGrammaticalGender(gender).commit();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index aaa9ee5..5353092 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -599,6 +599,26 @@
     })
     @interface RcProfileSource {}
 
+    static final int HDMI_EARC_STATUS_IDLE = 0;             // IDLE1
+    static final int HDMI_EARC_STATUS_EARC_PENDING = 1;     // DISC1 and DISC2
+    static final int HDMI_EARC_STATUS_ARC_PENDING = 2;      // IDLE2 for ARC
+    static final int HDMI_EARC_STATUS_EARC_CONNECTED = 3;   // eARC connected
+    @IntDef({
+            HDMI_EARC_STATUS_IDLE,
+            HDMI_EARC_STATUS_EARC_PENDING,
+            HDMI_EARC_STATUS_ARC_PENDING,
+            HDMI_EARC_STATUS_EARC_CONNECTED
+            })
+    @interface EarcStatus {}
+
+    static final int HDMI_HPD_TYPE_PHYSICAL = 0;   // Default. Physical hotplug signal.
+    static final int HDMI_HPD_TYPE_STATUS_BIT = 1; // HDMI_HPD status bit.
+    @IntDef({
+            HDMI_HPD_TYPE_PHYSICAL,
+            HDMI_HPD_TYPE_STATUS_BIT
+    })
+    @interface HpdSignalType {}
+
     private Constants() {
         /* cannot be instantiated */
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index f3c67fb9..2e73a41 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -70,6 +70,10 @@
  * <p>It can be created only by {@link HdmiCecController#create}
  *
  * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+ *
+ * <p>Also manages HDMI HAL methods that are shared between CEC and eARC. To make eARC
+ * fully independent of the presence of a CEC HAL, we should split this class into HdmiCecController
+ * and HdmiController TODO(b/255751565).
  */
 final class HdmiCecController {
     private static final String TAG = "HdmiCecController";
@@ -413,6 +417,31 @@
     }
 
     /**
+     * Configures the type of HDP signal that the driver and HAL use for actions other than eARC,
+     * such as signaling EDID updates.
+     */
+    @ServiceThreadOnly
+    void setHpdSignalType(@Constants.HpdSignalType int signal, int portId) {
+        assertRunOnServiceThread();
+        // Stub.
+        // TODO: bind to native.
+        // TODO: handle error return values here, with logging.
+    }
+
+    /**
+     * Gets the type of the HDP signal that the driver and HAL use for actions other than eARC,
+     * such as signaling EDID updates.
+     */
+    @ServiceThreadOnly
+    @Constants.HpdSignalType
+    int getHpdSignalType(int portId) {
+        assertRunOnServiceThread();
+        // Stub.
+        // TODO: bind to native.
+        return Constants.HDMI_HPD_TYPE_PHYSICAL;
+    }
+
+    /**
      * Informs CEC HAL about the current system language.
      *
      * @param language Three-letter code defined in ISO/FDIS 639-2. Must be lowercase letters.
@@ -1066,6 +1095,8 @@
                 HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[hdmiPortInfos.length];
                 int i = 0;
                 for (android.hardware.tv.hdmi.HdmiPortInfo portInfo : hdmiPortInfos) {
+                    // TODO: the earc argument is stubbed for now.
+                    // To be replaced by portInfo.earcSupported.
                     hdmiPortInfo[i] =
                             new HdmiPortInfo(
                                     portInfo.portId,
@@ -1073,7 +1104,8 @@
                                     portInfo.physicalAddress,
                                     portInfo.cecSupported,
                                     false,
-                                    portInfo.arcSupported);
+                                    portInfo.arcSupported,
+                                    false);
                     i++;
                 }
                 return hdmiPortInfo;
@@ -1234,7 +1266,8 @@
                             portInfo.physicalAddress,
                             portInfo.cecSupported,
                             false,
-                            portInfo.arcSupported);
+                            portInfo.arcSupported,
+                            false);
                     i++;
                 }
                 return hdmiPortInfo;
@@ -1415,7 +1448,8 @@
                             portInfo.physicalAddress,
                             portInfo.cecSupported,
                             false,
-                            portInfo.arcSupported);
+                            portInfo.arcSupported,
+                            false);
                     i++;
                 }
                 return hdmiPortInfo;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index b4d7fb9..91f58db 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -54,7 +54,7 @@
  * Class that models a logical CEC device hosted in this system. Handles initialization, CEC
  * commands that call for actions customized per device type.
  */
-abstract class HdmiCecLocalDevice {
+abstract class HdmiCecLocalDevice extends HdmiLocalDevice {
     private static final String TAG = "HdmiCecLocalDevice";
 
     private static final int MAX_HDMI_ACTIVE_SOURCE_HISTORY = 10;
@@ -67,8 +67,6 @@
     // When it expires, we can assume <User Control Release> is received.
     private static final int FOLLOWER_SAFETY_TIMEOUT = 550;
 
-    protected final HdmiControlService mService;
-    protected final int mDeviceType;
     protected int mPreferredAddress;
     @GuardedBy("mLock")
     private HdmiDeviceInfo mDeviceInfo;
@@ -154,8 +152,6 @@
     private int mActiveRoutingPath;
 
     protected final HdmiCecMessageCache mCecMessageCache = new HdmiCecMessageCache();
-    @VisibleForTesting
-    protected final Object mLock;
 
     // A collection of FeatureAction.
     // Note that access to this collection should happen in service thread.
@@ -188,9 +184,7 @@
     protected PendingActionClearedCallback mPendingActionClearedCallback;
 
     protected HdmiCecLocalDevice(HdmiControlService service, int deviceType) {
-        mService = service;
-        mDeviceType = deviceType;
-        mLock = service.getServiceLock();
+        super(service, deviceType);
     }
 
     // Factory method that returns HdmiCecLocalDevice of corresponding type.
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 96e7b03..6303bdc 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -899,10 +899,16 @@
 
     @ServiceThreadOnly
     void startArcAction(boolean enabled) {
+        startArcAction(enabled, null);
+    }
+
+    @ServiceThreadOnly
+    void startArcAction(boolean enabled, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
         HdmiDeviceInfo info = getAvrDeviceInfo();
         if (info == null) {
             Slog.w(TAG, "Failed to start arc action; No AVR device.");
+            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
             return;
         }
         if (!canStartArcUpdateAction(info.getLogicalAddress(), enabled)) {
@@ -910,19 +916,37 @@
             if (enabled && !isConnectedToArcPort(info.getPhysicalAddress())) {
                 displayOsd(OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT);
             }
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+            return;
+        }
+        if (enabled && mService.earcBlocksArcConnection()) {
+            Slog.i(TAG,
+                    "ARC connection blocked because eARC connection is established or being "
+                            + "established.");
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
             return;
         }
 
-        // Terminate opposite action and start action if not exist.
+        // Terminate opposite action and create an action with callback.
         if (enabled) {
             removeAction(RequestArcTerminationAction.class);
-            if (!hasAction(RequestArcInitiationAction.class)) {
-                addAndStartAction(new RequestArcInitiationAction(this, info.getLogicalAddress()));
+            if (hasAction(RequestArcInitiationAction.class)) {
+                RequestArcInitiationAction existingInitiationAction =
+                        getActions(RequestArcInitiationAction.class).get(0);
+                existingInitiationAction.addCallback(callback);
+            } else {
+                addAndStartAction(
+                        new RequestArcInitiationAction(this, info.getLogicalAddress(), callback));
             }
         } else {
             removeAction(RequestArcInitiationAction.class);
-            if (!hasAction(RequestArcTerminationAction.class)) {
-                addAndStartAction(new RequestArcTerminationAction(this, info.getLogicalAddress()));
+            if (hasAction(RequestArcTerminationAction.class)) {
+                RequestArcTerminationAction existingTerminationAction =
+                        getActions(RequestArcTerminationAction.class).get(0);
+                existingTerminationAction.addCallback(callback);
+            } else {
+                addAndStartAction(
+                        new RequestArcTerminationAction(this, info.getLogicalAddress(), callback));
             }
         }
     }
@@ -1010,6 +1034,13 @@
     protected int handleInitiateArc(HdmiCecMessage message) {
         assertRunOnServiceThread();
 
+        if (mService.earcBlocksArcConnection()) {
+            Slog.i(TAG,
+                    "ARC connection blocked because eARC connection is established or being "
+                            + "established.");
+            return Constants.ABORT_NOT_IN_CORRECT_MODE;
+        }
+
         if (!canStartArcUpdateAction(message.getSource(), true)) {
             HdmiDeviceInfo avrDeviceInfo = getAvrDeviceInfo();
             if (avrDeviceInfo == null) {
@@ -1023,9 +1054,8 @@
             return Constants.ABORT_REFUSED;
         }
 
-        // In case where <Initiate Arc> is started by <Request ARC Initiation>
-        // need to clean up RequestArcInitiationAction.
-        removeAction(RequestArcInitiationAction.class);
+        // In case where <Initiate Arc> is started by <Request ARC Initiation>, this message is
+        // handled in RequestArcInitiationAction as well.
         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                 message.getSource(), true);
         addAndStartAction(action);
@@ -1059,9 +1089,8 @@
             return Constants.HANDLED;
         }
         // Do not check ARC configuration since the AVR might have been already removed.
-        // Clean up RequestArcTerminationAction in case <Terminate Arc> was started by
-        // <Request ARC Termination>.
-        removeAction(RequestArcTerminationAction.class);
+        // In case where <Terminate Arc> is started by <Request ARC Termination>, this
+        // message is handled in RequestArcTerminationAction as well.
         SetArcTransmissionStateAction action = new SetArcTransmissionStateAction(this,
                 message.getSource(), false);
         addAndStartAction(action);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
index f708941..a57292a 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecNetwork.java
@@ -470,7 +470,8 @@
         for (HdmiPortInfo info : cecPortInfo) {
             if (mhlSupportedPorts.contains(info.getId())) {
                 result.add(new HdmiPortInfo(info.getId(), info.getType(), info.getAddress(),
-                        info.isCecSupported(), true, info.isArcSupported()));
+                        info.isCecSupported(), true, info.isArcSupported(),
+                        info.isEarcSupported()));
             } else {
                 result.add(info);
             }
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2f15e57..f66f8ea 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -18,6 +18,7 @@
 
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
 import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
+import static android.hardware.hdmi.HdmiControlManager.EARC_FEATURE_ENABLED;
 import static android.hardware.hdmi.HdmiControlManager.HDMI_CEC_CONTROL_ENABLED;
 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_DISABLED;
 import static android.hardware.hdmi.HdmiControlManager.SOUNDBAR_MODE_ENABLED;
@@ -25,6 +26,7 @@
 import static com.android.server.hdmi.Constants.ADDR_UNREGISTERED;
 import static com.android.server.hdmi.Constants.DISABLED;
 import static com.android.server.hdmi.Constants.ENABLED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
 import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
 import static com.android.server.hdmi.Constants.OPTION_MHL_INPUT_SWITCHING;
 import static com.android.server.hdmi.Constants.OPTION_MHL_POWER_CHARGE;
@@ -126,6 +128,8 @@
 /**
  * Provides a service for sending and processing HDMI control messages,
  * HDMI-CEC and MHL control command, and providing the information on both standard.
+ *
+ * Additionally takes care of establishing and managing an eARC connection.
  */
 public class HdmiControlService extends SystemService {
     private static final String TAG = "HdmiControlService";
@@ -184,13 +188,14 @@
 
     static final String PERMISSION = "android.permission.HDMI_CEC";
 
-    // The reason code to initiate initializeCec().
+    // The reason code to initiate initializeCec() and initializeEarc().
     static final int INITIATED_BY_ENABLE_CEC = 0;
     static final int INITIATED_BY_BOOT_UP = 1;
     static final int INITIATED_BY_SCREEN_ON = 2;
     static final int INITIATED_BY_WAKE_UP_MESSAGE = 3;
     static final int INITIATED_BY_HOTPLUG = 4;
     static final int INITIATED_BY_SOUNDBAR_MODE = 5;
+    static final int INITIATED_BY_ENABLE_EARC = 6;
 
     // The reason code representing the intent action that drives the standby
     // procedure. The procedure starts either by Intent.ACTION_SCREEN_OFF or
@@ -384,6 +389,18 @@
     @HdmiControlManager.HdmiCecControl
     private int mHdmiControlEnabled;
 
+    // Set to true while the eARC feature is supported by the hardware on at least one port
+    // and the eARC HAL is present.
+    @GuardedBy("mLock")
+    @VisibleForTesting
+    private boolean mEarcSupported;
+
+    // Set to true while the eARC feature is enabled.
+    @GuardedBy("mLock")
+    private boolean mEarcEnabled;
+
+    private int mEarcPortId = -1;
+
     // Set to true while the service is in normal mode. While set to false, no input change is
     // allowed. Used for situations where input change can confuse users such as channel auto-scan,
     // system upgrade, etc., a.k.a. "prohibit mode".
@@ -417,6 +434,12 @@
 
     private HdmiCecPowerStatusController mPowerStatusController;
 
+    @Nullable
+    private HdmiEarcController mEarcController;
+
+    @Nullable
+    private HdmiEarcLocalDevice mEarcLocalDevice;
+
     @ServiceThreadOnly
     private String mMenuLanguage = localeToMenuLanguage(Locale.getDefault());
 
@@ -631,9 +654,13 @@
             mPowerStatusController = new HdmiCecPowerStatusController(this);
         }
         mPowerStatusController.setPowerStatus(getInitialPowerStatus());
-        mProhibitMode = false;
+        setProhibitMode(false);
         mHdmiControlEnabled = mHdmiCecConfig.getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
+        synchronized (mLock) {
+            mEarcEnabled = (mHdmiCecConfig.getIntValue(
+                    HdmiControlManager.SETTING_NAME_EARC_ENABLED) == EARC_FEATURE_ENABLED);
+        }
         setHdmiCecVolumeControlEnabledInternal(getHdmiCecConfig().getIntValue(
                 HdmiControlManager.CEC_SETTING_NAME_VOLUME_CONTROL_MODE));
         mMhlInputChangeEnabled = readBooleanSetting(Global.MHL_INPUT_SWITCHING_ENABLED, true);
@@ -646,7 +673,6 @@
         }
         if (mCecController == null) {
             Slog.i(TAG, "Device does not support HDMI-CEC.");
-            return;
         }
         if (mMhlController == null) {
             mMhlController = HdmiMhlControllerStub.create(this);
@@ -654,15 +680,56 @@
         if (!mMhlController.isReady()) {
             Slog.i(TAG, "Device does not support MHL-control.");
         }
+        if (mEarcController == null) {
+            mEarcController = HdmiEarcController.create(this);
+        }
+        if (mEarcController == null) {
+            Slog.i(TAG, "Device does not support eARC.");
+        }
+        if (mCecController == null && mEarcController == null) {
+            return;
+        }
         mHdmiCecNetwork = new HdmiCecNetwork(this, mCecController, mMhlController);
-        if (mHdmiControlEnabled == HdmiControlManager.HDMI_CEC_CONTROL_ENABLED) {
+        if (isCecControlEnabled()) {
             initializeCec(INITIATED_BY_BOOT_UP);
         } else {
             mCecController.enableCec(false);
         }
-        mMhlDevices = Collections.emptyList();
+
+        synchronized (mLock) {
+            mMhlDevices = Collections.emptyList();
+        }
 
         mHdmiCecNetwork.initPortInfo();
+        List<HdmiPortInfo> ports = getPortInfo();
+        synchronized (mLock) {
+            mEarcSupported = false;
+            for (HdmiPortInfo port : ports) {
+                boolean earcSupportedOnPort = port.isEarcSupported();
+                if (earcSupportedOnPort && mEarcSupported) {
+                    // This means that more than 1 port supports eARC.
+                    // The HDMI specification only allows 1 active eARC connection.
+                    // Android does not support devices with multiple eARC-enabled ports.
+                    // Consider eARC not supported in this case.
+                    Slog.e(TAG, "HDMI eARC supported on more than 1 port.");
+                    mEarcSupported = false;
+                    mEarcPortId = -1;
+                    break;
+                } else if (earcSupportedOnPort) {
+                    mEarcPortId = port.getId();
+                    mEarcSupported = earcSupportedOnPort;
+                }
+            }
+            mEarcSupported &= (mEarcController != null);
+        }
+        if (isEarcSupported()) {
+            if (isEarcEnabled()) {
+                initializeEarc(INITIATED_BY_BOOT_UP);
+            } else {
+                setEarcEnabledInHal(false, false);
+            }
+        }
+
         mHdmiCecConfig.registerChangeListener(HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
                 new HdmiCecConfig.SettingChangeListener() {
                     @Override
@@ -743,8 +810,16 @@
                             mCecController.enableWakeupByOtp(tv().getAutoWakeup());
                         }
                     }
-                },
-                mServiceThreadExecutor);
+                }, mServiceThreadExecutor);
+        mHdmiCecConfig.registerChangeListener(HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                new HdmiCecConfig.SettingChangeListener() {
+                    @Override
+                    public void onChange(String setting) {
+                        @HdmiControlManager.HdmiCecControl int enabled = mHdmiCecConfig.getIntValue(
+                                HdmiControlManager.SETTING_NAME_EARC_ENABLED);
+                        setEarcEnabled(enabled);
+                    }
+                }, mServiceThreadExecutor);
     }
 
     /** Returns true if the device screen is off */
@@ -1083,7 +1158,8 @@
     }
 
     @ServiceThreadOnly
-    private void initializeCecLocalDevices(final int initiatedBy) {
+    @VisibleForTesting
+    protected void initializeCecLocalDevices(final int initiatedBy) {
         assertRunOnServiceThread();
         // A container for [Device type, Local device info].
         ArrayList<HdmiCecLocalDevice> localDevices = new ArrayList<>();
@@ -1245,7 +1321,6 @@
      * Returns {@link Looper} of main thread. Use this {@link Looper} instance
      * for tasks that are running on main service thread.
      */
-    @VisibleForTesting
     protected Looper getServiceLooper() {
         return mHandler.getLooper();
     }
@@ -2568,7 +2643,9 @@
             if (!DumpUtils.checkDumpPermission(getContext(), TAG, writer)) return;
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
 
-            pw.println("mProhibitMode: " + mProhibitMode);
+            synchronized (mLock) {
+                pw.println("mProhibitMode: " + mProhibitMode);
+            }
             pw.println("mPowerStatus: " + mPowerStatusController.getPowerStatus());
             pw.println("mIsCecAvailable: " + mIsCecAvailable);
             pw.println("mCecVersion: " + mCecVersion);
@@ -2577,9 +2654,9 @@
             // System settings
             pw.println("System_settings:");
             pw.increaseIndent();
-            pw.println("mMhlInputChangeEnabled: " + mMhlInputChangeEnabled);
+            pw.println("mMhlInputChangeEnabled: " + isMhlInputChangeEnabled());
             pw.println("mSystemAudioActivated: " + isSystemAudioActivated());
-            pw.println("mHdmiCecVolumeControlEnabled: " + mHdmiCecVolumeControl);
+            pw.println("mHdmiCecVolumeControlEnabled: " + getHdmiCecVolumeControl());
             pw.decreaseIndent();
 
             // CEC settings
@@ -2605,6 +2682,14 @@
             pw.increaseIndent();
             mMhlController.dump(pw);
             pw.decreaseIndent();
+            pw.print("eARC local device: ");
+            pw.increaseIndent();
+            if (mEarcLocalDevice == null) {
+                pw.println("None. eARC is either disabled or not available.");
+            } else {
+                mEarcLocalDevice.dump(pw);
+            }
+            pw.decreaseIndent();
             mHdmiCecNetwork.dump(pw);
             if (mCecController != null) {
                 pw.println("mCecController: ");
@@ -3152,6 +3237,9 @@
     }
 
     private void invokeCallback(IHdmiControlCallback callback, int result) {
+        if (callback == null) {
+            return;
+        }
         try {
             callback.onComplete(result);
         } catch (RemoteException e) {
@@ -3313,6 +3401,18 @@
         }
     }
 
+    private boolean isEarcEnabled() {
+        synchronized (mLock) {
+            return mEarcEnabled;
+        }
+    }
+
+    private boolean isEarcSupported() {
+        synchronized (mLock) {
+            return mEarcSupported;
+        }
+    }
+
     @ServiceThreadOnly
     int getPowerStatus() {
         assertRunOnServiceThread();
@@ -3384,7 +3484,7 @@
         mPowerStatusController.setPowerStatus(HdmiControlManager.POWER_STATUS_TRANSIENT_TO_ON,
                 false);
         if (mCecController != null) {
-            if (mHdmiControlEnabled == HDMI_CEC_CONTROL_ENABLED) {
+            if (isCecControlEnabled()) {
                 int startReason = -1;
                 switch (wakeUpAction) {
                     case WAKE_UP_SCREEN_ON:
@@ -3406,6 +3506,25 @@
         } else {
             Slog.i(TAG, "Device does not support HDMI-CEC.");
         }
+        if (isEarcSupported()) {
+            if (isEarcEnabled()) {
+                int startReason = -1;
+                switch (wakeUpAction) {
+                    case WAKE_UP_SCREEN_ON:
+                        startReason = INITIATED_BY_SCREEN_ON;
+                        break;
+                    case WAKE_UP_BOOT_UP:
+                        startReason = INITIATED_BY_BOOT_UP;
+                        break;
+                    default:
+                        Slog.e(TAG, "wakeUpAction " + wakeUpAction + " not defined.");
+                        return;
+                }
+                initializeEarc(startReason);
+            } else {
+                setEarcEnabledInHal(false, false);
+            }
+        }
         // TODO: Initialize MHL local devices.
     }
 
@@ -4296,4 +4415,176 @@
         getAudioManager().setStreamVolume(AudioManager.STREAM_MUSIC,
                 volume * mStreamMusicMaxVolume / AudioStatus.MAX_VOLUME, flags);
     }
+
+    private void initializeEarc(int initiatedBy) {
+        Slog.i(TAG, "eARC initialized, reason = " + initiatedBy);
+        initializeEarcLocalDevice(initiatedBy);
+
+        if (initiatedBy == INITIATED_BY_ENABLE_EARC) {
+            // Since ARC and eARC cannot be connected simultaneously, we need to terminate ARC
+            // before even enabling eARC.
+            setEarcEnabledInHal(true, true);
+        } else {
+            // On boot, wake-up, and hotplug in, eARC will always be attempted before ARC.
+            // So there is no need to explicitly terminate ARC before enabling eARC.
+            setEarcEnabledInHal(true, false);
+        }
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected void initializeEarcLocalDevice(final int initiatedBy) {
+        // TODO remove initiatedBy argument if it stays unused
+        assertRunOnServiceThread();
+        if (mEarcLocalDevice == null) {
+            mEarcLocalDevice = HdmiEarcLocalDevice.create(this, HdmiDeviceInfo.DEVICE_TV);
+        }
+        // TODO create HdmiEarcLocalDeviceRx if we're an audio system device.
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected void setEarcEnabled(@HdmiControlManager.EarcFeature int enabled) {
+        assertRunOnServiceThread();
+        synchronized (mLock) {
+            mEarcEnabled = (enabled == EARC_FEATURE_ENABLED);
+
+            if (!isEarcSupported()) {
+                Slog.i(TAG, "Enabled/disabled eARC setting, but the hardware doesn´t support eARC. "
+                        + "This settings change doesn´t have an effect.");
+                return;
+            }
+
+            if (mEarcEnabled) {
+                onEnableEarc();
+                return;
+            }
+        }
+        runOnServiceThread(new Runnable() {
+            @Override
+            public void run() {
+                onDisableEarc();
+            }
+        });
+    }
+
+    @VisibleForTesting
+    protected void setEarcSupported(boolean supported) {
+        synchronized (mLock) {
+            mEarcSupported = supported;
+        }
+    }
+
+    @ServiceThreadOnly
+    private void onEnableEarc() {
+        // This will terminate ARC as well.
+        initializeEarc(INITIATED_BY_ENABLE_EARC);
+    }
+
+    @ServiceThreadOnly
+    private void onDisableEarc() {
+        disableEarcLocalDevice();
+        setEarcEnabledInHal(false, false);
+        clearEarcLocalDevice();
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected void clearEarcLocalDevice() {
+        assertRunOnServiceThread();
+        mEarcLocalDevice = null;
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected void addEarcLocalDevice(HdmiEarcLocalDevice localDevice) {
+        assertRunOnServiceThread();
+        mEarcLocalDevice = localDevice;
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    HdmiEarcLocalDevice getEarcLocalDevice() {
+        assertRunOnServiceThread();
+        return mEarcLocalDevice;
+    }
+
+    private void disableEarcLocalDevice() {
+        if (mEarcLocalDevice == null) {
+            return;
+        }
+        mEarcLocalDevice.disableDevice();
+    }
+
+    @ServiceThreadOnly
+    @VisibleForTesting
+    protected void setEarcEnabledInHal(boolean enabled, boolean terminateArcFirst) {
+        assertRunOnServiceThread();
+        if (terminateArcFirst) {
+            startArcAction(false, new IHdmiControlCallback.Stub() {
+                @Override
+                public void onComplete(int result) throws RemoteException {
+                    // Independently of the result (i.e. independently of whether the ARC RX device
+                    // responded with <Terminate ARC> or not), we always end up terminating ARC in
+                    // the HAL. As soon as we do that, we can enable eARC in the HAL.
+                    mEarcController.setEarcEnabled(enabled);
+                    mCecController.setHpdSignalType(
+                            enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT
+                                    : Constants.HDMI_HPD_TYPE_PHYSICAL,
+                            mEarcPortId);
+                }
+            });
+        } else {
+            mEarcController.setEarcEnabled(enabled);
+            mCecController.setHpdSignalType(
+                    enabled ? Constants.HDMI_HPD_TYPE_STATUS_BIT : Constants.HDMI_HPD_TYPE_PHYSICAL,
+                    mEarcPortId);
+        }
+    }
+
+    @ServiceThreadOnly
+    void handleEarcStateChange(int status, int portId) {
+        assertRunOnServiceThread();
+        if (!getPortInfo(portId).isEarcSupported()) {
+            Slog.w(TAG, "Tried to update eARC status on a port that doesn't support eARC.");
+            return;
+        }
+        // If eARC is disabled, the local device is null. In this case, the HAL shouldn't have
+        // reported connection state changes, but even if it did, it won't take effect.
+        if (mEarcLocalDevice != null) {
+            mEarcLocalDevice.handleEarcStateChange(status);
+        }
+    }
+
+    @ServiceThreadOnly
+    void handleEarcCapabilitiesReported(byte[] rawCapabilities, int portId) {
+        assertRunOnServiceThread();
+        if (!getPortInfo(portId).isEarcSupported()) {
+            Slog.w(TAG,
+                    "Tried to process eARC capabilities from a port that doesn't support eARC.");
+            return;
+        }
+        // If eARC is disabled, the local device is null. In this case, the HAL shouldn't have
+        // reported eARC capabilities, but even if it did, it won't take effect.
+        if (mEarcLocalDevice != null) {
+            mEarcLocalDevice.handleEarcCapabilitiesReported(rawCapabilities);
+        }
+    }
+
+    protected boolean earcBlocksArcConnection() {
+        if (mEarcLocalDevice == null) {
+            return false;
+        }
+        synchronized (mLock) {
+            return mEarcLocalDevice.mEarcStatus != HDMI_EARC_STATUS_ARC_PENDING;
+        }
+    }
+
+    protected void startArcAction(boolean enabled, IHdmiControlCallback callback) {
+        if (!isTvDeviceEnabled()) {
+            invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
+        } else {
+            tv().startArcAction(enabled, callback);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcController.java b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
new file mode 100644
index 0000000..8522509
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcController.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hdmi;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+final class HdmiEarcController {
+    private static final String TAG = "HdmiEarcController";
+
+    // Handler instance to process HAL calls.
+    private Handler mControlHandler;
+
+    private final HdmiControlService mService;
+
+    // Private constructor. Use HdmiEarcController.create().
+    private HdmiEarcController(HdmiControlService service) {
+        mService = service;
+    }
+
+    /**
+     * A factory method to get {@link HdmiEarcController}. If it fails to initialize
+     * inner device or has no device it will return {@code null}.
+     *
+     * <p>Declared as package-private, accessed by {@link HdmiControlService} only.
+     * @param service    {@link HdmiControlService} instance used to create internal handler
+     *                   and to pass callback for incoming message or event.
+     * @return {@link HdmiEarcController} if device is initialized successfully. Otherwise,
+     *         returns {@code null}.
+     */
+    static HdmiEarcController create(HdmiControlService service) {
+        // TODO add the native wrapper and return null if eARC HAL is not present.
+        HdmiEarcController controller = new HdmiEarcController(service);
+        controller.init();
+        return controller;
+    }
+
+    private void init() {
+        mControlHandler = new Handler(mService.getServiceLooper());
+    }
+
+    private void assertRunOnServiceThread() {
+        if (Looper.myLooper() != mControlHandler.getLooper()) {
+            throw new IllegalStateException("Should run on service thread.");
+        }
+    }
+
+    @VisibleForTesting
+    void runOnServiceThread(Runnable runnable) {
+        mControlHandler.post(new WorkSourceUidPreservingRunnable(runnable));
+    }
+
+    /**
+     * Enable eARC in the HAL
+     * @param enabled
+     */
+    @HdmiAnnotations.ServiceThreadOnly
+    void setEarcEnabled(boolean enabled) {
+        assertRunOnServiceThread();
+        // Stub.
+        // TODO: bind to native.
+        // TODO: handle error return values here, with logging.
+    }
+
+    /**
+     * Getter for the current eARC state.
+     * @param portId the ID of the port on which to get the connection state
+     * @return the current eARC state
+     */
+    @HdmiAnnotations.ServiceThreadOnly
+    @Constants.EarcStatus
+    int getState(int portId) {
+        // Stub.
+        // TODO: bind to native.
+        return Constants.HDMI_EARC_STATUS_IDLE;
+    }
+
+     /**
+     * Ask the HAL to report the last eARC capabilities that the connected audio system reported.
+     * @return the raw eARC capabilities
+     */
+    @HdmiAnnotations.ServiceThreadOnly
+    byte[] getLastReportedCaps() {
+        // Stub. TODO: bind to native.
+        return new byte[] {};
+    }
+
+    final class EarcCallback {
+        public void onStateChange(@Constants.EarcStatus int status, int portId) {
+            runOnServiceThread(
+                    () -> mService.handleEarcStateChange(status, portId));
+        }
+
+        public void onCapabilitiesReported(byte[] rawCapabilities, int portId) {
+            runOnServiceThread(
+                    () -> mService.handleEarcCapabilitiesReported(rawCapabilities, portId));
+        }
+    }
+
+    // TODO: bind to native.
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java
new file mode 100644
index 0000000..e95ecd2
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDevice.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Class that models a local eARC device hosted in this system.
+ * The class contains methods that are common between eARC TX and eARC RX devices.
+ */
+abstract class HdmiEarcLocalDevice extends HdmiLocalDevice {
+    private static final String TAG = "HdmiEarcLocalDevice";
+
+    // The current status of the eARC connection, as reported by the HAL
+    @GuardedBy("mLock")
+    @Constants.EarcStatus
+    protected int mEarcStatus;
+
+    protected HdmiEarcLocalDevice(HdmiControlService service, int deviceType) {
+        super(service, deviceType);
+    }
+
+    // Factory method that returns HdmiCecLocalDevice of corresponding type.
+    static HdmiEarcLocalDevice create(HdmiControlService service, int deviceType) {
+        switch (deviceType) {
+            case HdmiDeviceInfo.DEVICE_TV:
+                return new HdmiEarcLocalDeviceTx(service);
+            default:
+                return null;
+        }
+    }
+
+    protected abstract void handleEarcStateChange(@Constants.EarcStatus int status);
+
+    protected abstract void handleEarcCapabilitiesReported(byte[] rawCapabilities);
+
+    protected void disableDevice() {
+    }
+
+    /** Dump internal status of HdmiEarcLocalDevice object */
+    protected void dump(final IndentingPrintWriter pw) {
+        // Should be overridden in the more specific classes
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
new file mode 100644
index 0000000..abb8439
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiEarcLocalDeviceTx.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_IDLE;
+
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDescriptor;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+import android.media.AudioProfile;
+import android.os.Handler;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents a local eARC device of type TX residing in the Android system.
+ * Only TV panel devices can have a local eARC TX device.
+ */
+public class HdmiEarcLocalDeviceTx extends HdmiEarcLocalDevice {
+    private static final String TAG = "HdmiEarcLocalDeviceTx";
+
+    // How long to wait for the audio system to report its capabilities after eARC was connected
+    static final long REPORT_CAPS_MAX_DELAY_MS = 2_000;
+
+    // eARC Capability Data Structure parameters
+    private static final int EARC_CAPS_PAYLOAD_LENGTH = 0x02;
+    private static final int EARC_CAPS_DATA_START = 0x03;
+
+    // Table 55 CTA Data Block Tag Codes
+    private static final int TAGCODE_AUDIO_DATA_BLOCK = 0x01; // Includes one or more Short Audio
+                                                              // Descriptors
+    private static final int TAGCODE_SADB_DATA_BLOCK = 0x04;  // Speaker Allocation Data Block
+    private static final int TAGCODE_USE_EXTENDED_TAG = 0x07; // Use Extended Tag
+
+    // Table 56 Extended Tag Format (2nd byte of Data Block)
+    private static final int EXTENDED_TAGCODE_VSADB = 0x11;   // Vendor-Specific Audio Data Block
+
+    // eARC capability mask and shift
+    private static final int EARC_CAPS_TAGCODE_MASK = 0xE0;
+    private static final int EARC_CAPS_TAGCODE_SHIFT = 0x05;
+    private static final int EARC_CAPS_LENGTH_MASK = 0x1F;
+
+    // Handler and runnable for waiting for the audio system to report its capabilities after eARC
+    // was connected
+    private Handler mReportCapsHandler;
+    private ReportCapsRunnable mReportCapsRunnable;
+
+    HdmiEarcLocalDeviceTx(HdmiControlService service) {
+        super(service, HdmiDeviceInfo.DEVICE_TV);
+
+        synchronized (mLock) {
+            mEarcStatus = HDMI_EARC_STATUS_EARC_PENDING;
+        }
+        mReportCapsHandler = new Handler(service.getServiceLooper());
+        mReportCapsRunnable = new ReportCapsRunnable();
+    }
+
+    protected void handleEarcStateChange(@Constants.EarcStatus int status) {
+        int oldEarcStatus;
+        synchronized (mLock) {
+            HdmiLogger.debug(TAG, "eARC state change [old:%b new %b]", mEarcStatus,
+                    status);
+            oldEarcStatus = mEarcStatus;
+            mEarcStatus = status;
+        }
+
+        mReportCapsHandler.removeCallbacksAndMessages(null);
+        if (status == HDMI_EARC_STATUS_IDLE) {
+            notifyEarcStatusToAudioService(false, new ArrayList<>());
+            mService.startArcAction(false, null);
+        } else if (status == HDMI_EARC_STATUS_ARC_PENDING) {
+            notifyEarcStatusToAudioService(false, new ArrayList<>());
+            mService.startArcAction(true, null);
+        } else if (status == HDMI_EARC_STATUS_EARC_PENDING
+                && oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) {
+            mService.startArcAction(false, null);
+        } else if (status == HDMI_EARC_STATUS_EARC_CONNECTED) {
+            if (oldEarcStatus == HDMI_EARC_STATUS_ARC_PENDING) {
+                mService.startArcAction(false, null);
+            }
+            mReportCapsHandler.postDelayed(mReportCapsRunnable, REPORT_CAPS_MAX_DELAY_MS);
+        }
+    }
+
+    protected void handleEarcCapabilitiesReported(byte[] rawCapabilities) {
+        synchronized (mLock) {
+            if (mEarcStatus == HDMI_EARC_STATUS_EARC_CONNECTED
+                    && mReportCapsHandler.hasCallbacks(mReportCapsRunnable)) {
+                mReportCapsHandler.removeCallbacksAndMessages(null);
+                List<AudioDescriptor> audioDescriptors = parseCapabilities(rawCapabilities);
+                notifyEarcStatusToAudioService(true, audioDescriptors);
+            }
+        }
+    }
+
+    private void notifyEarcStatusToAudioService(
+            boolean enabled, List<AudioDescriptor> audioDescriptors) {
+        AudioDeviceAttributes attributes = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HDMI_EARC, "", "",
+                new ArrayList<AudioProfile>(), audioDescriptors);
+        mService.getAudioManager().setWiredDeviceConnectionState(attributes, enabled ? 1 : 0);
+    }
+
+    /**
+     * Runnable for waiting for a certain amount of time for the audio system to report its
+     * capabilities after eARC was connected. If the audio system doesn´t report its capabilities in
+     * this time, we inform AudioService about the connection state only, without any specified
+     * capabilities.
+     */
+    private class ReportCapsRunnable implements Runnable {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                if (mEarcStatus == HDMI_EARC_STATUS_EARC_CONNECTED) {
+                    notifyEarcStatusToAudioService(true, new ArrayList<>());
+                }
+            }
+        }
+    }
+
+    private List<AudioDescriptor> parseCapabilities(byte[] rawCapabilities) {
+        List<AudioDescriptor> audioDescriptors = new ArrayList<>();
+        if (rawCapabilities.length < EARC_CAPS_DATA_START + 1) {
+            Slog.i(TAG, "Raw eARC capabilities array doesn´t contain any blocks.");
+            return audioDescriptors;
+        }
+        int earcCapsSize = rawCapabilities[EARC_CAPS_PAYLOAD_LENGTH];
+        if (rawCapabilities.length < earcCapsSize) {
+            Slog.i(TAG, "Raw eARC capabilities array is shorter than the reported payload length.");
+            return audioDescriptors;
+        }
+        int firstByteOfBlock = EARC_CAPS_DATA_START;
+        while (firstByteOfBlock < earcCapsSize) {
+            // Tag Code: Bit 5-7
+            int tagCode =
+                    (rawCapabilities[firstByteOfBlock] & EARC_CAPS_TAGCODE_MASK)
+                            >> EARC_CAPS_TAGCODE_SHIFT;
+            // Length: Bit 0-4
+            int length = rawCapabilities[firstByteOfBlock] & EARC_CAPS_LENGTH_MASK;
+            if (length == 0) {
+                // End Marker of eARC capability.
+                break;
+            }
+            AudioDescriptor descriptor;
+            switch (tagCode) {
+                case TAGCODE_AUDIO_DATA_BLOCK:
+                    int earcSadLen = length;
+                    if (length % 3 != 0) {
+                        Slog.e(TAG, "Invalid length of SAD block: expected a factor of 3 but got "
+                                + length % 3);
+                        break;
+                    }
+                    byte[] earcSad = new byte[earcSadLen];
+                    System.arraycopy(rawCapabilities, firstByteOfBlock + 1, earcSad, 0, earcSadLen);
+                    for (int i = 0; i < earcSadLen; i += 3) {
+                        descriptor = new AudioDescriptor(
+                                AudioDescriptor.STANDARD_EDID,
+                                AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
+                                Arrays.copyOfRange(earcSad, i, i + 3));
+                        audioDescriptors.add(descriptor);
+                    }
+                    break;
+                case TAGCODE_SADB_DATA_BLOCK:
+                    //Include Tag code size
+                    int earcSadbLen = length + 1;
+                    byte[] earcSadb = new byte[earcSadbLen];
+                    System.arraycopy(rawCapabilities, firstByteOfBlock, earcSadb, 0, earcSadbLen);
+                    descriptor = new AudioDescriptor(
+                            AudioDescriptor.STANDARD_SADB,
+                            AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
+                            earcSadb);
+                    audioDescriptors.add(descriptor);
+                    break;
+                case TAGCODE_USE_EXTENDED_TAG:
+                    if (rawCapabilities[firstByteOfBlock + 1] == EXTENDED_TAGCODE_VSADB) {
+                        int earcVsadbLen = length + 1; //Include Tag code size
+                        byte[] earcVsadb = new byte[earcVsadbLen];
+                        System.arraycopy(rawCapabilities, firstByteOfBlock, earcVsadb, 0,
+                                earcVsadbLen);
+                        descriptor = new AudioDescriptor(
+                                AudioDescriptor.STANDARD_VSADB,
+                                AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE,
+                                earcVsadb);
+                        audioDescriptors.add(descriptor);
+                    }
+                    break;
+                default:
+                    Slog.w(TAG, "This tagcode was not handled: " + tagCode);
+                    break;
+            }
+            firstByteOfBlock += (length + 1);
+        }
+        return audioDescriptors;
+    }
+
+    /** Dump internal status of HdmiEarcLocalDeviceTx object */
+    protected void dump(final IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println("TX, mEarcStatus: " + mEarcStatus);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java
new file mode 100644
index 0000000..f661e40
--- /dev/null
+++ b/services/core/java/com/android/server/hdmi/HdmiLocalDevice.java
@@ -0,0 +1,37 @@
+/*
+ * 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.hdmi;
+
+/**
+ * Class that models an HDMI device hosted in this system.
+ * Can be used to share methods between CEC and eARC local devices.
+ * Currently just a placeholder.
+ */
+abstract class HdmiLocalDevice {
+    private static final String TAG = "HdmiLocalDevice";
+
+    protected final HdmiControlService mService;
+    protected final int mDeviceType;
+
+    protected final Object mLock;
+
+    protected HdmiLocalDevice(HdmiControlService service, int deviceType) {
+        mService = service;
+        mDeviceType = deviceType;
+        mLock = service.getServiceLock();
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/RequestArcAction.java b/services/core/java/com/android/server/hdmi/RequestArcAction.java
index 3d9a290..54c8c00 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcAction.java
@@ -16,7 +16,9 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
 
 /**
  * Base feature action class for &lt;Request ARC Initiation&gt;/&lt;Request ARC Termination&gt;.
@@ -35,41 +37,19 @@
      *
      * @param source {@link HdmiCecLocalDevice} instance
      * @param avrAddress address of AV receiver. It should be AUDIO_SYSTEM type
+     * @param callback callback to inform about the status of the action
      * @throws IllegalArgumentException if device type of sourceAddress and avrAddress
      *                      is invalid
      */
-    RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
-        super(source);
+    RequestArcAction(HdmiCecLocalDevice source, int avrAddress, IHdmiControlCallback callback) {
+        super(source, callback);
         HdmiUtils.verifyAddressType(getSourceAddress(), HdmiDeviceInfo.DEVICE_TV);
         HdmiUtils.verifyAddressType(avrAddress, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         mAvrAddress = avrAddress;
     }
 
-    @Override
-    boolean processCommand(HdmiCecMessage cmd) {
-        if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
-                || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
-            return false;
-        }
-        int opcode = cmd.getOpcode();
-        switch (opcode) {
-            // Handles only <Feature Abort> here and, both <Initiate ARC> and <Terminate ARC>
-            // are handled in HdmiControlService itself because both can be
-            // received without <Request ARC Initiation> or <Request ARC Termination>.
-            case Constants.MESSAGE_FEATURE_ABORT:
-                int originalOpcode = cmd.getParams()[0] & 0xFF;
-                if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
-                    disableArcTransmission();
-                    finish();
-                    return true;
-                } else if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) {
-                    tv().disableArc();
-                    finish();
-                    return true;
-                }
-                return false;
-        }
-        return false;
+    RequestArcAction(HdmiCecLocalDevice source, int avrAddress) {
+        this(source, avrAddress, null);
     }
 
     protected final void disableArcTransmission() {
@@ -86,6 +66,6 @@
         }
         HdmiLogger.debug("[T] RequestArcAction.");
         disableArcTransmission();
-        finish();
+        finishWithCallback(HdmiControlManager.RESULT_TIMEOUT);
     }
 }
diff --git a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
index 3b7f1dd..db21a33 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcInitiationAction.java
@@ -16,6 +16,8 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 
 /**
@@ -35,6 +37,16 @@
         super(source, avrAddress);
     }
 
+    /**
+     * @Constructor
+     *
+     * For more details look at {@link RequestArcAction#RequestArcAction}.
+     */
+    RequestArcInitiationAction(HdmiCecLocalDevice source, int avrAddress,
+            IHdmiControlCallback callback) {
+        super(source, avrAddress, callback);
+    }
+
     @Override
     boolean start() {
         // Seq #38
@@ -49,10 +61,34 @@
                 if (error != SendMessageResult.SUCCESS) {
                     // Turn off ARC status if <Request ARC Initiation> fails.
                     tv().disableArc();
-                    finish();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
                 }
             }
         });
         return true;
     }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
+                || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
+            return false;
+        }
+        int opcode = cmd.getOpcode();
+        switch (opcode) {
+            case Constants.MESSAGE_FEATURE_ABORT:
+                int originalOpcode = cmd.getParams()[0] & 0xFF;
+                if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_INITIATION) {
+                    tv().disableArc();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+                    return true;
+                }
+                return false;
+            case Constants.MESSAGE_INITIATE_ARC:
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+                // This message still needs to be handled in HdmiCecLocalDeviceTv as well.
+                return false;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
index 8b5a2931..85128b6 100644
--- a/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
+++ b/services/core/java/com/android/server/hdmi/RequestArcTerminationAction.java
@@ -16,6 +16,8 @@
 
 package com.android.server.hdmi;
 
+import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 
 /**
@@ -35,6 +37,16 @@
         super(source, avrAddress);
     }
 
+    /**
+     * @Constructor
+     *
+     * @see RequestArcAction#RequestArcAction
+     */
+    RequestArcTerminationAction(HdmiCecLocalDevice source, int avrAddress,
+            IHdmiControlCallback callback) {
+        super(source, avrAddress, callback);
+    }
+
     @Override
     boolean start() {
         mState = STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE;
@@ -49,10 +61,34 @@
                     // If failed to send <Request ARC Termination>, start "Disabled" ARC
                     // transmission action.
                     disableArcTransmission();
-                    finish();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
                 }
             }
         });
         return true;
     }
+
+    @Override
+    boolean processCommand(HdmiCecMessage cmd) {
+        if (mState != STATE_WATING_FOR_REQUEST_ARC_REQUEST_RESPONSE
+                || !HdmiUtils.checkCommandSource(cmd, mAvrAddress, TAG)) {
+            return false;
+        }
+        int opcode = cmd.getOpcode();
+        switch (opcode) {
+            case Constants.MESSAGE_FEATURE_ABORT:
+                int originalOpcode = cmd.getParams()[0] & 0xFF;
+                if (originalOpcode == Constants.MESSAGE_REQUEST_ARC_TERMINATION) {
+                    disableArcTransmission();
+                    finishWithCallback(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+                    return true;
+                }
+                return false;
+            case Constants.MESSAGE_TERMINATE_ARC:
+                finishWithCallback(HdmiControlManager.RESULT_SUCCESS);
+                // This message still needs to be handled in HdmiCecLocalDeviceTv as well.
+                return false;
+        }
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
index b8f1db4..ddb19f0 100644
--- a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
@@ -182,8 +182,12 @@
             final String componentName = getComponentNameLocked();
             return new ComponentName[] { getServiceComponent(componentName) };
         }
+
         final String[] componentNames = mMaster.mServiceNameResolver.getServiceNameList(
                 mUserId);
+        if (componentNames == null) {
+            return null;
+        }
         ComponentName[] serviceComponents = new ComponentName[componentNames.length];
         for (int i = 0; i < componentNames.length; i++) {
             serviceComponents[i] = getServiceComponent(componentNames[i]);
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index d76da83..b8eb901 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -385,8 +385,10 @@
                                     R.styleable.KeyboardLayout_keyboardLayout,
                                     0);
                             String languageTags = a.getString(
-                                    R.styleable.KeyboardLayout_locale);
+                                    R.styleable.KeyboardLayout_keyboardLocale);
                             LocaleList locales = getLocalesFromLanguageTags(languageTags);
+                            int layoutType = a.getInt(R.styleable.KeyboardLayout_keyboardLayoutType,
+                                    0);
                             int vid = a.getInt(
                                     R.styleable.KeyboardLayout_vendorId, -1);
                             int pid = a.getInt(
@@ -403,7 +405,7 @@
                                 if (keyboardName == null || name.equals(keyboardName)) {
                                     KeyboardLayout layout = new KeyboardLayout(
                                             descriptor, label, collection, priority,
-                                            locales, vid, pid);
+                                            locales, layoutType, vid, pid);
                                     visitor.visitKeyboardLayout(
                                             resources, keyboardLayoutResId, layout);
                                 }
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
new file mode 100644
index 0000000..60167b4
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -0,0 +1,338 @@
+/*
+ * 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.inputmethod;
+
+import android.Manifest;
+import android.annotation.EnforcePermission;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.inputmethod.ImeTracker;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.view.IImeTracker;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayDeque;
+import java.util.Locale;
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Service for managing and logging {@link ImeTracker.Token} instances.
+ *
+ * @implNote Suppresses {@link GuardedBy} warnings, as linter reports that {@link #mHistory}
+ * interactions are guarded by {@code this} instead of {@code ImeTrackerService.this}, which should
+ * be identical.
+ *
+ * @hide
+ */
+@SuppressWarnings("GuardedBy")
+public final class ImeTrackerService extends IImeTracker.Stub {
+
+    static final String TAG = "ImeTrackerService";
+
+    /** The threshold in milliseconds after which a history entry is considered timed out. */
+    private static final long TIMEOUT_MS = 10_000;
+
+    /** Handler for registering timeouts for live entries. */
+    private final Handler mHandler =
+            new Handler(Looper.myLooper(), null /* callback */, true /* async */);
+
+    /** Singleton instance of the History. */
+    @GuardedBy("ImeTrackerService.this")
+    private final History mHistory = new History();
+
+    @NonNull
+    @Override
+    public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IBinder binder = new Binder();
+        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW,
+                ImeTracker.STATUS_RUN, origin, reason);
+        mHistory.addEntry(binder, entry);
+
+        // Register a delayed task to handle the case where the new entry times out.
+        mHandler.postDelayed(() -> {
+            synchronized (ImeTrackerService.this) {
+                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+            }
+        }, TIMEOUT_MS);
+
+        return binder;
+    }
+
+    @NonNull
+    @Override
+    public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
+            @SoftInputShowHideReason int reason) {
+        final IBinder binder = new Binder();
+        final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE,
+                ImeTracker.STATUS_RUN, origin, reason);
+        mHistory.addEntry(binder, entry);
+
+        // Register a delayed task to handle the case where the new entry times out.
+        mHandler.postDelayed(() -> {
+            synchronized (ImeTrackerService.this) {
+                mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET);
+            }
+        }, TIMEOUT_MS);
+
+        return binder;
+    }
+
+    @Override
+    public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        final History.Entry entry = mHistory.getEntry(statsToken);
+        if (entry == null) return;
+
+        entry.mPhase = phase;
+    }
+
+    @Override
+    public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
+    }
+
+    @Override
+    public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
+    }
+
+    @Override
+    public synchronized void onShown(@NonNull IBinder statsToken) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    }
+
+    @Override
+    public synchronized void onHidden(@NonNull IBinder statsToken) {
+        mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
+    }
+
+    /**
+     * Updates the IME request tracking token with new information available in IMMS.
+     *
+     * @param statsToken the token corresponding to the current IME request.
+     * @param requestWindowName the name of the window that created the IME request.
+     */
+    public synchronized void onImmsUpdate(@NonNull IBinder statsToken,
+            @NonNull String requestWindowName) {
+        final History.Entry entry = mHistory.getEntry(statsToken);
+        if (entry == null) return;
+
+        entry.mRequestWindowName = requestWindowName;
+    }
+
+    /** Dumps the contents of the history. */
+    public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+        mHistory.dump(pw, prefix);
+    }
+
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
+    @Override
+    public synchronized boolean hasPendingImeVisibilityRequests() {
+        super.hasPendingImeVisibilityRequests_enforcePermission();
+
+        return !mHistory.mLiveEntries.isEmpty();
+    }
+
+    /**
+     * A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
+     */
+    private static final class History {
+
+        /** The circular buffer's capacity. */
+        private static final int CAPACITY = 100;
+
+        /** Backing store for the circular buffer. */
+        @GuardedBy("ImeTrackerService.this")
+        private final ArrayDeque<Entry> mEntries = new ArrayDeque<>(CAPACITY);
+
+        /** Backing store for the live entries (i.e. entries that are not finished yet). */
+        @GuardedBy("ImeTrackerService.this")
+        private final WeakHashMap<IBinder, Entry> mLiveEntries = new WeakHashMap<>();
+
+        /** Latest entry sequence number. */
+        private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
+
+        /** Adds a live entry. */
+        @GuardedBy("ImeTrackerService.this")
+        private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
+            mLiveEntries.put(statsToken, entry);
+        }
+
+        /** Gets the entry corresponding to the given IME tracking token, if it exists. */
+        @Nullable
+        @GuardedBy("ImeTrackerService.this")
+        private Entry getEntry(@NonNull IBinder statsToken) {
+            return mLiveEntries.get(statsToken);
+        }
+
+        /**
+         * Sets the live entry corresponding to the tracking token, if it exists, as finished,
+         * and uploads the data for metrics.
+         *
+         * @param statsToken the token corresponding to the current IME request.
+         * @param status the finish status of the IME request.
+         * @param phase the phase the IME request finished at, if it exists
+         *              (or {@link ImeTracker#PHASE_NOT_SET} otherwise).
+         */
+        @GuardedBy("ImeTrackerService.this")
+        private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status,
+                @ImeTracker.Phase int phase) {
+            final Entry entry = mLiveEntries.remove(statsToken);
+            if (entry == null) return;
+
+            entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
+            entry.mStatus = status;
+
+            if (phase != ImeTracker.PHASE_NOT_SET) {
+                entry.mPhase = phase;
+            }
+
+            // Remove excess entries overflowing capacity (plus one for the new entry).
+            while (mEntries.size() >= CAPACITY) {
+                mEntries.remove();
+            }
+
+            mEntries.offer(entry);
+
+            // Log newly finished entry.
+            FrameworkStatsLog.write(FrameworkStatsLog.IME_REQUEST_FINISHED, entry.mUid,
+                    entry.mDuration, entry.mType, entry.mStatus, entry.mReason,
+                    entry.mOrigin, entry.mPhase);
+        }
+
+        /** Dumps the contents of the circular buffer. */
+        @GuardedBy("ImeTrackerService.this")
+        private void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
+
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History.mLiveEntries:");
+
+            for (final Entry entry: mLiveEntries.values()) {
+                dumpEntry(entry, pw, prefix, formatter);
+            }
+
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History.mEntries:");
+
+            for (final Entry entry: mEntries) {
+                dumpEntry(entry, pw, prefix, formatter);
+            }
+        }
+
+        @GuardedBy("ImeTrackerService.this")
+        private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
+                @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
+            pw.print(prefix);
+            pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":");
+
+            pw.print(prefix);
+            pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+
+            pw.print(prefix);
+            pw.println(" duration=" + entry.mDuration + "ms");
+
+            pw.print(prefix);
+            pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType));
+
+            pw.print(prefix);
+            pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus));
+
+            pw.print(prefix);
+            pw.print(" origin="
+                    + ImeTracker.Debug.originToString(entry.mOrigin));
+
+            pw.print(prefix);
+            pw.print(" reason="
+                    + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+
+            pw.print(prefix);
+            pw.print(" phase="
+                    + ImeTracker.Debug.phaseToString(entry.mPhase));
+
+            pw.print(prefix);
+            pw.print(" requestWindowName=" + entry.mRequestWindowName);
+        }
+
+        /** A history entry. */
+        private static final class Entry {
+
+            /** The entry's sequence number in the history. */
+            private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
+
+            /** Uid of the client that requested the IME. */
+            private final int mUid;
+
+            /** Clock time in milliseconds when the IME request was created. */
+            private final long mStartTime = System.currentTimeMillis();
+
+            /** Duration in milliseconds of the IME request from start to end. */
+            private long mDuration = 0;
+
+            /** Type of the IME request. */
+            @ImeTracker.Type
+            private final int mType;
+
+            /** Status of the IME request. */
+            @ImeTracker.Status
+            private int mStatus;
+
+            /** Origin of the IME request. */
+            @ImeTracker.Origin
+            private final int mOrigin;
+
+            /** Reason for creating the IME request. */
+            @SoftInputShowHideReason
+            private final int mReason;
+
+            /** Latest phase of the IME request. */
+            @ImeTracker.Phase
+            private int mPhase = ImeTracker.PHASE_NOT_SET;
+
+            /**
+             * Name of the window that created the IME request.
+             *
+             * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
+             */
+            @NonNull
+            private String mRequestWindowName = "not set";
+
+            private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status,
+                    @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) {
+                mUid = uid;
+                mType = type;
+                mStatus = status;
+                mOrigin = origin;
+                mReason = reason;
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 97c8305..c15b538 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -176,6 +176,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.view.IImeTracker;
 import com.android.internal.view.IInputMethodManager;
 import com.android.server.AccessibilityManagerInternal;
 import com.android.server.EventLogTags;
@@ -183,6 +184,7 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemServerInitThreadPool;
 import com.android.server.SystemService;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
@@ -197,11 +199,12 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.security.InvalidParameterException;
-import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Date;
 import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
@@ -304,6 +307,8 @@
      */
     @Nullable
     private AudioManagerInternal mAudioManagerInternal = null;
+    @Nullable
+    private VirtualDeviceManagerInternal mVdmInternal = null;
 
     // All known input methods.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
@@ -916,8 +921,9 @@
         }
 
         void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final SimpleDateFormat dataFormat =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
 
             for (int i = 0; i < mEntries.length; ++i) {
                 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -928,7 +934,7 @@
                 pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")");
 
                 pw.print(prefix);
@@ -996,7 +1002,7 @@
         private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
 
         /**
-         * Entry size for non low-RAM devices.
+         * Entry size for low-RAM devices.
          *
          * <p>TODO: Consider to follow what other system services have been doing to manage
          * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
@@ -1012,7 +1018,7 @@
         }
 
         /**
-         * Backing store for the ring bugger.
+         * Backing store for the ring buffer.
          */
         private final Entry[] mEntries = new Entry[getEntrySize()];
 
@@ -1092,8 +1098,9 @@
         }
 
         void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-            final SimpleDateFormat dataFormat =
-                    new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+            final DateTimeFormatter formatter =
+                    DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
+                            .withZone(ZoneId.systemDefault());
 
             for (int i = 0; i < mEntries.length; ++i) {
                 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -1104,7 +1111,7 @@
                 pw.println("StartInput #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
+                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")"
                         + " reason="
                         + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
@@ -1146,6 +1153,9 @@
     private final SoftInputShowHideHistory mSoftInputShowHideHistory =
             new SoftInputShowHideHistory();
 
+    @NonNull
+    private final ImeTrackerService mImeTrackerService = new ImeTrackerService();
+
     class SettingsObserver extends ContentObserver {
         int mUserId;
         boolean mRegistered = false;
@@ -2533,6 +2543,16 @@
         mCurVirtualDisplayToScreenMatrix =
                 getVirtualDisplayToScreenMatrixLocked(cs.mSelfReportedDisplayId,
                         mDisplayIdToShowIme);
+        // Override the locale hints if the app is running on a virtual device.
+        if (mVdmInternal == null) {
+            mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
+        }
+        if (mVdmInternal != null && editorInfo.hintLocales == null) {
+            LocaleList hintsFromVirtualDevice = mVdmInternal.getPreferredLocaleListForUid(cs.mUid);
+            if (hintsFromVirtualDevice != null) {
+                editorInfo.hintLocales = hintsFromVirtualDevice;
+            }
+        }
         mCurEditorInfo = editorInfo;
 
         // If configured, we want to avoid starting up the IME if it is not supposed to be showing
@@ -3392,13 +3412,11 @@
             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            String packageName = null;
-            if (mCurEditorInfo != null) {
-                packageName = mCurEditorInfo.packageName;
-            }
-            statsToken = new ImeTracker.Token(packageName);
-            ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT,
-                    reason);
+            // TODO(b/261565259): to avoid using null, add package name in ClientState
+            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+            final int uid = mCurClient != null ? mCurClient.mUid : -1;
+            statsToken = ImeTracker.get().onRequestShow(packageName, uid,
+                    ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
         }
 
         mShowRequested = true;
@@ -3448,7 +3466,7 @@
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                 }
-                onShowHideSoftInputRequested(true /* show */, windowToken, reason);
+                onShowHideSoftInputRequested(true /* show */, windowToken, reason, statsToken);
             }
             mInputShown = true;
             return true;
@@ -3495,12 +3513,18 @@
             int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
         // Create statsToken is none exists.
         if (statsToken == null) {
-            String packageName = null;
-            if (mCurEditorInfo != null) {
-                packageName = mCurEditorInfo.packageName;
+            // TODO(b/261565259): to avoid using null, add package name in ClientState
+            final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
+            final int uid;
+            if (mCurClient != null) {
+                uid = mCurClient.mUid;
+            } else if (mCurFocusedWindowClient != null) {
+                uid = mCurFocusedWindowClient.mUid;
+            } else {
+                uid = -1;
             }
-            statsToken = new ImeTracker.Token(packageName);
-            ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
+            statsToken = ImeTracker.get().onRequestHide(packageName, uid,
+                    ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
         }
 
         if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -3552,7 +3576,7 @@
                             InputMethodDebug.softInputDisplayReasonToString(reason),
                             InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                 }
-                onShowHideSoftInputRequested(false /* show */, windowToken, reason);
+                onShowHideSoftInputRequested(false /* show */, windowToken, reason, statsToken);
             }
             res = true;
         } else {
@@ -4768,6 +4792,7 @@
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
         synchronized (ImfLock.class) {
             if (!calledWithValidTokenLocked(token)) {
+                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                 return;
             }
             if (!setVisible) {
@@ -4833,7 +4858,7 @@
     /** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
     @GuardedBy("ImfLock.class")
     private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
-            @SoftInputShowHideReason int reason) {
+            @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
         final WindowManagerInternal.ImeTargetInfo info =
                 mWindowManagerInternal.onToggleImeRequested(
                         show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
@@ -4842,6 +4867,8 @@
                 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
                 info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
                 info.imeSurfaceParentName));
+
+        mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
     }
 
     @BinderThread
@@ -5981,6 +6008,9 @@
 
             p.println("  mSoftInputShowHideHistory:");
             mSoftInputShowHideHistory.dump(pw, "   ");
+
+            p.println("  mImeTrackerService#History:");
+            mImeTrackerService.dump(pw, "   ");
         }
 
         // Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6571,6 +6601,12 @@
         return true;
     }
 
+    /** @hide */
+    @Override
+    public IImeTracker getImeTrackerService() {
+        return mImeTrackerService;
+    }
+
     private static final class InputMethodPrivilegedOperationsImpl
             extends IInputMethodPrivilegedOperations.Stub {
         private final InputMethodManagerService mImms;
diff --git a/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java b/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java
new file mode 100644
index 0000000..3b5f340
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/AuthSecretHidlAdapter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.hardware.authsecret.IAuthSecret;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+
+/**
+ * Adapt the legacy HIDL interface to present the AIDL interface.
+ */
+class AuthSecretHidlAdapter implements IAuthSecret {
+    // private final String TAG = "AuthSecretHidlAdapter";
+    private final android.hardware.authsecret.V1_0.IAuthSecret mImpl;
+
+    AuthSecretHidlAdapter(android.hardware.authsecret.V1_0.IAuthSecret impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public void setPrimaryUserCredential(byte[] secret) throws RemoteException {
+        final ArrayList<Byte> secretAsArrayList = new ArrayList<>(secret.length);
+        for (int i = 0; i < secret.length; ++i) {
+            secretAsArrayList.add(secret[i]);
+        }
+        mImpl.primaryUserCredential(secretAsArrayList);
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        // Supports only V1
+        return 1;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        throw new UnsupportedOperationException("AuthSecretHidlAdapter does not support asBinder");
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        throw new UnsupportedOperationException(
+                "AuthSecretHidlAdapter does not support getInterfaceHash");
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 90f2a6a..121b7c8 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -173,7 +173,6 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
-import java.util.Random;
 import java.util.Set;
 import java.util.StringJoiner;
 import java.util.concurrent.CountDownLatch;
@@ -234,7 +233,6 @@
     private final SynchronizedStrongAuthTracker mStrongAuthTracker;
     private final BiometricDeferredQueue mBiometricDeferredQueue;
     private final LongSparseArray<byte[]> mGatekeeperPasswords;
-    private final Random mRandom;
 
     private final NotificationManager mNotificationManager;
     protected final UserManager mUserManager;
@@ -267,8 +265,7 @@
     protected boolean mHasSecureLockScreen;
 
     protected IGateKeeperService mGateKeeperService;
-    protected IAuthSecret mAuthSecretServiceAidl;
-    protected android.hardware.authsecret.V1_0.IAuthSecret mAuthSecretServiceHidl;
+    protected IAuthSecret mAuthSecretService;
 
     private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
 
@@ -349,23 +346,17 @@
     }
 
     private LockscreenCredential generateRandomProfilePassword() {
-        byte[] randomLockSeed = new byte[] {};
-        try {
-            randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
-            char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
-            byte[] newPassword = new byte[newPasswordChars.length];
-            for (int i = 0; i < newPasswordChars.length; i++) {
-                newPassword[i] = (byte) newPasswordChars[i];
-            }
-            LockscreenCredential credential =
-                    LockscreenCredential.createManagedPassword(newPassword);
-            Arrays.fill(newPasswordChars, '\u0000');
-            Arrays.fill(newPassword, (byte) 0);
-            Arrays.fill(randomLockSeed, (byte) 0);
-            return credential;
-        } catch (NoSuchAlgorithmException e) {
-            throw new IllegalStateException("Fail to generate profile password", e);
+        byte[] randomLockSeed = SecureRandomUtils.randomBytes(40);
+        char[] newPasswordChars = HexEncoding.encode(randomLockSeed);
+        byte[] newPassword = new byte[newPasswordChars.length];
+        for (int i = 0; i < newPasswordChars.length; i++) {
+            newPassword[i] = (byte) newPasswordChars[i];
         }
+        LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+        Arrays.fill(newPasswordChars, '\u0000');
+        Arrays.fill(newPassword, (byte) 0);
+        Arrays.fill(randomLockSeed, (byte) 0);
+        return credential;
     }
 
     /**
@@ -375,7 +366,7 @@
      * @param profileUserId  profile user Id
      * @param profileUserPassword  profile original password (when it has separated lock).
      */
-    public void tieProfileLockIfNecessary(int profileUserId,
+    private void tieProfileLockIfNecessary(int profileUserId,
             LockscreenCredential profileUserPassword) {
         if (DEBUG) Slog.v(TAG, "Check child profile lock for user: " + profileUserId);
         // Only for profiles that shares credential with parent
@@ -598,7 +589,6 @@
         mStrongAuthTracker = injector.getStrongAuthTracker();
         mStrongAuthTracker.register(mStrongAuth);
         mGatekeeperPasswords = new LongSparseArray<>();
-        mRandom = new SecureRandom();
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
         mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
@@ -713,7 +703,8 @@
             userHandle);
     }
 
-    public void onCleanupUser(int userId) {
+    @VisibleForTesting
+    void onCleanupUser(int userId) {
         hideEncryptionNotification(new UserHandle(userId));
         // User is stopped with its CE key evicted. Restore strong auth requirement to the default
         // flags after boot since stopping and restarting a user later is equivalent to rebooting
@@ -725,7 +716,7 @@
         }
     }
 
-    public void onStartUser(final int userId) {
+    private void onStartUser(final int userId) {
         maybeShowEncryptionNotificationForUser(userId, "user started");
     }
 
@@ -779,7 +770,7 @@
         }
     }
 
-    public void onUnlockUser(final int userId) {
+    private void onUnlockUser(final int userId) {
         // Perform tasks which require locks in LSS on a handler, as we are callbacks from
         // ActivityManager.unlockUser()
         mHandler.post(new Runnable() {
@@ -836,16 +827,19 @@
     }
 
     private void getAuthSecretHal() {
-        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");
-
+        mAuthSecretService =
+                IAuthSecret.Stub.asInterface(
+                        ServiceManager.waitForDeclaredService(IAuthSecret.DESCRIPTOR + "/default"));
+        if (mAuthSecretService != null) {
+            Slog.i(TAG, "Device implements AIDL AuthSecret HAL");
+        } else {
             try {
-                mAuthSecretServiceHidl =
-                    android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+                android.hardware.authsecret.V1_0.IAuthSecret authSecretServiceHidl =
+                        android.hardware.authsecret.V1_0.IAuthSecret.getService(/* retry */ true);
+                mAuthSecretService = new AuthSecretHidlAdapter(authSecretServiceHidl);
+                Slog.i(TAG, "Device implements HIDL AuthSecret HAL");
             } catch (NoSuchElementException e) {
-                Slog.i(TAG, "Device doesn't implement AuthSecret HAL(hidl)");
+                Slog.i(TAG, "Device doesn't implement AuthSecret HAL");
             } catch (RemoteException e) {
                 Slog.w(TAG, "Failed to get AuthSecret HAL(hidl)", e);
             }
@@ -1222,7 +1216,7 @@
      * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
      * {@link #CREDENTIAL_TYPE_PASSWORD}
      */
-    public int getCredentialTypeInternal(int userId) {
+    private int getCredentialTypeInternal(int userId) {
         if (userId == USER_FRP) {
             return getFrpCredentialType();
         }
@@ -1242,7 +1236,8 @@
 
     private int getFrpCredentialType() {
         PersistentData data = mStorage.readPersistentDataBlock();
-        if (data.type != PersistentData.TYPE_SP && data.type != PersistentData.TYPE_SP_WEAVER) {
+        if (data.type != PersistentData.TYPE_SP_GATEKEEPER &&
+                data.type != PersistentData.TYPE_SP_WEAVER) {
             return CREDENTIAL_TYPE_NONE;
         }
         int credentialType = SyntheticPasswordManager.getFrpCredentialType(data.payload);
@@ -1687,10 +1682,6 @@
     }
 
     private void onPostPasswordChanged(LockscreenCredential newCredential, int userHandle) {
-        if (userHandle == UserHandle.USER_SYSTEM && isDeviceEncryptionEnabled() &&
-            shouldEncryptWithCredentials() && newCredential.isNone()) {
-            setCredentialRequiredToDecrypt(false);
-        }
         if (newCredential.isPattern()) {
             setBoolean(LockPatternUtils.PATTERN_EVER_CHOSEN_KEY, true, userHandle);
         }
@@ -1752,14 +1743,9 @@
     private String getSalt(int userId) {
         long salt = getLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, 0, userId);
         if (salt == 0) {
-            try {
-                salt = SecureRandom.getInstance("SHA1PRNG").nextLong();
-                setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
-                Slog.v(TAG, "Initialized lock password salt for user: " + userId);
-            } catch (NoSuchAlgorithmException e) {
-                // Throw an exception rather than storing a password we'll never be able to recover
-                throw new IllegalStateException("Couldn't get SecureRandom number", e);
-            }
+            salt = SecureRandomUtils.randomLong();
+            setLong(LockPatternUtils.LOCK_PASSWORD_SALT_KEY, salt, userId);
+            Slog.v(TAG, "Initialized lock password salt for user: " + userId);
         }
         return Long.toHexString(salt);
     }
@@ -1768,24 +1754,6 @@
         return mInjector.getDevicePolicyManager().getPasswordHistoryLength(null, userId);
     }
 
-    private static boolean isDeviceEncryptionEnabled() {
-        return StorageManager.isEncrypted();
-    }
-
-    private boolean shouldEncryptWithCredentials() {
-        return isCredentialRequiredToDecrypt() && !isDoNotAskCredentialsOnBootSet();
-    }
-
-    private boolean isDoNotAskCredentialsOnBootSet() {
-        return mInjector.getDevicePolicyManager().getDoNotAskCredentialsOnBoot();
-    }
-
-    private boolean isCredentialRequiredToDecrypt() {
-        final int value = Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, -1);
-        return value != 0;
-    }
-
     private UserManager getUserManagerFromCache(int userId) {
         UserHandle userHandle = UserHandle.of(userId);
         if (mUserManagerCache.containsKey(userHandle)) {
@@ -1802,17 +1770,11 @@
         }
     }
 
+    @VisibleForTesting /** Note: this method is overridden in unit tests */
     protected boolean isCredentialSharableWithParent(int userId) {
         return getUserManagerFromCache(userId).isCredentialSharableWithParent();
     }
 
-    private void setCredentialRequiredToDecrypt(boolean required) {
-        if (isDeviceEncryptionEnabled()) {
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    Settings.Global.REQUIRE_PASSWORD_TO_DECRYPT, required ? 1 : 0);
-        }
-    }
-
     /** Register the given WeakEscrowTokenRemovedListener. */
     @Override
     public boolean registerWeakEscrowTokenRemovedListener(
@@ -2560,7 +2522,7 @@
         }
     }
 
-    protected synchronized IGateKeeperService getGateKeeperService() {
+    private synchronized IGateKeeperService getGateKeeperService() {
         if (mGateKeeperService != null) {
             return mGateKeeperService;
         }
@@ -2604,25 +2566,16 @@
         // 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) {
+            return;
+        }
         if (userId == UserHandle.USER_SYSTEM &&
                 mUserManager.getUserInfo(userId).isPrimary()) {
-            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);
-                }
-            } 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);
-                }
+            final byte[] secret = sp.deriveVendorAuthSecret();
+            try {
+                mAuthSecretService.setPrimaryUserCredential(secret);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to pass primary user secret to AuthSecret HAL", e);
             }
         }
     }
@@ -2677,7 +2630,7 @@
 
         synchronized (mGatekeeperPasswords) {
             while (handle == 0L || mGatekeeperPasswords.get(handle) != null) {
-                handle = mRandom.nextLong();
+                handle = SecureRandomUtils.randomLong();
             }
             mGatekeeperPasswords.put(handle, gatekeeperPassword);
         }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 473c4b6..2c28af1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -587,7 +587,7 @@
         static final int VERSION_1_HEADER_SIZE = 1 + 1 + 4 + 4;
 
         public static final int TYPE_NONE = 0;
-        public static final int TYPE_SP = 1;
+        public static final int TYPE_SP_GATEKEEPER = 1;
         public static final int TYPE_SP_WEAVER = 2;
 
         public static final PersistentData NONE = new PersistentData(TYPE_NONE,
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index 1203769..b77670d 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -44,6 +44,7 @@
 /**
  * Keeps track of requests for strong authentication.
  */
+@VisibleForTesting // public visibility is needed for Mockito
 public class LockSettingsStrongAuth {
 
     private static final String TAG = "LockSettings";
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
index e43d4e8..ddc0e54 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
@@ -26,6 +26,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.widget.LockscreenCredential;
 
@@ -60,6 +61,7 @@
  * <p> The encrypted credential is stored in-memory only so the cache does not persist across
  * reboots.
  */
+@VisibleForTesting // public visibility is needed for Mockito
 public class ManagedProfilePasswordCache {
 
     private static final String TAG = "ManagedProfilePasswordCache";
diff --git a/services/core/java/com/android/server/locksettings/PasswordSlotManager.java b/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
index 21fb403..b039d5e 100644
--- a/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
+++ b/services/core/java/com/android/server/locksettings/PasswordSlotManager.java
@@ -43,7 +43,7 @@
  * If a /metadata partition does not exist, GSIs are not supported, and PasswordSlotManager will
  * simply not persist the slot mapping.
  */
-public class PasswordSlotManager {
+class PasswordSlotManager {
     private static final String TAG = "PasswordSlotManager";
 
     private static final String GSI_RUNNING_PROP = "ro.gsid.image_running";
diff --git a/services/core/java/com/android/server/locksettings/SecureRandomUtils.java b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
new file mode 100644
index 0000000..4ba4dd0
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/SecureRandomUtils.java
@@ -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.
+ */
+
+package com.android.server.locksettings;
+
+import java.security.SecureRandom;
+
+/** Utilities using a static SecureRandom */
+public class SecureRandomUtils {
+    private static final SecureRandom RNG = new SecureRandom();
+
+    /** Use SecureRandom to generate `length` random bytes */
+    public static byte[] randomBytes(int length) {
+        byte[] res = new byte[length];
+        RNG.nextBytes(res);
+        return res;
+    }
+
+    /** Use SecureRandom to generate a random long */
+    public static long randomLong() {
+        return RNG.nextLong();
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
index cb6e43c..b283726 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordCrypto.java
@@ -49,7 +49,7 @@
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.SecretKeySpec;
 
-public class SyntheticPasswordCrypto {
+class SyntheticPasswordCrypto {
     private static final String TAG = "SyntheticPasswordCrypto";
     private static final int AES_GCM_KEY_SIZE = 32; // AES-256-GCM
     private static final int AES_GCM_IV_SIZE = 12;
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 66ce429..cd972dc 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -59,8 +59,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.nio.ByteBuffer;
-import java.security.NoSuchAlgorithmException;
-import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
@@ -112,7 +110,7 @@
  *       WEAVER_SLOT: Contains the Weaver slot number used by this protector.  Only exists if the
  *                    protector uses Weaver.
  */
-public class SyntheticPasswordManager {
+class SyntheticPasswordManager {
     private static final String SP_BLOB_NAME = "spblob";
     private static final String SP_E0_NAME = "e0";
     private static final String SP_P1_NAME = "p1";
@@ -284,8 +282,10 @@
          */
         static SyntheticPassword create() {
             SyntheticPassword result = new SyntheticPassword(SYNTHETIC_PASSWORD_VERSION_V3);
-            byte[] escrowSplit0 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
-            byte[] escrowSplit1 = secureRandom(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+            byte[] escrowSplit0 =
+                    SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
+            byte[] escrowSplit1 =
+                    SecureRandomUtils.randomBytes(SYNTHETIC_PASSWORD_SECURITY_STRENGTH);
             result.recreate(escrowSplit0, escrowSplit1);
             byte[] encrypteEscrowSplit0 = SyntheticPasswordCrypto.encrypt(result.mSyntheticPassword,
                     PERSONALIZATION_E0, escrowSplit0);
@@ -347,7 +347,7 @@
             result.scryptLogR = PASSWORD_SCRYPT_LOG_R;
             result.scryptLogP = PASSWORD_SCRYPT_LOG_P;
             result.credentialType = credentialType;
-            result.salt = secureRandom(PASSWORD_SALT_LENGTH);
+            result.salt = SecureRandomUtils.randomBytes(PASSWORD_SALT_LENGTH);
             return result;
         }
 
@@ -394,7 +394,7 @@
         }
     }
 
-    static class SyntheticPasswordBlob {
+    private static class SyntheticPasswordBlob {
         byte mVersion;
         byte mProtectorType;
         byte[] mContent;
@@ -431,7 +431,7 @@
     static final int TOKEN_TYPE_STRONG = 0;
     static final int TOKEN_TYPE_WEAK = 1;
 
-    static class TokenData {
+    private static class TokenData {
         byte[] secdiscardableOnDisk;
         byte[] weaverSecret;
         byte[] aggregatedSecret;
@@ -490,7 +490,7 @@
             android.hardware.weaver.V1_0.IWeaver hidlWeaver = getWeaverHidlService();
             if (hidlWeaver != null) {
                 Slog.i(TAG, "Using HIDL weaver service");
-                return new WeaverHidlWrapper(hidlWeaver);
+                return new WeaverHidlAdapter(hidlWeaver);
             }
         } catch (RemoteException e) {
             Slog.w(TAG, "Failed to get HIDL weaver service.", e);
@@ -552,7 +552,7 @@
             throw new IllegalArgumentException("Invalid key size for weaver");
         }
         if (value == null) {
-            value = secureRandom(mWeaverConfig.valueSize);
+            value = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
         }
         try {
             mWeaver.write(slot, key, value);
@@ -900,7 +900,7 @@
             protectorSecret = transformUnderSecdiscardable(stretchedLskf,
                     createSecdiscardable(protectorId, userId));
             // No need to pass in quality since the credential type already encodes sufficient info
-            synchronizeFrpPassword(pwd, 0, userId);
+            synchronizeGatekeeperFrpPassword(pwd, 0, userId);
         }
         if (!credential.isNone()) {
             saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
@@ -916,7 +916,7 @@
             LockscreenCredential userCredential,
             ICheckCredentialProgressCallback progressCallback) {
         PersistentData persistentData = mStorage.readPersistentDataBlock();
-        if (persistentData.type == PersistentData.TYPE_SP) {
+        if (persistentData.type == PersistentData.TYPE_SP_GATEKEEPER) {
             PasswordData pwd = PasswordData.fromBytes(persistentData.payload);
             byte[] stretchedLskf = stretchLskf(userCredential, pwd);
 
@@ -941,7 +941,7 @@
 
             return weaverVerify(weaverSlot, stretchedLskfToWeaverKey(stretchedLskf)).stripPayload();
         } else {
-            Slog.e(TAG, "persistentData.type must be TYPE_SP or TYPE_SP_WEAVER, but is "
+            Slog.e(TAG, "persistentData.type must be TYPE_SP_GATEKEEPER or TYPE_SP_WEAVER, but is "
                     + persistentData.type);
             return VerifyCredentialResponse.ERROR;
         }
@@ -960,7 +960,7 @@
             if (weaverSlot != INVALID_WEAVER_SLOT) {
                 synchronizeWeaverFrpPassword(pwd, requestedQuality, userInfo.id, weaverSlot);
             } else {
-                synchronizeFrpPassword(pwd, requestedQuality, userInfo.id);
+                synchronizeGatekeeperFrpPassword(pwd, requestedQuality, userInfo.id);
             }
         }
     }
@@ -994,13 +994,13 @@
         return true;
     }
 
-    private void synchronizeFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
+    private void synchronizeGatekeeperFrpPassword(@Nullable PasswordData pwd, int requestedQuality,
             int userId) {
         if (shouldSynchronizeFrpCredential(pwd, userId)) {
             Slogf.d(TAG, "Syncing Gatekeeper-based FRP credential tied to user %d", userId);
             if (!isNoneCredential(pwd)) {
-                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
-                        pwd.toBytes());
+                mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_GATEKEEPER, userId,
+                        requestedQuality, pwd.toBytes());
             } else {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, userId, 0, null);
             }
@@ -1039,9 +1039,9 @@
         }
         TokenData tokenData = new TokenData();
         tokenData.mType = type;
-        final byte[] secdiscardable = secureRandom(SECDISCARDABLE_LENGTH);
+        final byte[] secdiscardable = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
         if (isWeaverAvailable()) {
-            tokenData.weaverSecret = secureRandom(mWeaverConfig.valueSize);
+            tokenData.weaverSecret = SecureRandomUtils.randomBytes(mWeaverConfig.valueSize);
             tokenData.secdiscardableOnDisk = SyntheticPasswordCrypto.encrypt(tokenData.weaverSecret,
                             PERSONALIZATION_WEAVER_TOKEN, secdiscardable);
         } else {
@@ -1224,7 +1224,7 @@
                             pwd.credentialType = credential.getType();
                             saveState(PASSWORD_DATA_NAME, pwd.toBytes(), protectorId, userId);
                             syncState(userId);
-                            synchronizeFrpPassword(pwd, 0, userId);
+                            synchronizeGatekeeperFrpPassword(pwd, 0, userId);
                         } else {
                             Slog.w(TAG, "Fail to re-enroll user password for user " + userId);
                             // continue the flow anyway
@@ -1510,7 +1510,7 @@
      * been created.
      */
     private byte[] createSecdiscardable(long protectorId, int userId) {
-        byte[] data = secureRandom(SECDISCARDABLE_LENGTH);
+        byte[] data = SecureRandomUtils.randomBytes(SECDISCARDABLE_LENGTH);
         saveSecdiscardable(protectorId, data, userId);
         return data;
     }
@@ -1623,13 +1623,13 @@
         SyntheticPasswordCrypto.destroyProtectorKey(keyAlias);
     }
 
-    public static long generateProtectorId() {
-        SecureRandom rng = new SecureRandom();
-        long result;
-        do {
-            result = rng.nextLong();
-        } while (result == NULL_PROTECTOR_ID);
-        return result;
+    private static long generateProtectorId() {
+        while (true) {
+            final long result = SecureRandomUtils.randomLong();
+            if (result != NULL_PROTECTOR_ID) {
+                return result;
+            }
+        }
     }
 
     @VisibleForTesting
@@ -1637,15 +1637,6 @@
         return 100000 + userId;
     }
 
-    protected static byte[] secureRandom(int length) {
-        try {
-            return SecureRandom.getInstance("SHA1PRNG").generateSeed(length);
-        } catch (NoSuchAlgorithmException e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
-
     private String getProtectorKeyAlias(long protectorId) {
         return TextUtils.formatSimple("%s%x", PROTECTOR_KEY_ALIAS_PREFIX, protectorId);
     }
@@ -1685,15 +1676,17 @@
         return Arrays.copyOf(key, mWeaverConfig.keySize);
     }
 
+    @VisibleForTesting
     protected long sidFromPasswordHandle(byte[] handle) {
         return nativeSidFromPasswordHandle(handle);
     }
 
+    @VisibleForTesting
     protected byte[] scrypt(byte[] password, byte[] salt, int n, int r, int p, int outLen) {
         return new Scrypt().scrypt(password, salt, n, r, p, outLen);
     }
 
-    native long nativeSidFromPasswordHandle(byte[] handle);
+    private native long nativeSidFromPasswordHandle(byte[] handle);
 
     @VisibleForTesting
     static byte[] bytesToHex(byte[] bytes) {
diff --git a/services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java b/services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java
index b06e381..b10dfb2 100644
--- a/services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java
+++ b/services/core/java/com/android/server/locksettings/VersionedPasswordMetrics.java
@@ -25,7 +25,7 @@
  * A versioned and serializable wrapper around {@link PasswordMetrics},
  * for long-term persistence on disk.
  */
-public class VersionedPasswordMetrics {
+class VersionedPasswordMetrics {
     private static final int VERSION_1 = 1;
 
     private final PasswordMetrics mMetrics;
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
new file mode 100644
index 0000000..2e9c3fd
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/WeaverHidlAdapter.java
@@ -0,0 +1,137 @@
+/*
+ * 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.locksettings;
+
+import android.hardware.weaver.IWeaver;
+import android.hardware.weaver.WeaverConfig;
+import android.hardware.weaver.WeaverReadResponse;
+import android.hardware.weaver.WeaverReadStatus;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/**
+ * Adapt the legacy HIDL interface to present the AIDL interface.
+ */
+class WeaverHidlAdapter implements IWeaver {
+    private static final String TAG = "WeaverHidlAdapter";
+    private final android.hardware.weaver.V1_0.IWeaver mImpl;
+
+    WeaverHidlAdapter(android.hardware.weaver.V1_0.IWeaver impl) {
+        mImpl = impl;
+    }
+
+    @Override
+    public WeaverConfig getConfig() throws RemoteException {
+        final WeaverConfig[] res = new WeaverConfig[1];
+        mImpl.getConfig((int status, android.hardware.weaver.V1_0.WeaverConfig config) -> {
+            if (status == android.hardware.weaver.V1_0.WeaverStatus.OK) {
+                WeaverConfig aidlRes = new WeaverConfig();
+                aidlRes.slots = config.slots;
+                aidlRes.keySize = config.keySize;
+                aidlRes.valueSize = config.valueSize;
+                res[0] = aidlRes;
+            } else {
+                Slog.e(TAG,
+                        "Failed to get HIDL weaver config. status: " + status
+                                + ", slots: " + config.slots);
+            }
+        });
+        return res[0];
+    }
+
+    @Override
+    public WeaverReadResponse read(int slotId, byte[] key)
+            throws RemoteException {
+        final WeaverReadResponse[] res = new WeaverReadResponse[1];
+        mImpl.read(
+                slotId, toByteArrayList(key),
+                (int inStatus, android.hardware.weaver.V1_0.WeaverReadResponse readResponse) -> {
+                    WeaverReadResponse aidlRes =
+                            new WeaverReadResponse();
+                    switch (inStatus) {
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.OK:
+                            aidlRes.status = WeaverReadStatus.OK;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.THROTTLE:
+                            aidlRes.status = WeaverReadStatus.THROTTLE;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.INCORRECT_KEY:
+                            aidlRes.status = WeaverReadStatus.INCORRECT_KEY;
+                            break;
+                        case android.hardware.weaver.V1_0.WeaverReadStatus.FAILED:
+                            aidlRes.status = WeaverReadStatus.FAILED;
+                            break;
+                        default:
+                            Slog.e(TAG, "Unexpected status in read: " + inStatus);
+                            aidlRes.status = WeaverReadStatus.FAILED;
+                            break;
+                    }
+                    aidlRes.timeout = readResponse.timeout;
+                    aidlRes.value = fromByteArrayList(readResponse.value);
+                    res[0] = aidlRes;
+                });
+        return res[0];
+    }
+
+    @Override
+    public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
+        int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
+        if (writeStatus != android.hardware.weaver.V1_0.WeaverStatus.OK) {
+            throw new ServiceSpecificException(
+                    IWeaver.STATUS_FAILED, "Failed IWeaver.write call, status: " + writeStatus);
+        }
+    }
+
+    @Override
+    public String getInterfaceHash() {
+        // We do not require the interface hash as the client.
+        throw new UnsupportedOperationException(
+                "WeaverHidlAdapter does not support getInterfaceHash");
+    }
+
+    @Override
+    public int getInterfaceVersion() {
+        // Supports only V2 which is at feature parity.
+        return 2;
+    }
+
+    @Override
+    public IBinder asBinder() {
+        // There is no IHwBinder to IBinder. Not required as the client.
+        throw new UnsupportedOperationException("WeaverHidlAdapter does not support asBinder");
+    }
+
+    private static ArrayList<Byte> toByteArrayList(byte[] data) {
+        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
+        for (int i = 0; i < data.length; i++) {
+            result.add(data[i]);
+        }
+        return result;
+    }
+
+    private static byte[] fromByteArrayList(ArrayList<Byte> data) {
+        byte[] result = new byte[data.size()];
+        for (int i = 0; i < data.size(); i++) {
+            result[i] = data.get(i);
+        }
+        return result;
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java b/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
deleted file mode 100644
index 9d93c3d..0000000
--- a/services/core/java/com/android/server/locksettings/WeaverHidlWrapper.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings;
-
-import android.hardware.weaver.V1_0.IWeaver;
-import android.hardware.weaver.V1_0.WeaverConfig;
-import android.hardware.weaver.V1_0.WeaverReadResponse;
-import android.hardware.weaver.V1_0.WeaverReadStatus;
-import android.hardware.weaver.V1_0.WeaverStatus;
-import android.os.RemoteException;
-import android.os.ServiceSpecificException;
-import android.util.Slog;
-
-import java.util.ArrayList;
-
-/**
- * Implement the AIDL IWeaver interface wrapping the HIDL implementation
- */
-class WeaverHidlWrapper implements android.hardware.weaver.IWeaver {
-    private static final String TAG = "WeaverHidlWrapper";
-    private final IWeaver mImpl;
-
-    WeaverHidlWrapper(IWeaver impl) {
-        mImpl = impl;
-    }
-
-    private static ArrayList<Byte> toByteArrayList(byte[] data) {
-        ArrayList<Byte> result = new ArrayList<Byte>(data.length);
-        for (int i = 0; i < data.length; i++) {
-            result.add(data[i]);
-        }
-        return result;
-    }
-
-    private static byte[] fromByteArrayList(ArrayList<Byte> data) {
-        byte[] result = new byte[data.size()];
-        for (int i = 0; i < data.size(); i++) {
-            result[i] = data.get(i);
-        }
-        return result;
-    }
-
-    @Override
-    public String getInterfaceHash() {
-        // We do not require the interface hash as the client.
-        throw new UnsupportedOperationException(
-            "WeaverHidlWrapper does not support getInterfaceHash");
-    }
-    @Override
-    public int getInterfaceVersion() {
-        // Supports only V2 which is at feature parity.
-        return 2;
-    }
-    @Override
-    public android.os.IBinder asBinder() {
-        // There is no IHwBinder to IBinder. Not required as the client.
-        throw new UnsupportedOperationException("WeaverHidlWrapper does not support asBinder");
-    }
-
-    @Override
-    public android.hardware.weaver.WeaverConfig getConfig() throws RemoteException {
-        final WeaverConfig[] res = new WeaverConfig[1];
-        mImpl.getConfig((int status, WeaverConfig config) -> {
-            if (status == WeaverStatus.OK && config.slots > 0) {
-                res[0] = config;
-            } else {
-                res[0] = null;
-                Slog.e(TAG,
-                        "Failed to get HIDL weaver config. status: " + status
-                                + ", slots: " + config.slots);
-            }
-        });
-
-        if (res[0] == null) {
-            return null;
-        }
-        android.hardware.weaver.WeaverConfig config = new android.hardware.weaver.WeaverConfig();
-        config.slots = res[0].slots;
-        config.keySize = res[0].keySize;
-        config.valueSize = res[0].valueSize;
-        return config;
-    }
-
-    @Override
-    public android.hardware.weaver.WeaverReadResponse read(int slotId, byte[] key)
-            throws RemoteException {
-        final WeaverReadResponse[] res = new WeaverReadResponse[1];
-        final int[] status = new int[1];
-        mImpl.read(
-                slotId, toByteArrayList(key), (int inStatus, WeaverReadResponse readResponse) -> {
-                    status[0] = inStatus;
-                    res[0] = readResponse;
-                });
-
-        android.hardware.weaver.WeaverReadResponse aidlRes =
-                new android.hardware.weaver.WeaverReadResponse();
-        switch (status[0]) {
-            case WeaverReadStatus.OK:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.OK;
-                break;
-            case WeaverReadStatus.THROTTLE:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.THROTTLE;
-                break;
-            case WeaverReadStatus.INCORRECT_KEY:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.INCORRECT_KEY;
-                break;
-            case WeaverReadStatus.FAILED:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
-                break;
-            default:
-                aidlRes.status = android.hardware.weaver.WeaverReadStatus.FAILED;
-                break;
-        }
-        if (res[0] != null) {
-            aidlRes.timeout = res[0].timeout;
-            aidlRes.value = fromByteArrayList(res[0].value);
-        }
-        return aidlRes;
-    }
-
-    @Override
-    public void write(int slotId, byte[] key, byte[] value) throws RemoteException {
-        int writeStatus = mImpl.write(slotId, toByteArrayList(key), toByteArrayList(value));
-        if (writeStatus != WeaverStatus.OK) {
-            throw new ServiceSpecificException(
-                android.hardware.weaver.IWeaver.STATUS_FAILED, "Failed IWeaver.write call");
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 50e1fca..e9ee750 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -370,6 +370,26 @@
             }
         }
 
+        @Override
+        public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
+                    != PackageManager.PERMISSION_GRANTED) {
+                throw new SecurityException("Requires MANAGE_MEDIA_PROJECTION in order to notify "
+                        + "on captured content resize");
+            }
+            if (!isValidMediaProjection(mProjectionGrant)) {
+                return;
+            }
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mProjectionGrant != null && mCallbackDelegate != null) {
+                    mCallbackDelegate.dispatchVisibilityChanged(mProjectionGrant, isVisible);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         @Override //Binder call
         public void addCallback(final IMediaProjectionWatcherCallback callback) {
             if (mContext.checkCallingPermission(Manifest.permission.MANAGE_MEDIA_PROJECTION)
@@ -750,8 +770,9 @@
 
         public void dispatchResize(MediaProjection projection, int width, int height) {
             if (projection == null) {
-                Slog.e(TAG, "Tried to dispatch stop notification for a null media projection."
-                        + " Ignoring!");
+                Slog.e(TAG,
+                        "Tried to dispatch resize notification for a null media projection. "
+                                + "Ignoring!");
                 return;
             }
             synchronized (mLock) {
@@ -774,6 +795,36 @@
                 // is for passing along if recording is still ongoing or not.
             }
         }
+
+        public void dispatchVisibilityChanged(MediaProjection projection, boolean isVisible) {
+            if (projection == null) {
+                Slog.e(TAG,
+                        "Tried to dispatch visibility changed notification for a null media "
+                                + "projection. Ignoring!");
+                return;
+            }
+            synchronized (mLock) {
+                // TODO(b/249827847) Currently the service assumes there is only one projection
+                //  at once - need to find the callback for the given projection, when there are
+                //  multiple sessions.
+                for (IMediaProjectionCallback callback : mClientCallbacks.values()) {
+                    mHandler.post(() -> {
+                        try {
+                            // Notify every callback the client has registered for a particular
+                            // MediaProjection instance.
+                            callback.onCapturedContentVisibilityChanged(isVisible);
+                        } catch (RemoteException e) {
+                            Slog.w(TAG,
+                                    "Failed to notify media projection has captured content "
+                                            + "visibility change to "
+                                            + isVisible, e);
+                        }
+                    });
+                }
+                // Do not need to notify watcher callback about visibility changes, since watcher
+                // callback is for passing along if recording is still ongoing or not.
+            }
+        }
     }
 
     private static final class WatcherStartCallback implements Runnable {
diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java
index b611b5d..25a39cc 100644
--- a/services/core/java/com/android/server/om/IdmapManager.java
+++ b/services/core/java/com/android/server/om/IdmapManager.java
@@ -21,6 +21,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayableInfo;
 import android.os.Build.VERSION_CODES;
@@ -32,6 +33,7 @@
 import android.util.Slog;
 
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
@@ -99,16 +101,17 @@
      * </ul>.
      */
     @IdmapStatus int createIdmap(@NonNull final AndroidPackage targetPackage,
-            @NonNull final AndroidPackage overlayPackage, String overlayBasePath,
-            String overlayName, int userId) {
+            @NonNull PackageState overlayPackageState, @NonNull final AndroidPackage overlayPackage,
+            String overlayBasePath, String overlayName, @UserIdInt int userId) {
         if (DEBUG) {
             Slog.d(TAG, "create idmap for " + targetPackage.getPackageName() + " and "
                     + overlayPackage.getPackageName());
         }
-        final String targetPath = targetPackage.getBaseApkPath();
+        final String targetPath = targetPackage.getSplits().get(0).getPath();
         try {
-            int policies = calculateFulfilledPolicies(targetPackage, overlayPackage, userId);
-            boolean enforce = enforceOverlayable(overlayPackage);
+            int policies = calculateFulfilledPolicies(targetPackage, overlayPackageState,
+                    overlayPackage, userId);
+            boolean enforce = enforceOverlayable(overlayPackageState, overlayPackage);
             if (mIdmapDaemon.verifyIdmap(targetPath, overlayBasePath, overlayName, policies,
                     enforce, userId)) {
                 return IDMAP_IS_VERIFIED;
@@ -174,13 +177,14 @@
      * Checks if overlayable and policies should be enforced on the specified overlay for backwards
      * compatibility with pre-Q overlays.
      */
-    private boolean enforceOverlayable(@NonNull final AndroidPackage overlayPackage) {
+    private boolean enforceOverlayable(@NonNull PackageState overlayPackageState,
+            @NonNull final AndroidPackage overlayPackage) {
         if (overlayPackage.getTargetSdkVersion() >= VERSION_CODES.Q) {
             // Always enforce policies for overlays targeting Q+.
             return true;
         }
 
-        if (overlayPackage.isVendor()) {
+        if (overlayPackageState.isVendor()) {
             // If the overlay is on a pre-Q vendor partition, do not enforce overlayable
             // restrictions on this overlay because the pre-Q platform has no understanding of
             // overlayable.
@@ -189,14 +193,15 @@
 
         // Do not enforce overlayable restrictions on pre-Q overlays that are signed with the
         // platform signature or that are preinstalled.
-        return !(overlayPackage.isSystem() || overlayPackage.isSignedWithPlatformKey());
+        return !(overlayPackageState.isSystem() || overlayPackage.isSignedWithPlatformKey());
     }
 
     /**
      * Retrieves a bitmask for idmap2 that represents the policies the overlay fulfills.
      */
     private int calculateFulfilledPolicies(@NonNull final AndroidPackage targetPackage,
-            @NonNull final AndroidPackage overlayPackage, int userId)  {
+            @NonNull PackageState overlayPackageState, @NonNull final AndroidPackage overlayPackage,
+            @UserIdInt int userId)  {
         int fulfilledPolicies = OverlayablePolicy.PUBLIC;
 
         // Overlay matches target signature
@@ -221,28 +226,28 @@
         }
 
         // Vendor partition (/vendor)
-        if (overlayPackage.isVendor()) {
+        if (overlayPackageState.isVendor()) {
             return fulfilledPolicies | OverlayablePolicy.VENDOR_PARTITION;
         }
 
         // Product partition (/product)
-        if (overlayPackage.isProduct()) {
+        if (overlayPackageState.isProduct()) {
             return fulfilledPolicies | OverlayablePolicy.PRODUCT_PARTITION;
         }
 
         // Odm partition (/odm)
-        if (overlayPackage.isOdm()) {
+        if (overlayPackageState.isOdm()) {
             return fulfilledPolicies | OverlayablePolicy.ODM_PARTITION;
         }
 
         // Oem partition (/oem)
-        if (overlayPackage.isOem()) {
+        if (overlayPackageState.isOem()) {
             return fulfilledPolicies | OverlayablePolicy.OEM_PARTITION;
         }
 
         // System_ext partition (/system_ext) is considered as system
         // Check this last since every partition except for data is scanned as system in the PMS.
-        if (overlayPackage.isSystem() || overlayPackage.isSystemExt()) {
+        if (overlayPackageState.isSystem() || overlayPackageState.isSystemExt()) {
             return fulfilledPolicies | OverlayablePolicy.SYSTEM_PARTITION;
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
index c35d8631..015b7fd 100644
--- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java
+++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java
@@ -28,7 +28,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
-import com.android.server.pm.pkg.AndroidPackage;
 
 import java.io.IOException;
 import java.util.List;
@@ -112,13 +111,13 @@
         }
 
         final String targetPackageName = overlayInfo.targetPackageName;
-        final AndroidPackage targetPkgInfo = mPackageManager.getPackageForUser(targetPackageName,
-                userId);
-        if (targetPkgInfo == null) {
+        var targetPkgState = mPackageManager.getPackageStateForUser(targetPackageName, userId);
+        var targetPkg = targetPkgState == null ? null : targetPkgState.getAndroidPackage();
+        if (targetPkg == null) {
             return ActorState.TARGET_NOT_FOUND;
         }
 
-        if (targetPkgInfo.isDebuggable()) {
+        if (targetPkg.isDebuggable()) {
             return ActorState.ALLOWED;
         }
 
@@ -189,13 +188,13 @@
         }
 
         String actorPackageName = actorUriPair.first;
-        AndroidPackage actorPackage = mPackageManager.getPackageForUser(actorPackageName, userId);
-        if (actorPackage == null) {
+        var actorPackageState = mPackageManager.getPackageStateForUser(actorPackageName, userId);
+        if (actorPackageState == null || actorPackageState.getAndroidPackage() == null) {
             return ActorState.ACTOR_NOT_FOUND;
         }
 
         // Currently only pre-installed apps can be actors
-        if (!actorPackage.isSystem()) {
+        if (!actorPackageState.isSystem()) {
             return ActorState.ACTOR_NOT_PREINSTALLED;
         }
 
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index 9022a885..062f0fc 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -91,7 +91,7 @@
 import com.android.server.SystemService;
 import com.android.server.pm.KnownPackages;
 import com.android.server.pm.UserManagerService;
-import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import libcore.util.EmptyArray;
 
@@ -426,9 +426,9 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageAdded " + packageName);
                 for (final int userId : userIds) {
                     synchronized (mLock) {
-                        final AndroidPackage pkg = mPackageManager.onPackageAdded(
-                                packageName, userId);
-                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
+                        var packageState = mPackageManager.onPackageAdded(packageName, userId);
+                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                                userId)) {
                             try {
                                 updateTargetPackagesLocked(
                                         mImpl.onPackageAdded(packageName, userId));
@@ -449,9 +449,9 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageChanged " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
-                                packageName, userId);
-                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
+                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                                userId)) {
                             try {
                                 updateTargetPackagesLocked(
                                         mImpl.onPackageChanged(packageName, userId));
@@ -472,9 +472,9 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplacing " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
-                                packageName, userId);
-                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
+                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                                userId)) {
                             try {
                                 updateTargetPackagesLocked(mImpl.onPackageReplacing(packageName,
                                         systemUpdateUninstall, userId));
@@ -495,9 +495,9 @@
                 traceBegin(TRACE_TAG_RRO, "OMS#onPackageReplaced " + packageName);
                 for (int userId : userIds) {
                     synchronized (mLock) {
-                        final AndroidPackage pkg = mPackageManager.onPackageUpdated(
-                                packageName, userId);
-                        if (pkg != null && !mPackageManager.isInstantApp(packageName, userId)) {
+                        var packageState = mPackageManager.onPackageUpdated(packageName, userId);
+                        if (packageState != null && !mPackageManager.isInstantApp(packageName,
+                                userId)) {
                             try {
                                 updateTargetPackagesLocked(
                                         mImpl.onPackageReplaced(packageName, userId));
@@ -1123,11 +1123,11 @@
     };
 
     private static final class PackageManagerHelperImpl implements PackageManagerHelper {
-        private static class AndroidPackageUsers {
-            private AndroidPackage mPackage;
+        private static class PackageStateUsers {
+            private PackageState mPackageState;
             private final Set<Integer> mInstalledUsers = new ArraySet<>();
-            private AndroidPackageUsers(@NonNull AndroidPackage pkg) {
-                this.mPackage = pkg;
+            private PackageStateUsers(@NonNull PackageState packageState) {
+                this.mPackageState = packageState;
             }
         }
         private final Context mContext;
@@ -1139,7 +1139,7 @@
         // intent, querying the PackageManagerService for the actual current
         // state may lead to contradictions within OMS. Better then to lag
         // behind until all pending intents have been processed.
-        private final ArrayMap<String, AndroidPackageUsers> mCache = new ArrayMap<>();
+        private final ArrayMap<String, PackageStateUsers> mCache = new ArrayMap<>();
         private final Set<Integer> mInitializedUsers = new ArraySet<>();
 
         PackageManagerHelperImpl(Context context) {
@@ -1155,18 +1155,22 @@
          * @return a map of package name to all packages installed in the user
          */
         @NonNull
-        public ArrayMap<String, AndroidPackage> initializeForUser(final int userId) {
+        public ArrayMap<String, PackageState> initializeForUser(final int userId) {
             if (!mInitializedUsers.contains(userId)) {
                 mInitializedUsers.add(userId);
-                mPackageManagerInternal.forEachInstalledPackage(
-                        (pkg) -> addPackageUser(pkg, userId), userId);
+                mPackageManagerInternal.forEachPackageState((packageState -> {
+                    if (packageState.getPkg() != null
+                            && packageState.getUserStateOrDefault(userId).isInstalled()) {
+                        addPackageUser(packageState, userId);
+                    }
+                }));
             }
 
-            final ArrayMap<String, AndroidPackage> userPackages = new ArrayMap<>();
+            final ArrayMap<String, PackageState> userPackages = new ArrayMap<>();
             for (int i = 0, n = mCache.size(); i < n; i++) {
-                final AndroidPackageUsers pkg = mCache.valueAt(i);
+                final PackageStateUsers pkg = mCache.valueAt(i);
                 if (pkg.mInstalledUsers.contains(userId)) {
-                    userPackages.put(mCache.keyAt(i), pkg.mPackage);
+                    userPackages.put(mCache.keyAt(i), pkg.mPackageState);
                 }
             }
             return userPackages;
@@ -1174,11 +1178,11 @@
 
         @Override
         @Nullable
-        public AndroidPackage getPackageForUser(@NonNull final String packageName,
+        public PackageState getPackageStateForUser(@NonNull final String packageName,
                 final int userId) {
-            final AndroidPackageUsers pkg = mCache.get(packageName);
+            final PackageStateUsers pkg = mCache.get(packageName);
             if (pkg != null && pkg.mInstalledUsers.contains(userId)) {
-                return pkg.mPackage;
+                return pkg.mPackageState;
             }
             try {
                 if (!mPackageManager.isPackageAvailable(packageName, userId)) {
@@ -1193,9 +1197,9 @@
         }
 
         @NonNull
-        private AndroidPackage addPackageUser(@NonNull final String packageName,
+        private PackageState addPackageUser(@NonNull final String packageName,
                 final int user) {
-            final AndroidPackage pkg = mPackageManagerInternal.getPackage(packageName);
+            final PackageState pkg = mPackageManagerInternal.getPackageStateInternal(packageName);
             if (pkg == null) {
                 Slog.w(TAG, "Android package for '" + packageName + "' could not be found;"
                         + " continuing as if package was never added", new Throwable());
@@ -1205,23 +1209,23 @@
         }
 
         @NonNull
-        private AndroidPackage addPackageUser(@NonNull final AndroidPackage pkg,
+        private PackageState addPackageUser(@NonNull final PackageState pkg,
                 final int user) {
-            AndroidPackageUsers pkgUsers = mCache.get(pkg.getPackageName());
+            PackageStateUsers pkgUsers = mCache.get(pkg.getPackageName());
             if (pkgUsers == null) {
-                pkgUsers = new AndroidPackageUsers(pkg);
+                pkgUsers = new PackageStateUsers(pkg);
                 mCache.put(pkg.getPackageName(), pkgUsers);
             } else {
-                pkgUsers.mPackage = pkg;
+                pkgUsers.mPackageState = pkg;
             }
             pkgUsers.mInstalledUsers.add(user);
-            return pkgUsers.mPackage;
+            return pkgUsers.mPackageState;
         }
 
 
         @NonNull
         private void removePackageUser(@NonNull final String packageName, final int user) {
-            final AndroidPackageUsers pkgUsers = mCache.get(packageName);
+            final PackageStateUsers pkgUsers = mCache.get(packageName);
             if (pkgUsers == null) {
                 return;
             }
@@ -1229,20 +1233,20 @@
         }
 
         @NonNull
-        private void removePackageUser(@NonNull final AndroidPackageUsers pkg, final int user) {
+        private void removePackageUser(@NonNull final PackageStateUsers pkg, final int user) {
             pkg.mInstalledUsers.remove(user);
             if (pkg.mInstalledUsers.isEmpty()) {
-                mCache.remove(pkg.mPackage.getPackageName());
+                mCache.remove(pkg.mPackageState.getPackageName());
             }
         }
 
         @Nullable
-        public AndroidPackage onPackageAdded(@NonNull final String packageName, final int userId) {
+        public PackageState onPackageAdded(@NonNull final String packageName, final int userId) {
             return addPackageUser(packageName, userId);
         }
 
         @Nullable
-        public AndroidPackage onPackageUpdated(@NonNull final String packageName,
+        public PackageState onPackageUpdated(@NonNull final String packageName,
                 final int userId) {
             return addPackageUser(packageName, userId);
         }
@@ -1289,14 +1293,15 @@
         public OverlayableInfo getOverlayableForTarget(@NonNull String packageName,
                 @NonNull String targetOverlayableName, int userId)
                 throws IOException {
-            final AndroidPackage packageInfo = getPackageForUser(packageName, userId);
-            if (packageInfo == null) {
+            var packageState = getPackageStateForUser(packageName, userId);
+            var pkg = packageState == null ? null : packageState.getAndroidPackage();
+            if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
+                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
                 return apkAssets.getOverlayableInfo(targetOverlayableName);
             } finally {
                 if (apkAssets != null) {
@@ -1311,14 +1316,15 @@
         @Override
         public boolean doesTargetDefineOverlayable(String targetPackageName, int userId)
                 throws IOException {
-            AndroidPackage packageInfo = getPackageForUser(targetPackageName, userId);
-            if (packageInfo == null) {
+            var packageState = getPackageStateForUser(targetPackageName, userId);
+            var pkg = packageState == null ? null : packageState.getAndroidPackage();
+            if (pkg == null) {
                 throw new IOException("Unable to get target package");
             }
 
             ApkAssets apkAssets = null;
             try {
-                apkAssets = ApkAssets.loadFromPath(packageInfo.getBaseApkPath());
+                apkAssets = ApkAssets.loadFromPath(pkg.getSplits().get(0).getPath());
                 return apkAssets.definesOverlayable();
             } finally {
                 if (apkAssets != null) {
@@ -1370,8 +1376,8 @@
 
             for (int i = 0, n = mCache.size(); i < n; i++) {
                 final String packageName = mCache.keyAt(i);
-                final AndroidPackageUsers pkg = mCache.valueAt(i);
-                pw.print(TAB1 + packageName + ": " + pkg.mPackage + " users=");
+                final PackageStateUsers pkg = mCache.valueAt(i);
+                pw.print(TAB1 + packageName + ": " + pkg.mPackageState + " users=");
                 pw.println(TextUtils.join(", ", pkg.mInstalledUsers));
             }
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 9d5830c..1beba9f 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -50,6 +50,7 @@
 import com.android.internal.content.om.OverlayConfig;
 import com.android.internal.util.CollectionUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -72,7 +73,7 @@
  */
 final class OverlayManagerServiceImpl {
     /**
-     * @deprecated Not used. See {@link android.content.om.OverlayInfo#STATE_TARGET_UPGRADING}.
+     * @deprecated Not used. See {@link OverlayInfo#STATE_TARGET_IS_BEING_REPLACED}.
      */
     @Deprecated
     private static final int FLAG_TARGET_IS_BEING_REPLACED = 1 << 0;
@@ -164,14 +165,15 @@
 
         // Remove the settings of all overlays that are no longer installed for this user.
         final ArraySet<UserPackage> updatedTargets = new ArraySet<>();
-        final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
+        final ArrayMap<String, PackageState> userPackages = mPackageManager.initializeForUser(
                 newUserId);
         CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
                 (info) -> !userPackages.containsKey(info.packageName), newUserId));
 
         final ArraySet<String> overlaidByOthers = new ArraySet<>();
-        for (AndroidPackage androidPackage : userPackages.values()) {
-            final String overlayTarget = androidPackage.getOverlayTarget();
+        for (PackageState packageState : userPackages.values()) {
+            var pkg = packageState.getAndroidPackage();
+            final String overlayTarget = pkg == null ? null : pkg.getOverlayTarget();
             if (!TextUtils.isEmpty(overlayTarget)) {
                 overlaidByOthers.add(overlayTarget);
             }
@@ -180,18 +182,24 @@
         // Update the state of all installed packages containing overlays, and initialize new
         // overlays that are not currently in the settings.
         for (int i = 0, n = userPackages.size(); i < n; i++) {
-            final AndroidPackage pkg = userPackages.valueAt(i);
+            final PackageState packageState = userPackages.valueAt(i);
+            var pkg = packageState.getAndroidPackage();
+            if (pkg == null) {
+                continue;
+            }
+
+            var packageName = packageState.getPackageName();
             try {
                 CollectionUtils.addAll(updatedTargets,
                         updatePackageOverlays(pkg, newUserId, 0 /* flags */));
 
                 // When a new user is switched to for the first time, package manager must be
                 // informed of the overlay paths for all overlaid packages installed in the user.
-                if (overlaidByOthers.contains(pkg.getPackageName())) {
-                    updatedTargets.add(UserPackage.of(newUserId, pkg.getPackageName()));
+                if (overlaidByOthers.contains(packageName)) {
+                    updatedTargets.add(UserPackage.of(newUserId, packageName));
                 }
             } catch (OperationFailedException e) {
-                Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
+                Slog.e(TAG, "failed to initialize overlays of '" + packageName
                         + "' for user " + newUserId + "", e);
             }
         }
@@ -364,7 +372,7 @@
                 }
 
                 currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
-                        pkg.getOverlayTargetOverlayableName(), pkg.getBaseApkPath(),
+                        pkg.getOverlayTargetOverlayableName(), pkg.getSplits().get(0).getPath(),
                         isPackageConfiguredMutable(pkg),
                         isPackageConfiguredEnabled(pkg),
                         getPackageConfiguredPriority(pkg), pkg.getOverlayCategory(),
@@ -401,7 +409,8 @@
                 updateOverlaysForTarget(pkgName, userId, flags));
 
         // Realign the overlay settings with PackageManager's view of the package.
-        final AndroidPackage pkg = mPackageManager.getPackageForUser(pkgName, userId);
+        final PackageState packageState = mPackageManager.getPackageStateForUser(pkgName, userId);
+        var pkg = packageState == null ? null : packageState.getAndroidPackage();
         if (pkg == null) {
             return onPackageRemoved(pkgName, userId);
         }
@@ -790,10 +799,15 @@
     private boolean updateState(@NonNull final CriticalOverlayInfo info,
             final int userId, final int flags) throws OverlayManagerSettings.BadKeyException {
         final OverlayIdentifier overlay = info.getOverlayIdentifier();
-        final AndroidPackage targetPackage = mPackageManager.getPackageForUser(
-                info.getTargetPackageName(), userId);
-        final AndroidPackage overlayPackage = mPackageManager.getPackageForUser(
-                info.getPackageName(), userId);
+        var targetPackageState =
+                mPackageManager.getPackageStateForUser(info.getTargetPackageName(), userId);
+        var targetPackage =
+                targetPackageState == null ? null : targetPackageState.getAndroidPackage();
+
+        var overlayPackageState =
+                mPackageManager.getPackageStateForUser(info.getPackageName(), userId);
+        var overlayPackage =
+                overlayPackageState == null ? null : overlayPackageState.getAndroidPackage();
 
         boolean modified = false;
         if (overlayPackage == null) {
@@ -803,7 +817,8 @@
 
         modified |= mSettings.setCategory(overlay, userId, overlayPackage.getOverlayCategory());
         if (!info.isFabricated()) {
-            modified |= mSettings.setBaseCodePath(overlay, userId, overlayPackage.getBaseApkPath());
+            modified |= mSettings.setBaseCodePath(overlay, userId,
+                    overlayPackage.getSplits().get(0).getPath());
         }
 
         // Immutable RROs targeting to "android", ie framework-res.apk, are handled by native
@@ -812,7 +827,7 @@
         @IdmapManager.IdmapStatus int idmapStatus = IDMAP_NOT_EXIST;
         if (targetPackage != null && !("android".equals(info.getTargetPackageName())
                 && !isPackageConfiguredMutable(overlayPackage))) {
-            idmapStatus = mIdmapManager.createIdmap(targetPackage,
+            idmapStatus = mIdmapManager.createIdmap(targetPackage, overlayPackageState,
                     overlayPackage, updatedOverlayInfo.baseCodePath, overlay.getOverlayName(),
                     userId);
             modified |= (idmapStatus & IDMAP_IS_MODIFIED) != 0;
diff --git a/services/core/java/com/android/server/om/PackageManagerHelper.java b/services/core/java/com/android/server/om/PackageManagerHelper.java
index cbdabdd..da2fc04 100644
--- a/services/core/java/com/android/server/om/PackageManagerHelper.java
+++ b/services/core/java/com/android/server/om/PackageManagerHelper.java
@@ -24,7 +24,7 @@
 import android.util.ArrayMap;
 
 import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.IOException;
 import java.util.Map;
@@ -44,13 +44,13 @@
      * @return a map of package name to all packages installed in the user
      */
     @NonNull
-    ArrayMap<String, AndroidPackage> initializeForUser(final int userId);
+    ArrayMap<String, PackageState> initializeForUser(final int userId);
 
     /**
      * Retrieves the package information if it is installed for the user.
      */
     @Nullable
-    AndroidPackage getPackageForUser(@NonNull final String packageName, final int userId);
+    PackageState getPackageStateForUser(@NonNull final String packageName, final int userId);
 
     /**
      * Returns whether the package is an instant app.
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index b8bdabe..ed31187 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -23,6 +23,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.os.CreateAppDataArgs;
@@ -47,6 +48,7 @@
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SELinuxUtil;
 
@@ -179,13 +181,13 @@
     }
 
     private void prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
-            @NonNull AndroidPackage pkg, int userId, @StorageManager.StorageFlags int flags,
-            boolean maybeMigrateAppData) {
+            @NonNull PackageState packageState, @NonNull AndroidPackage pkg, @UserIdInt int userId,
+            @StorageManager.StorageFlags int flags, boolean maybeMigrateAppData) {
         prepareAppData(batch, pkg, Process.INVALID_UID, userId, flags).thenRun(() -> {
             // Note: this code block is executed with the Installer lock
             // already held, since it's invoked as a side-effect of
             // executeBatchLI()
-            if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
+            if (maybeMigrateAppData && maybeMigrateAppDataLIF(packageState, pkg, userId)) {
                 // We may have just shuffled around app data directories, so
                 // prepare them one more time
                 final Installer.Batch batchInner = new Installer.Batch();
@@ -320,8 +322,9 @@
      * CE/DE data to match the {@code defaultToDeviceProtectedStorage} flag
      * requested by the app.
      */
-    private boolean maybeMigrateAppDataLIF(AndroidPackage pkg, int userId) {
-        if (pkg.isSystem() && !StorageManager.isFileEncrypted()
+    private boolean maybeMigrateAppDataLIF(@NonNull PackageState packageState,
+            @NonNull AndroidPackage pkg, @UserIdInt int userId) {
+        if (packageState.isSystem() && !StorageManager.isFileEncrypted()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
             final int storageTarget = pkg.isDefaultToDeviceProtectedStorage()
                     ? StorageManager.FLAG_STORAGE_DE : StorageManager.FLAG_STORAGE_CE;
@@ -454,7 +457,7 @@
             }
 
             if (ps.getUserStateOrDefault(userId).isInstalled()) {
-                prepareAppDataAndMigrate(batch, ps.getPkg(), userId, flags, migrateAppData);
+                prepareAppDataAndMigrate(batch, ps, ps.getPkg(), userId, flags, migrateAppData);
                 preparedCount++;
             }
         }
@@ -529,8 +532,8 @@
                         && packageStateInternal.getUserStateOrDefault(
                                 UserHandle.USER_SYSTEM).isInstalled()) {
                     AndroidPackage pkg = packageStateInternal.getPkg();
-                    prepareAppDataAndMigrate(batch, pkg, UserHandle.USER_SYSTEM, storageFlags,
-                            true /* maybeMigrateAppData */);
+                    prepareAppDataAndMigrate(batch, packageStateInternal, pkg,
+                            UserHandle.USER_SYSTEM, storageFlags, true /* maybeMigrateAppData */);
                     count++;
                 }
             }
diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java
index 2ef193c..32479ee 100644
--- a/services/core/java/com/android/server/pm/AppStateHelper.java
+++ b/services/core/java/com/android/server/pm/AppStateHelper.java
@@ -23,6 +23,7 @@
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
+import android.content.pm.PackageManagerInternal;
 import android.media.AudioManager;
 import android.media.IAudioService;
 import android.net.ConnectivityManager;
@@ -36,6 +37,7 @@
 import com.android.server.LocalServices;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
@@ -157,6 +159,56 @@
         return false;
     }
 
+    private static boolean containsAny(Collection<String> list, Collection<String> which) {
+        if (list.isEmpty()) {
+            return false;
+        }
+        for (var element : which) {
+            if (list.contains(element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addLibraryDependency(ArraySet<String> results, List<String> libPackageNames) {
+        var pmInternal = LocalServices.getService(PackageManagerInternal.class);
+
+        var libraryNames = new ArraySet<String>();
+        var staticSharedLibraryNames = new ArraySet<String>();
+        var sdkLibraryNames = new ArraySet<String>();
+        for (var packageName : libPackageNames) {
+            var pkg = pmInternal.getAndroidPackage(packageName);
+            if (pkg == null) {
+                continue;
+            }
+            libraryNames.addAll(pkg.getLibraryNames());
+            var libraryName = pkg.getStaticSharedLibraryName();
+            if (libraryName != null) {
+                staticSharedLibraryNames.add(libraryName);
+            }
+            libraryName = pkg.getSdkLibraryName();
+            if (libraryName != null) {
+                sdkLibraryNames.add(libraryName);
+            }
+        }
+
+        if (libraryNames.isEmpty()
+                && staticSharedLibraryNames.isEmpty()
+                && sdkLibraryNames.isEmpty()) {
+            return;
+        }
+
+        pmInternal.forEachPackage(pkg -> {
+            if (containsAny(pkg.getUsesLibraries(), libraryNames)
+                    || containsAny(pkg.getUsesOptionalLibraries(), libraryNames)
+                    || containsAny(pkg.getUsesStaticLibraries(), staticSharedLibraryNames)
+                    || containsAny(pkg.getUsesSdkLibraries(), sdkLibraryNames)) {
+                results.add(pkg.getPackageName());
+            }
+        });
+    }
+
     /**
      * True if any app has sent or received network data over the past
      * {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds.
@@ -225,6 +277,7 @@
      */
     public List<String> getDependencyPackages(List<String> packageNames) {
         var results = new ArraySet<String>();
+        // Include packages sharing the same process
         var am = mContext.getSystemService(ActivityManager.class);
         for (var info : am.getRunningAppProcesses()) {
             for (var packageName : packageNames) {
@@ -236,10 +289,14 @@
                 }
             }
         }
+        // Include packages using bounded services
         var amInternal = LocalServices.getService(ActivityManagerInternal.class);
         for (var packageName : packageNames) {
             results.addAll(amInternal.getClientPackages(packageName));
         }
+        // Include packages using libraries
+        addLibraryDependency(results, packageNames);
+
         return new ArrayList<>(results);
     }
 }
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index ea84283..c232b36 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -593,7 +593,7 @@
     CharSequence getHarmfulAppWarning(@NonNull String packageName, @UserIdInt int userId);
 
     /**
-     * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
+     * Only keep package names that refer to {@link PackageState#isSystem system} packages.
      *
      * @param pkgNames The packages to filter
      *
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 06aadd92..0a59c19 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -130,6 +130,7 @@
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.PackageUserStateInternal;
@@ -223,7 +224,7 @@
             }
 
             return PackageUserStateUtils.isMatch(pkgState.getUserStateOrDefault(userId),
-                    pkg.isSystem(), pkg.isEnabled(), component, flags);
+                    pkgState.isSystem(), pkg.isEnabled(), component, flags);
         }
 
         @Nullable
@@ -1571,7 +1572,8 @@
         }
 
         AndroidPackage p = mPackages.get(packageName);
-        if (matchFactoryOnly && p != null && !p.isSystem()) {
+        var packageState = mSettings.getPackage(packageName);
+        if (matchFactoryOnly && p != null && !packageState.isSystem()) {
             return null;
         }
         if (DEBUG_PACKAGE_INFO) {
@@ -1689,7 +1691,7 @@
             for (AndroidPackage p : mPackages.values()) {
                 PackageStateInternal ps = getPackageStateInternal(p.getPackageName());
                 if (listFactory) {
-                    if (!p.isSystem()) {
+                    if (!ps.isSystem()) {
                         continue;
                     }
                     PackageStateInternal psDisabled =
@@ -2043,7 +2045,8 @@
         }
 
         final SharedLibraryInfo libraryInfo = getSharedLibraryInfo(
-                ps.getPkg().getStaticSharedLibraryName(), ps.getPkg().getStaticSharedLibVersion());
+                ps.getPkg().getStaticSharedLibraryName(),
+                ps.getPkg().getStaticSharedLibraryVersion());
         if (libraryInfo == null) {
             return false;
         }
@@ -2566,8 +2569,9 @@
     public int getPackageUidInternal(String packageName,
             @PackageManager.PackageInfoFlagsBits long flags, int userId, int callingUid) {
         // reader
+        var packageState = mSettings.getPackage(packageName);
         final AndroidPackage p = mPackages.get(packageName);
-        if (p != null && AndroidPackageUtils.isMatchForSystemOnly(p, flags)) {
+        if (p != null && AndroidPackageUtils.isMatchForSystemOnly(packageState, flags)) {
             final PackageStateInternal ps = getPackageStateInternal(p.getPackageName(), callingUid);
             if (ps != null && ps.getUserStateOrDefault(userId).isInstalled()
                     && !shouldFilterApplication(ps, callingUid, userId)) {
@@ -3616,7 +3620,7 @@
             return null;
         }
         if (ps.getPkg() != null
-                && AndroidPackageUtils.isMatchForSystemOnly(ps.getPkg(), flags)) {
+                && AndroidPackageUtils.isMatchForSystemOnly(ps, flags)) {
             if (ps.getUserStateOrDefault(userId).isInstalled()
                     && !shouldFilterApplication(ps, callingUid, userId)) {
                 return mPermissionManager.getGidsForUid(UserHandle.getUid(userId,
@@ -5416,6 +5420,7 @@
         final int userId = UserHandle.getCallingUserId();
         for (int index = 0; index < numPackages; index++) {
             final AndroidPackage p = mPackages.valueAt(index);
+            var packageState = mSettings.getPackage(p.getPackageName());
 
             final boolean matchesUnaware = ((flags & MATCH_DIRECT_BOOT_UNAWARE) != 0)
                     && !p.isDirectBootAware();
@@ -5423,7 +5428,7 @@
                     && p.isDirectBootAware();
 
             if (p.isPersistent()
-                    && (!safeMode || p.isSystem())
+                    && (!safeMode || packageState.isSystem())
                     && (matchesUnaware || matchesAware)) {
                 PackageStateInternal ps = mSettings.getPackage(p.getPackageName());
                 if (ps != null) {
@@ -5522,7 +5527,7 @@
     }
 
     /**
-     * Only keep package names that refer to {@link AndroidPackage#isSystem system} packages.
+     * Only keep package names that refer to {@link PackageState#isSystem system} packages.
      *
      * @param pkgNames The packages to filter
      *
@@ -5542,13 +5547,13 @@
                 continue;
             }
 
-            AndroidPackage pkg = getPackage(pkgName);
-            if (pkg == null) {
+            var packageState = getPackageStateInternal(pkgName);
+            if (packageState == null || packageState.getAndroidPackage() == null) {
                 Log.w(TAG, "Could not find package " + pkgName);
                 continue;
             }
 
-            if (!pkg.isSystem()) {
+            if (!packageState.isSystem()) {
                 Log.w(TAG, pkgName + " is not system");
                 continue;
             }
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index a6def7d..3df46a2 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -186,7 +186,7 @@
                 SharedLibraryInfo libraryInfo = null;
                 if (pkg.getStaticSharedLibraryName() != null) {
                     libraryInfo = computer.getSharedLibraryInfo(pkg.getStaticSharedLibraryName(),
-                            pkg.getStaticSharedLibVersion());
+                            pkg.getStaticSharedLibraryVersion());
                 } else if (pkg.getSdkLibraryName() != null) {
                     libraryInfo = computer.getSharedLibraryInfo(pkg.getSdkLibraryName(),
                             pkg.getSdkLibVersionMajor());
@@ -536,7 +536,7 @@
                     nextUserId);
             mPm.mDomainVerificationManager.clearPackageForUser(ps.getPackageName(), nextUserId);
         }
-        mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), pkg,
+        mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
                 sharedUserPkgs, userId);
 
         if (outInfo != null) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 5661399..69aaa0d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -124,21 +124,22 @@
      * which are (in order) {@code numberOfPackagesOptimized}, {@code numberOfPackagesSkipped}
      * and {@code numberOfPackagesFailed}.
      */
-    public int[] performDexOptUpgrade(List<AndroidPackage> pkgs, boolean showDialog,
+    public int[] performDexOptUpgrade(List<PackageStateInternal> packageStates, boolean showDialog,
             final int compilationReason, boolean bootComplete) {
 
         int numberOfPackagesVisited = 0;
         int numberOfPackagesOptimized = 0;
         int numberOfPackagesSkipped = 0;
         int numberOfPackagesFailed = 0;
-        final int numberOfPackagesToDexopt = pkgs.size();
+        final int numberOfPackagesToDexopt = packageStates.size();
 
-        for (AndroidPackage pkg : pkgs) {
+        for (var packageState : packageStates) {
+            var pkg = packageState.getAndroidPackage();
             numberOfPackagesVisited++;
 
             boolean useProfileForDexopt = false;
 
-            if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && pkg.isSystem()) {
+            if ((mPm.isFirstBoot() || mPm.isDeviceUpgrading()) && packageState.isSystem()) {
                 // Copy over initial preopt profiles since we won't get any JIT samples for methods
                 // that are already compiled.
                 File profileFile = new File(getPrebuildProfilePath(pkg));
@@ -234,7 +235,7 @@
             }
 
             if (SystemProperties.getBoolean(mPm.PRECOMPILE_LAYOUTS, false)) {
-                mPm.mArtManagerService.compileLayouts(pkg);
+                mPm.mArtManagerService.compileLayouts(packageState, pkg);
             }
 
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
@@ -369,13 +370,8 @@
         List<PackageStateInternal> pkgSettings =
                 getPackagesForDexopt(snapshot.getPackageStates().values(), mPm);
 
-        List<AndroidPackage> pkgs = new ArrayList<>(pkgSettings.size());
-        for (int index = 0; index < pkgSettings.size(); index++) {
-            pkgs.add(pkgSettings.get(index).getPkg());
-        }
-
         final long startTime = System.nanoTime();
-        final int[] stats = performDexOptUpgrade(pkgs, mPm.isPreNUpgrade() /* showDialog */,
+        final int[] stats = performDexOptUpgrade(pkgSettings, mPm.isPreNUpgrade() /* showDialog */,
                 causeFirstBoot ? REASON_FIRST_BOOT : REASON_BOOT_AFTER_OTA,
                 false /* bootComplete */);
 
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 823bc71..6825dd7 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -194,9 +194,14 @@
             }
         }
         final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(
-                consumer -> mPm.forEachPackageInternal(mPm.snapshotComputer(),
-                        pkg -> consumer.accept(pkg, pkg.isSystem(),
-                                apkInApexPreInstalledPaths.get(pkg.getPackageName()))));
+                consumer -> mPm.forEachPackageState(mPm.snapshotComputer(),
+                        packageState -> {
+                            var pkg = packageState.getPkg();
+                            if (pkg != null) {
+                                consumer.accept(pkg, packageState.isSystem(),
+                                        apkInApexPreInstalledPaths.get(pkg.getPackageName()));
+                            }
+                        }));
 
         // do this first before mucking with mPackages for the "expecting better" case
         updateStubSystemAppsList(mStubSystemApps);
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 34bb6ec..16bf0fe 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -168,6 +168,7 @@
 import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
@@ -285,7 +286,8 @@
             pkgSetting = request.getScannedPackageSetting();
             if (originalPkgSetting != null) {
                 mPm.mSettings.addRenamedPackageLPw(
-                        AndroidPackageUtils.getRealPackageOrNull(parsedPackage),
+                        AndroidPackageUtils.getRealPackageOrNull(parsedPackage,
+                                pkgSetting.isSystem()),
                         originalPkgSetting.getPackageName());
                 mPm.mTransferredPackages.add(originalPkgSetting.getPackageName());
             } else {
@@ -295,7 +297,7 @@
         SharedUserSetting sharedUserSetting = mPm.mSettings.getSharedUserSettingLPr(pkgSetting);
         if (sharedUserSetting != null) {
             sharedUserSetting.addPackage(pkgSetting);
-            if (parsedPackage.isLeavingSharedUid()
+            if (parsedPackage.isLeavingSharedUser()
                     && SharedUidMigration.applyStrategy(BEST_EFFORT)
                     && sharedUserSetting.isSingleUser()) {
                 // Attempt the transparent shared UID migration
@@ -502,7 +504,7 @@
                 }
             }
 
-            mPm.mPermissionManager.onPackageAdded(pkg,
+            mPm.mPermissionManager.onPackageAdded(pkgSetting,
                     (scanFlags & SCAN_AS_INSTANT_APP) != 0, oldPkg);
         }
 
@@ -1224,9 +1226,7 @@
             if (ps != null) {
                 if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps);
 
-                if (ps.getPkg() != null) {
-                    systemApp = ps.getPkg().isSystem();
-                }
+                systemApp = ps.isSystem();
                 request.setOriginUsers(
                         ps.queryInstalledUsers(mPm.mUserManager.getUserIds(), true));
             }
@@ -1401,17 +1401,17 @@
 
             try {
                 PackageSetting pkgSetting;
-                AndroidPackage oldPackage;
                 synchronized (mPm.mLock) {
                     pkgSetting = mPm.mSettings.getPackageLPr(pkgName);
-                    oldPackage = mPm.mPackages.get(pkgName);
                 }
                 boolean isUpdatedSystemAppFromExistingSetting = pkgSetting != null
-                        && pkgSetting.getPkgState().isUpdatedSystemApp();
+                        && pkgSetting.isUpdatedSystemApp();
                 final String abiOverride = deriveAbiOverride(request.getAbiOverride());
-                boolean isUpdatedSystemAppInferred = oldPackage != null && oldPackage.isSystem();
+
+                // TODO: Are these system flags actually set properly at this stage?
+                boolean isUpdatedSystemAppInferred = pkgSetting != null && pkgSetting.isSystem();
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths>
-                        derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage,
+                        derivedAbi = mPackageAbiHelper.derivePackageAbi(parsedPackage, systemApp,
                         isUpdatedSystemAppFromExistingSetting || isUpdatedSystemAppInferred,
                         abiOverride, ScanPackageUtils.getAppLib32InstallDir());
                 derivedAbi.first.applyTo(parsedPackage);
@@ -1443,6 +1443,7 @@
                 freezePackageForInstall(pkgName, installFlags, "installPackageLI");
         boolean shouldCloseFreezerBeforeReturn = true;
         try {
+            final PackageState oldPackageState;
             final AndroidPackage oldPackage;
             String renamedPackage;
             boolean sysPkg = false;
@@ -1454,8 +1455,9 @@
             if (replace) {
                 final String pkgName11 = parsedPackage.getPackageName();
                 synchronized (mPm.mLock) {
-                    oldPackage = mPm.mPackages.get(pkgName11);
+                    oldPackageState = mPm.mSettings.getPackageLPr(pkgName11);
                 }
+                oldPackage = oldPackageState.getAndroidPackage();
                 if (parsedPackage.isStaticSharedLibrary()) {
                     // Static libs have a synthetic package name containing the version
                     // and cannot be updated as an update would get a new package name,
@@ -1514,7 +1516,7 @@
                     }
 
                     // don't allow a system upgrade unless the upgrade hash matches
-                    if (oldPackage.getRestrictUpdateHash() != null && oldPackage.isSystem()) {
+                    if (oldPackage.getRestrictUpdateHash() != null && oldPackageState.isSystem()) {
                         final byte[] digestBytes;
                         try {
                             final MessageDigest digest = MessageDigest.getInstance("SHA-512");
@@ -1550,7 +1552,7 @@
                     }
 
                     // APK should not re-join shared UID
-                    if (oldPackage.isLeavingSharedUid() && !parsedPackage.isLeavingSharedUid()) {
+                    if (oldPackage.isLeavingSharedUser() && !parsedPackage.isLeavingSharedUser()) {
                         throw new PrepareFailure(INSTALL_FAILED_UID_CHANGED,
                                 "Package " + parsedPackage.getPackageName()
                                         + " attempting to rejoin " + newSharedUid);
@@ -1611,15 +1613,15 @@
                 removedInfo.mRemovedPackageVersionCode = oldPackage.getLongVersionCode();
                 request.setRemovedInfo(removedInfo);
 
-                sysPkg = oldPackage.isSystem();
+                sysPkg = oldPackageState.isSystem();
                 if (sysPkg) {
                     // Set the system/privileged/oem/vendor/product flags as needed
-                    final boolean privileged = oldPackage.isPrivileged();
-                    final boolean oem = oldPackage.isOem();
-                    final boolean vendor = oldPackage.isVendor();
-                    final boolean product = oldPackage.isProduct();
-                    final boolean odm = oldPackage.isOdm();
-                    final boolean systemExt = oldPackage.isSystemExt();
+                    final boolean privileged = oldPackageState.isPrivileged();
+                    final boolean oem = oldPackageState.isOem();
+                    final boolean vendor = oldPackageState.isVendor();
+                    final boolean product = oldPackageState.isProduct();
+                    final boolean odm = oldPackageState.isOdm();
+                    final boolean systemExt = oldPackageState.isSystemExt();
                     final @ParsingPackageUtils.ParseFlags int systemParseFlags = parseFlags;
                     final @PackageManagerService.ScanFlags int systemScanFlags = scanFlags
                             | SCAN_AS_SYSTEM
@@ -2040,7 +2042,7 @@
             final PackageSetting ps = mPm.mSettings.getPackageLPr(pkgName);
             final int userId = installRequest.getUserId();
             if (ps != null) {
-                if (pkg.isSystem()) {
+                if (ps.isSystem()) {
                     if (DEBUG_INSTALL) {
                         Slog.d(TAG, "Implicitly enabling system package on upgrade: " + pkgName);
                     }
@@ -2359,8 +2361,7 @@
 
                 // Unfortunately, the updated system app flag is only tracked on this PackageSetting
                 boolean isUpdatedSystemApp =
-                        installRequest.getScannedPackageSetting().getPkgState()
-                        .isUpdatedSystemApp();
+                        installRequest.getScannedPackageSetting().isUpdatedSystemApp();
 
                 realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp);
 
@@ -2703,7 +2704,7 @@
                 // Send PACKAGE_ADDED broadcast for users that see the package for the first time
                 // sendPackageAddedForNewUsers also deals with system apps
                 int appId = UserHandle.getAppId(request.getUid());
-                boolean isSystem = request.getPkg().isSystem();
+                boolean isSystem = request.isInstallSystem();
                 mPm.sendPackageAddedForNewUsers(mPm.snapshotComputer(), packageName,
                         isSystem || virtualPreload, virtualPreload /*startReceiver*/, appId,
                         firstUserIds, firstInstantUserIds, dataLoaderType);
@@ -2780,7 +2781,7 @@
                             null /*broadcastAllowList*/,
                             mBroadcastHelper.getTemporaryAppAllowlistBroadcastOptions(
                                     REASON_PACKAGE_REPLACED).toBundle());
-                } else if (launchedForRestore && !request.getPkg().isSystem()) {
+                } else if (launchedForRestore && !request.isInstallSystem()) {
                     // First-install and we did a restore, so we're responsible for the
                     // first-launch broadcast.
                     if (DEBUG_BACKUP) {
@@ -3779,9 +3780,11 @@
 
         synchronized (mPm.mLock) {
             platformPackage = mPm.getPlatformPackage();
+            var isSystemApp = AndroidPackageUtils.isSystem(parsedPackage);
             final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
-                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage));
-            realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName);
+                    AndroidPackageUtils.getRealPackageOrNull(parsedPackage, isSystemApp));
+            realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName,
+                    isSystemApp);
             if (realPkgName != null) {
                 ScanPackageUtils.ensurePackageRenamed(parsedPackage, renamedPkgName);
             }
@@ -3798,7 +3801,7 @@
             if (installedPkgSetting == null || !installedPkgSetting.hasSharedUser()) {
                 // Directly ignore sharedUserSetting for new installs, or if the app has
                 // already left shared UID
-                ignoreSharedUserId = parsedPackage.isLeavingSharedUid();
+                ignoreSharedUserId = parsedPackage.isLeavingSharedUser();
             }
 
             if (!ignoreSharedUserId && parsedPackage.getSharedUserId() != null) {
@@ -3845,7 +3848,7 @@
 
         boolean isUpdatedSystemApp;
         if (installedPkgSetting != null) {
-            isUpdatedSystemApp = installedPkgSetting.getPkgState().isUpdatedSystemApp();
+            isUpdatedSystemApp = installedPkgSetting.isUpdatedSystemApp();
         } else {
             isUpdatedSystemApp = disabledPkgSetting != null;
         }
@@ -4321,10 +4324,10 @@
                 SharedLibraryInfo libInfo = versionedLib.valueAt(i);
                 final long libVersionCode = libInfo.getDeclaringPackage()
                         .getLongVersionCode();
-                if (libInfo.getLongVersion() < pkg.getStaticSharedLibVersion()) {
+                if (libInfo.getLongVersion() < pkg.getStaticSharedLibraryVersion()) {
                     minVersionCode = Math.max(minVersionCode, libVersionCode + 1);
                 } else if (libInfo.getLongVersion()
-                        > pkg.getStaticSharedLibVersion()) {
+                        > pkg.getStaticSharedLibraryVersion()) {
                     maxVersionCode = Math.min(maxVersionCode, libVersionCode - 1);
                 } else {
                     minVersionCode = maxVersionCode = libVersionCode;
@@ -4433,7 +4436,7 @@
 
     private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg)
             throws PackageManagerException {
-        if (!pkg.isPrivileged() && (pkg.getSharedUserId() != null)) {
+        if (!AndroidPackageUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) {
             SharedUserSetting sharedUserSetting = null;
             try {
                 synchronized (mPm.mLock) {
@@ -4461,16 +4464,17 @@
 
     private @PackageManagerService.ScanFlags int adjustScanFlags(
             @PackageManagerService.ScanFlags int scanFlags,
-            PackageSetting pkgSetting, PackageSetting disabledPkgSetting, UserHandle user,
-            AndroidPackage pkg) {
-        scanFlags = ScanPackageUtils.adjustScanFlagsWithPackageSetting(scanFlags, pkgSetting,
+            @Nullable PackageSetting existingPkgSetting,
+            @Nullable PackageSetting disabledPkgSetting, UserHandle user,
+            @NonNull AndroidPackage pkg) {
+        scanFlags = ScanPackageUtils.adjustScanFlagsWithPackageSetting(scanFlags, existingPkgSetting,
                 disabledPkgSetting, user);
 
         // Exception for privileged apps that share a user with a priv-app.
         final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
                 && ScanPackageUtils.getVendorPartitionVersion() < 28;
         if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
-                && !pkg.isPrivileged()
+                && !AndroidPackageUtils.isPrivileged(pkg)
                 && (pkg.getSharedUserId() != null)
                 && !skipVendorPrivilegeScan) {
             SharedUserSetting sharedUserSetting = null;
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index b600aa8..eb3b29c 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -53,7 +53,6 @@
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.util.Preconditions;
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.pkg.AndroidPackage;
 
 import libcore.io.IoUtils;
 
@@ -197,12 +196,13 @@
         }
         // Override with defaults if needed.
         Computer snapshot = mPm.snapshotComputer();
-        AndroidPackage installedPkg = snapshot.getPackage(packageName);
+        var installedPkgState = snapshot.getPackageStateInternal(packageName);
+        var installedPkg = installedPkgState == null ? null : installedPkgState.getAndroidPackage();
         if (installedPkg != null) {
             // Currently installed package which the new package is attempting to replace
             recommendedInstallLocation = InstallLocationUtils.installLocationPolicy(
                     installLocation, recommendedInstallLocation, mInstallFlags,
-                    installedPkg.isSystem(), installedPkg.isExternalStorage());
+                    installedPkgState.isSystem(), installedPkg.isExternalStorage());
         }
 
         final boolean onInt = (mInstallFlags & PackageManager.INSTALL_INTERNAL) != 0;
diff --git a/services/core/java/com/android/server/pm/MovePackageHelper.java b/services/core/java/com/android/server/pm/MovePackageHelper.java
index b66c6ac..52adc0d 100644
--- a/services/core/java/com/android/server/pm/MovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/MovePackageHelper.java
@@ -86,7 +86,7 @@
             throw new PackageManagerException(MOVE_FAILED_DOESNT_EXIST, "Missing package");
         }
         final AndroidPackage pkg = packageState.getPkg();
-        if (pkg.isSystem()) {
+        if (packageState.isSystem()) {
             throw new PackageManagerException(MOVE_FAILED_SYSTEM_PACKAGE,
                     "Cannot move system application");
         }
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelper.java b/services/core/java/com/android/server/pm/PackageAbiHelper.java
index d839b14..6faf68d 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelper.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelper.java
@@ -38,8 +38,8 @@
      * which varies depending on where and how the package was installed.
      */
     @NonNull
-    NativeLibraryPaths deriveNativeLibraryPaths(AndroidPackage pkg, boolean isUpdatedSystemApp,
-            File appLib32InstallDir);
+    NativeLibraryPaths deriveNativeLibraryPaths(AndroidPackage pkg, boolean isSystemApp,
+            boolean isUpdatedSystemApp, File appLib32InstallDir);
 
     /**
      * Calculate the abis for a bundled app. These can uniquely be determined from the contents of
@@ -54,8 +54,9 @@
      *
      * If {@code extractLibs} is true, native libraries are extracted from the app if required.
      */
-    Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg, boolean isUpdatedSystemApp,
-            String cpuAbiOverride, File appLib32InstallDir) throws PackageManagerException;
+    Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg, boolean isSystemApp,
+            boolean isUpdatedSystemApp, String cpuAbiOverride, File appLib32InstallDir)
+            throws PackageManagerException;
 
     /**
      * Calculates adjusted ABIs for a set of packages belonging to a shared user so that they all
diff --git a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
index 249de3c..e254300 100644
--- a/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
+++ b/services/core/java/com/android/server/pm/PackageAbiHelperImpl.java
@@ -133,13 +133,13 @@
     }
 
     @Override
-    public NativeLibraryPaths deriveNativeLibraryPaths(AndroidPackage pkg,
+    public NativeLibraryPaths deriveNativeLibraryPaths(AndroidPackage pkg, boolean isSystemApp,
             boolean isUpdatedSystemApp, File appLib32InstallDir) {
         // Trying to derive the paths, thus need the raw ABI info from the parsed package, and the
         // current state in PackageSetting is irrelevant.
         return deriveNativeLibraryPaths(new Abis(AndroidPackageUtils.getRawPrimaryCpuAbi(pkg),
                 AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)), appLib32InstallDir, pkg.getPath(),
-                pkg.getBaseApkPath(), pkg.isSystem(), isUpdatedSystemApp);
+                pkg.getBaseApkPath(), isSystemApp, isUpdatedSystemApp);
     }
 
     private static NativeLibraryPaths deriveNativeLibraryPaths(final Abis abis,
@@ -297,7 +297,7 @@
     }
 
     @Override
-    public Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg,
+    public Pair<Abis, NativeLibraryPaths> derivePackageAbi(AndroidPackage pkg, boolean isSystemApp,
             boolean isUpdatedSystemApp, String cpuAbiOverride, File appLib32InstallDir)
             throws PackageManagerException {
         // Give ourselves some initial paths; we'll come back for another
@@ -307,10 +307,10 @@
         final NativeLibraryPaths initialLibraryPaths = deriveNativeLibraryPaths(
                 new Abis(pkgRawPrimaryCpuAbi, pkgRawSecondaryCpuAbi),
                 appLib32InstallDir, pkg.getPath(),
-                pkg.getBaseApkPath(), pkg.isSystem(),
+                pkg.getBaseApkPath(), isSystemApp,
                 isUpdatedSystemApp);
 
-        final boolean extractLibs = shouldExtractLibs(pkg, isUpdatedSystemApp);
+        final boolean extractLibs = shouldExtractLibs(pkg, isSystemApp, isUpdatedSystemApp);
 
         final String nativeLibraryRootStr = initialLibraryPaths.nativeLibraryRootDir;
         final boolean useIsaSpecificSubdirs = initialLibraryPaths.nativeLibraryRootRequiresIsa;
@@ -467,15 +467,16 @@
         final Abis abis = new Abis(primaryCpuAbi, secondaryCpuAbi);
         return new Pair<>(abis,
                 deriveNativeLibraryPaths(abis, appLib32InstallDir,
-                        pkg.getPath(), pkg.getBaseApkPath(), pkg.isSystem(),
+                        pkg.getPath(), pkg.getBaseApkPath(), isSystemApp,
                         isUpdatedSystemApp));
     }
 
-    private boolean shouldExtractLibs(AndroidPackage pkg, boolean isUpdatedSystemApp) {
+    private boolean shouldExtractLibs(AndroidPackage pkg, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         // We shouldn't extract libs if the package is a library or if extractNativeLibs=false
         boolean extractLibs = !AndroidPackageUtils.isLibrary(pkg) && pkg.isExtractNativeLibs();
         // We shouldn't attempt to extract libs from system app when it was not updated.
-        if (pkg.isSystem() && !isUpdatedSystemApp) {
+        if (isSystemApp && !isUpdatedSystemApp) {
             extractLibs = false;
         }
         return extractLibs;
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 49f3a3c..aaf8755 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -79,6 +79,7 @@
 import com.android.server.pm.dex.PackageDexUsage;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 
 import dalvik.system.DexFile;
@@ -472,8 +473,7 @@
             String classLoaderContext, int dexoptFlags, int uid,
             CompilerStats.PackageStats packageStats, boolean downgrade, String profileName,
             String dexMetadataPath, int compilationReason) {
-        String oatDir = getPackageOatDirIfSupported(pkg,
-                pkgSetting.getTransientState().isUpdatedSystemApp());
+        String oatDir = getPackageOatDirIfSupported(pkgSetting, pkg);
 
         int dexoptNeeded = getDexoptNeeded(pkg.getPackageName(), path, isa, compilerFilter,
                 classLoaderContext, profileAnalysisResult, downgrade, dexoptFlags, oatDir);
@@ -1026,8 +1026,9 @@
      * not needed or unsupported for the package.
      */
     @Nullable
-    private String getPackageOatDirIfSupported(AndroidPackage pkg, boolean isUpdatedSystemApp) {
-        if (!AndroidPackageUtils.canHaveOatDir(pkg, isUpdatedSystemApp)) {
+    private String getPackageOatDirIfSupported(@NonNull PackageState packageState,
+            @NonNull AndroidPackage pkg) {
+        if (!AndroidPackageUtils.canHaveOatDir(packageState, pkg)) {
             return null;
         }
         File codePath = new File(pkg.getPath());
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 9c60795..8c5bab6 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -1211,7 +1211,8 @@
             // Take a short detour to confirm with user
             final Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);
             intent.setData(Uri.fromParts("package", versionedPackage.getPackageName(), null));
-            intent.putExtra(PackageInstaller.EXTRA_CALLBACK, adapter.getBinder().asBinder());
+            intent.putExtra(PackageInstaller.EXTRA_CALLBACK,
+                    new PackageManager.UninstallCompleteCallback(adapter.getBinder().asBinder()));
             adapter.onUserActionRequired(intent);
         }
     }
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f65a65d..afcd9d1 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -1910,8 +1910,8 @@
             return true;
         }
 
-        return !existingPkgSetting.getPkg().isSystem()
-                && !existingPkgSetting.getTransientState().isUpdatedSystemApp();
+        return !existingPkgSetting.isSystem()
+                && !existingPkgSetting.isUpdatedSystemApp();
     }
 
     /**
@@ -2579,6 +2579,10 @@
                         : PackageInstaller.ACTION_CONFIRM_INSTALL);
         intent.setPackage(mPm.getPackageInstallerPackageName());
         intent.putExtra(PackageInstaller.EXTRA_SESSION_ID, sessionId);
+        synchronized (mLock) {
+            intent.putExtra(PackageInstaller.EXTRA_RESOLVED_BASE_PATH,
+                    mResolvedBaseFile != null ? mResolvedBaseFile.getAbsolutePath() : null);
+        }
 
         sendOnUserActionRequired(mContext, target, sessionId, intent);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 65e7ce1..cc9c1e0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -473,10 +473,10 @@
     public final ResolveInfo resolveIntentExported(Intent intent, String resolvedType,
             @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
-            boolean resolveForStart, int filterCallingUid) {
+            boolean resolveForStart, int filterCallingUid, int callingPid) {
         return getResolveIntentHelper().resolveIntentInternal(snapshot(),
                 intent, resolvedType, flags, privateResolveFlags, userId, resolveForStart,
-                filterCallingUid, true);
+                filterCallingUid, true, callingPid);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bf6bb22..7ab19ff 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1105,8 +1105,9 @@
     @Deprecated
     @NonNull
     public Computer snapshotComputer(boolean allowLiveComputer) {
+        var isHoldingPackageLock = Thread.holdsLock(mLock);
         if (allowLiveComputer) {
-            if (Thread.holdsLock(mLock)) {
+            if (isHoldingPackageLock) {
                 // If the current thread holds mLock then it may have modified state but not
                 // yet invalidated the snapshot.  Always give the thread the live computer.
                 return mLiveComputer;
@@ -1120,6 +1121,15 @@
             return oldSnapshot.use();
         }
 
+        if (isHoldingPackageLock) {
+            // If the current thread holds mLock then it already has exclusive write access to the
+            // two snapshot fields, and we can just go ahead and rebuild the snapshot.
+            @SuppressWarnings("GuardedBy")
+            var newSnapshot = rebuildSnapshot(oldSnapshot, pendingVersion);
+            sSnapshot.set(newSnapshot);
+            return newSnapshot.use();
+        }
+
         synchronized (mSnapshotLock) {
             // Re-capture pending version in case a new invalidation occurred since last check
             var rebuildSnapshot = sSnapshot.get();
@@ -1137,7 +1147,11 @@
                 // Fetch version one last time to ensure that the rebuilt snapshot matches
                 // the latest invalidation, which could have come in between entering the
                 // SnapshotLock and mLock sync blocks.
+                rebuildSnapshot = sSnapshot.get();
                 rebuildVersion = sSnapshotPendingVersion.get();
+                if (rebuildSnapshot != null && rebuildSnapshot.getVersion() == rebuildVersion) {
+                    return rebuildSnapshot.use();
+                }
 
                 // Build the snapshot for this version
                 var newSnapshot = rebuildSnapshot(rebuildSnapshot, rebuildVersion);
@@ -1147,7 +1161,7 @@
         }
     }
 
-    @GuardedBy({ "mLock", "mSnapshotLock"})
+    @GuardedBy("mLock")
     private Computer rebuildSnapshot(@Nullable Computer oldSnapshot, int newVersion) {
         var now = SystemClock.currentTimeMicro();
         var hits = oldSnapshot == null ? -1 : oldSnapshot.getUsed();
@@ -1574,7 +1588,7 @@
                     Slog.e(TAG, "Failed to find package " + packageName);
                     return;
                 }
-                final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser,
+                final String newSeInfo = SELinuxMMAC.getSeInfo(packageState, pkg, sharedUser,
                         m.mInjector.getCompatibility());
 
                 if (!newSeInfo.equals(oldSeInfo)) {
@@ -1905,7 +1919,8 @@
         mPreferredActivityHelper = new PreferredActivityHelper(this);
         mResolveIntentHelper = new ResolveIntentHelper(mContext, mPreferredActivityHelper,
                 injector.getCompatibility(), mUserManager, mDomainVerificationManager,
-                mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity);
+                mUserNeedsBadging, () -> mResolveInfo, () -> mInstantAppInstallerActivity,
+                injector.getBackgroundHandler());
         mDexOptHelper = new DexOptHelper(this);
         mSuspendPackageHelper = new SuspendPackageHelper(this, mInjector, mBroadcastHelper,
                 mProtectedPackages);
@@ -2241,20 +2256,19 @@
             mRequiredSdkSandboxPackage = getRequiredSdkSandboxPackageName(computer);
 
             // Initialize InstantAppRegistry's Instant App list for all users.
-            for (AndroidPackage pkg : mPackages.values()) {
-                if (pkg.isSystem()) {
-                    continue;
+            forEachPackageState(computer, packageState -> {
+                var pkg = packageState.getAndroidPackage();
+                if (pkg == null || packageState.isSystem()) {
+                    return;
                 }
                 for (int userId : userIds) {
-                    final PackageStateInternal ps =
-                            computer.getPackageStateInternal(pkg.getPackageName());
-                    if (ps == null || !ps.getUserStateOrDefault(userId).isInstantApp()
-                            || !ps.getUserStateOrDefault(userId).isInstalled()) {
+                    if (!packageState.getUserStateOrDefault(userId).isInstantApp()
+                            || !packageState.getUserStateOrDefault(userId).isInstalled()) {
                         continue;
                     }
-                    mInstantAppRegistry.addInstantApp(userId, ps.getAppId());
+                    mInstantAppRegistry.addInstantApp(userId, packageState.getAppId());
                 }
-            }
+            });
 
             mInstallerService = mInjector.getPackageInstallerService();
             final ComponentName instantAppResolverComponent = getInstantAppResolver(computer);
@@ -2878,7 +2892,7 @@
     static void renameStaticSharedLibraryPackage(ParsedPackage parsedPackage) {
         // Derive the new package synthetic package name
         parsedPackage.setPackageName(toStaticSharedLibraryPackageName(
-                parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibVersion()));
+                parsedPackage.getPackageName(), parsedPackage.getStaticSharedLibraryVersion()));
     }
 
     private static String toStaticSharedLibraryPackageName(
@@ -3685,7 +3699,7 @@
         PackageStateInternal packageState = computer.getPackageStateInternal(componentPkgName);
         if (packageState == null || packageState.getPkg() == null
                 || (!packageState.isSystem()
-                && !packageState.getTransientState().isUpdatedSystemApp())) {
+                && !packageState.isUpdatedSystemApp())) {
             throw new SecurityException(
                     "Changing the label is not allowed for " + componentName);
         }
@@ -3887,7 +3901,7 @@
             final AndroidPackage deletedPkg = pkgSetting.getPkg();
             final boolean isSystemStub = (deletedPkg != null)
                     && deletedPkg.isStub()
-                    && deletedPkg.isSystem();
+                    && pkgSetting.isSystem();
             if (isSystemStub
                     && (newState == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                     || newState == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)) {
@@ -6408,7 +6422,7 @@
             }
 
             AndroidPackage pkg = packageState.getPkg();
-            return pkg != null && pkg.isSystem() && pkg.isPersistent();
+            return pkg != null && packageState.isSystem() && pkg.isPersistent();
         }
 
         @Override
@@ -6967,8 +6981,7 @@
         if (packageState == null || packageState.getPkg() == null) {
             return false;
         }
-        return AndroidPackageUtils.canHaveOatDir(packageState.getPkg(),
-                packageState.getTransientState().isUpdatedSystemApp());
+        return AndroidPackageUtils.canHaveOatDir(packageState, packageState.getPkg());
     }
 
     long deleteOatArtifactsOfPackage(@NonNull Computer snapshot, String packageName) {
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index b18179e..433e7a1 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1386,6 +1386,8 @@
         return AndroidPackageUtils.getHiddenApiEnforcementPolicy(getAndroidPackage(), this);
     }
 
+
+
     // Code below generated by codegen v1.0.23.
     //
     // DO NOT MODIFY!
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 41985e3..eb99536 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -46,6 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -163,13 +164,16 @@
         synchronized (mPm.mLock) {
             final AndroidPackage removedPackage = mPm.mPackages.remove(packageName);
             if (removedPackage != null) {
-                cleanPackageDataStructuresLILPw(removedPackage, chatty);
+                // TODO: Use PackageState for isSystem
+                cleanPackageDataStructuresLILPw(removedPackage,
+                        AndroidPackageUtils.isSystem(removedPackage), chatty);
             }
         }
     }
 
     @GuardedBy("mPm.mLock")
-    private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean chatty) {
+    private void cleanPackageDataStructuresLILPw(AndroidPackage pkg, boolean isSystemApp,
+            boolean chatty) {
         mPm.mComponentResolver.removeAllComponents(pkg, chatty);
         mPermissionManager.onPackageRemoved(pkg);
         mPm.getPackageProperty().removeAllProperties(pkg);
@@ -194,7 +198,7 @@
         }
 
         r = null;
-        if (pkg.isSystem()) {
+        if (isSystemApp) {
             // Only system apps can hold shared libraries.
             final int libraryNamesSize = pkg.getLibraryNames().size();
             for (i = 0; i < libraryNamesSize; i++) {
@@ -230,7 +234,7 @@
         }
         if (pkg.getStaticSharedLibraryName() != null) {
             if (mSharedLibraries.removeSharedLibrary(pkg.getStaticSharedLibraryName(),
-                    pkg.getStaticSharedLibVersion())) {
+                    pkg.getStaticSharedLibraryVersion())) {
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
@@ -321,7 +325,7 @@
                     List<AndroidPackage> sharedUserPkgs =
                             sus != null ? sus.getPackages() : Collections.emptyList();
                     mPermissionManager.onPackageUninstalled(packageName, deletedPs.getAppId(),
-                            deletedPkg, sharedUserPkgs, UserHandle.USER_ALL);
+                            deletedPs, deletedPkg, sharedUserPkgs, UserHandle.USER_ALL);
                     // After permissions are handled, check if the shared user can be migrated
                     if (sus != null) {
                         mPm.mSettings.checkAndConvertSharedUserSettingsLPw(sus);
diff --git a/services/core/java/com/android/server/pm/ResolveIntentHelper.java b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
index 622c6ee..970f8ce 100644
--- a/services/core/java/com/android/server/pm/ResolveIntentHelper.java
+++ b/services/core/java/com/android/server/pm/ResolveIntentHelper.java
@@ -25,6 +25,8 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.IUnsafeIntentStrictModeCallback;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -42,6 +44,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.Trace;
@@ -53,6 +56,7 @@
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -84,6 +88,8 @@
     private final Supplier<ResolveInfo> mResolveInfoSupplier;
     @NonNull
     private final Supplier<ActivityInfo> mInstantAppInstallerActivitySupplier;
+    @NonNull
+    private final Handler mHandler;
 
     ResolveIntentHelper(@NonNull Context context,
             @NonNull PreferredActivityHelper preferredActivityHelper,
@@ -91,7 +97,8 @@
             @NonNull DomainVerificationManagerInternal domainVerificationManager,
             @NonNull UserNeedsBadgingCache userNeedsBadgingCache,
             @NonNull Supplier<ResolveInfo> resolveInfoSupplier,
-            @NonNull Supplier<ActivityInfo> instantAppInstallerActivitySupplier) {
+            @NonNull Supplier<ActivityInfo> instantAppInstallerActivitySupplier,
+            @NonNull Handler handler) {
         mContext = context;
         mPreferredActivityHelper = preferredActivityHelper;
         mPlatformCompat = platformCompat;
@@ -100,11 +107,12 @@
         mUserNeedsBadging = userNeedsBadgingCache;
         mResolveInfoSupplier = resolveInfoSupplier;
         mInstantAppInstallerActivitySupplier = instantAppInstallerActivitySupplier;
+        mHandler = handler;
     }
 
     private static void filterNonExportedComponents(Intent intent, int filterCallingUid,
-            List<ResolveInfo> query, PlatformCompat platformCompat, String resolvedType,
-            Computer computer) {
+            int callingPid, List<ResolveInfo> query, PlatformCompat platformCompat,
+            String resolvedType, Computer computer, Handler handler) {
         if (query == null
                 || intent.getPackage() != null
                 || intent.getComponent() != null
@@ -113,6 +121,10 @@
         }
         AndroidPackage caller = computer.getPackage(filterCallingUid);
         String callerPackage = caller == null ? "Not specified" : caller.getPackageName();
+        ActivityManagerInternal activityManagerInternal = LocalServices
+                .getService(ActivityManagerInternal.class);
+        final IUnsafeIntentStrictModeCallback callback = activityManagerInternal
+                .getRegisteredStrictModeCallback(callingPid);
         for (int i = query.size() - 1; i >= 0; i--) {
             if (!query.get(i).getComponentInfo().exported) {
                 boolean hasToBeExportedToMatch = platformCompat.isChangeEnabledByUid(
@@ -130,6 +142,15 @@
                         resolvedType,
                         intent.getScheme(),
                         hasToBeExportedToMatch);
+                if (callback != null) {
+                    handler.post(() -> {
+                        try {
+                            callback.onImplicitIntentMatchedInternalComponent(intent.cloneFilter());
+                        } catch (RemoteException e) {
+                            activityManagerInternal.unregisterStrictModeCallback(callingPid);
+                        }
+                    });
+                }
                 if (!hasToBeExportedToMatch) {
                     return;
                 }
@@ -148,7 +169,7 @@
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
             boolean resolveForStart, int filterCallingUid) {
         return resolveIntentInternal(computer, intent, resolvedType, flags,
-                privateResolveFlags, userId, resolveForStart, filterCallingUid, false);
+                privateResolveFlags, userId, resolveForStart, filterCallingUid, false, 0);
     }
 
     /**
@@ -160,7 +181,8 @@
     public ResolveInfo resolveIntentInternal(Computer computer, Intent intent, String resolvedType,
             @PackageManager.ResolveInfoFlagsBits long flags,
             @PackageManagerInternal.PrivateResolveFlags long privateResolveFlags, int userId,
-            boolean resolveForStart, int filterCallingUid, boolean exportedComponentsOnly) {
+            boolean resolveForStart, int filterCallingUid, boolean exportedComponentsOnly,
+            int callingPid) {
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "resolveIntent");
 
@@ -177,8 +199,8 @@
                     resolvedType, flags, privateResolveFlags, filterCallingUid, userId,
                     resolveForStart, true /*allowDynamicSplits*/);
             if (exportedComponentsOnly) {
-                filterNonExportedComponents(intent, filterCallingUid, query,
-                        mPlatformCompat, resolvedType, computer);
+                filterNonExportedComponents(intent, filterCallingUid, callingPid, query,
+                        mPlatformCompat, resolvedType, computer, mHandler);
             }
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index f7e04d4..2596006 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.Disabled;
 import android.compat.annotation.EnabledAfter;
@@ -29,6 +31,7 @@
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.SharedUserApi;
 
 import libcore.io.IoUtils;
@@ -381,14 +384,15 @@
      * @param compatibility     the PlatformCompat service to ask about state of compat changes.
      * @return String representing the resulting seinfo.
      */
-    public static String getSeInfo(AndroidPackage pkg, SharedUserApi sharedUser,
-            PlatformCompat compatibility) {
+    public static String getSeInfo(@NonNull PackageState packageState, @NonNull AndroidPackage pkg,
+            @Nullable SharedUserApi sharedUser, @NonNull PlatformCompat compatibility) {
         final int targetSdkVersion = getTargetSdkVersionForSeInfo(pkg, sharedUser,
                 compatibility);
         // TODO(b/71593002): isPrivileged for sharedUser and appInfo should never be out of sync.
         // They currently can be if the sharedUser apps are signed with the platform key.
-        final boolean isPrivileged = (sharedUser != null)
-                ? sharedUser.isPrivileged() | pkg.isPrivileged() : pkg.isPrivileged();
+        final boolean isPrivileged =
+                (sharedUser != null) ? sharedUser.isPrivileged() | packageState.isPrivileged()
+                        : packageState.isPrivileged();
         return getSeInfo(pkg, isPrivileged, targetSdkVersion);
     }
 
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 6572d7b..386e403 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -265,23 +265,24 @@
             pkgSetting.getPkgState().setUpdatedSystemApp(true);
         }
 
-        pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(parsedPackage,
+        pkgSetting.getTransientState().setSeInfo(SELinuxMMAC.getSeInfo(pkgSetting, parsedPackage,
                 sharedUserSetting, injector.getCompatibility()));
 
-        if (parsedPackage.isSystem()) {
+        if (pkgSetting.isSystem()) {
             configurePackageComponents(parsedPackage);
         }
 
         final String cpuAbiOverride = deriveAbiOverride(request.mCpuAbiOverride);
-        final boolean isUpdatedSystemApp = pkgSetting.getPkgState().isUpdatedSystemApp();
+        final boolean isSystemApp = pkgSetting.isSystem();
+        final boolean isUpdatedSystemApp = pkgSetting.isUpdatedSystemApp();
 
         final File appLib32InstallDir = getAppLib32InstallDir();
         if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
             if (needToDeriveAbi) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
                 final Pair<PackageAbiHelper.Abis, PackageAbiHelper.NativeLibraryPaths> derivedAbi =
-                        packageAbiHelper.derivePackageAbi(parsedPackage, isUpdatedSystemApp,
-                                cpuAbiOverride, appLib32InstallDir);
+                        packageAbiHelper.derivePackageAbi(parsedPackage, isSystemApp,
+                                isUpdatedSystemApp, cpuAbiOverride, appLib32InstallDir);
                 derivedAbi.first.applyTo(parsedPackage);
                 derivedAbi.second.applyTo(parsedPackage);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -291,14 +292,13 @@
                 // structure. Try to detect abi based on directory structure.
 
                 String pkgRawPrimaryCpuAbi = AndroidPackageUtils.getRawPrimaryCpuAbi(parsedPackage);
-                if (parsedPackage.isSystem() && !isUpdatedSystemApp
-                        && pkgRawPrimaryCpuAbi == null) {
+                if (isSystemApp && !isUpdatedSystemApp && pkgRawPrimaryCpuAbi == null) {
                     final PackageAbiHelper.Abis abis = packageAbiHelper.getBundledAppAbis(
                             parsedPackage);
                     abis.applyTo(parsedPackage);
                     abis.applyTo(pkgSetting);
                     final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                            packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
                                     isUpdatedSystemApp, appLib32InstallDir);
                     nativeLibraryPaths.applyTo(parsedPackage);
                 }
@@ -310,7 +310,7 @@
                         .setSecondaryCpuAbi(secondaryCpuAbiFromSettings);
 
                 final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage,
+                        packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
                                 isUpdatedSystemApp, appLib32InstallDir);
                 nativeLibraryPaths.applyTo(parsedPackage);
 
@@ -336,8 +336,8 @@
             // ABIs we determined during compilation, but the path will depend on the final
             // package path (after the rename away from the stage path).
             final PackageAbiHelper.NativeLibraryPaths nativeLibraryPaths =
-                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isUpdatedSystemApp,
-                            appLib32InstallDir);
+                    packageAbiHelper.deriveNativeLibraryPaths(parsedPackage, isSystemApp,
+                            isUpdatedSystemApp, appLib32InstallDir);
             nativeLibraryPaths.applyTo(parsedPackage);
         }
 
@@ -398,7 +398,7 @@
         parsedPackage.setFactoryTest(isUnderFactoryTest && parsedPackage.getRequestedPermissions()
                 .contains(android.Manifest.permission.FACTORY_TEST));
 
-        if (parsedPackage.isSystem()) {
+        if (isSystemApp) {
             pkgSetting.setIsOrphaned(true);
         }
 
@@ -428,8 +428,8 @@
         pkgSetting.setLastModifiedTime(scanFileTime);
         // TODO(b/135203078): Remove, move to constructor
         pkgSetting.setPkg(parsedPackage)
-                .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
-                        PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
+                .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
+                .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
         if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
             pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
         }
@@ -707,9 +707,9 @@
      * been installed under one of this package's original names.
      */
     public static @Nullable String getRealPackageName(@NonNull AndroidPackage pkg,
-            @Nullable String renamedPkgName) {
+            @Nullable String renamedPkgName, boolean isSystemApp) {
         if (isPackageRenamed(pkg, renamedPkgName)) {
-            return AndroidPackageUtils.getRealPackageOrNull(pkg);
+            return AndroidPackageUtils.getRealPackageOrNull(pkg, isSystemApp);
         }
         return null;
     }
@@ -825,7 +825,17 @@
     public static void applyPolicy(ParsedPackage parsedPackage,
             final @PackageManagerService.ScanFlags int scanFlags,
             @Nullable AndroidPackage platformPkg, boolean isUpdatedSystemApp) {
+        // TODO: In the real APIs, an updated system app is always a system app, but that may not
+        //  hold true during scan because PMS doesn't propagate the SCAN_AS_SYSTEM flag for the data
+        //  directory. This tries to emulate that behavior by using either the flag or the boolean,
+        //  but this logic is fragile. Specifically, it may affect the PackageBackwardCompatibility
+        //  checker, which switches branches based on whether an app is a system app. When install
+        //  is refactored, the scan policy flags should not be read this late and instead passed
+        //  around in the PackageSetting or a temporary object which infers these values early, so
+        //  that all further consumers agree on their values.
+        boolean isSystemApp = isUpdatedSystemApp;
         if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+            isSystemApp = true;
             parsedPackage.setSystem(true);
             // TODO(b/135203078): Can this be done in PackageParser? Or just inferred when the flag
             //  is set during parse.
@@ -871,13 +881,14 @@
                 ) == PackageManager.SIGNATURE_MATCH))
         );
 
-        if (!parsedPackage.isSystem()) {
+        if (!isSystemApp) {
             // Only system apps can use these features.
             parsedPackage.clearOriginalPackages()
                     .clearAdoptPermissions();
         }
 
-        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
+        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isSystemApp,
+                isUpdatedSystemApp);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index 61a251e..9054c85 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.permission.LegacyPermissionState;
@@ -125,34 +124,13 @@
     }
 
     public SettingBase setFlags(int pkgFlags) {
-        this.mPkgFlags = pkgFlags
-                & (ApplicationInfo.FLAG_SYSTEM
-                        | ApplicationInfo.FLAG_EXTERNAL_STORAGE
-                        | ApplicationInfo.FLAG_TEST_ONLY);
+        this.mPkgFlags = pkgFlags;
         onChanged();
         return this;
     }
 
     public SettingBase setPrivateFlags(int pkgPrivateFlags) {
-        this.mPkgPrivateFlags = pkgPrivateFlags
-                & (ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
-                | ApplicationInfo.PRIVATE_FLAG_OEM
-                | ApplicationInfo.PRIVATE_FLAG_VENDOR
-                | ApplicationInfo.PRIVATE_FLAG_PRODUCT
-                | ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT
-                | ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER
-                | ApplicationInfo.PRIVATE_FLAG_ODM);
-        onChanged();
-        return this;
-    }
-
-    /**
-     * Unconditionally set both mPkgFlags and mPkgPrivateFlags.
-     * Should not be used outside pkgSetting initialization or update.
-     */
-    SettingBase setPkgFlags(int flags, int privateFlags) {
-        this.mPkgFlags = flags;
-        this.mPkgPrivateFlags = privateFlags;
+        this.mPkgPrivateFlags = pkgPrivateFlags;
         onChanged();
         return this;
     }
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 7fec0eb00f..ff020eb 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -165,6 +165,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 /**
@@ -873,8 +874,8 @@
         }
         final PackageSetting dp = mDisabledSysPackages.get(name);
         // always make sure the system package code and resource paths dont change
-        if (dp == null && p.getPkg() != null && p.getPkg().isSystem()
-                && !p.getPkgState().isUpdatedSystemApp()) {
+        if (dp == null && p.getPkg() != null && p.isSystem()
+                && !p.isUpdatedSystemApp()) {
             final PackageSetting disabled;
             if (replaced) {
                 // a little trick...  when we install the new package, we don't
@@ -1057,7 +1058,8 @@
                     // Update new package state.
                     .setLastModifiedTime(codePath.lastModified())
                     .setDomainSetId(domainSetId);
-            pkgSetting.setPkgFlags(pkgFlags, pkgPrivateFlags);
+            pkgSetting.setFlags(pkgFlags)
+                    .setPrivateFlags(pkgPrivateFlags);
         } else {
             pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
                     legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
@@ -1251,7 +1253,7 @@
         newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
         newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
         // Only set pkgFlags.
-        pkgSetting.setPkgFlags(newPkgFlags, pkgSetting.getPrivateFlags());
+        pkgSetting.setFlags(newPkgFlags);
 
         boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags()
                 & ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
@@ -1453,7 +1455,7 @@
     void checkAndConvertSharedUserSettingsLPw(SharedUserSetting sharedUser) {
         if (!sharedUser.isSingleUser()) return;
         final AndroidPackage pkg = sharedUser.getPackageSettings().valueAt(0).getPkg();
-        if (pkg != null && pkg.isLeavingSharedUid()
+        if (pkg != null && pkg.isLeavingSharedUser()
                 && SharedUidMigration.applyStrategy(BEST_EFFORT)) {
             convertSharedUserSettingsLPw(sharedUser);
         }
@@ -4855,7 +4857,7 @@
                 pw.print(prefix); pw.println("  static library:");
                 pw.print(prefix); pw.print("    ");
                 pw.print("name:"); pw.print(pkg.getStaticSharedLibraryName());
-                pw.print(" version:"); pw.println(pkg.getStaticSharedLibVersion());
+                pw.print(" version:"); pw.println(pkg.getStaticSharedLibraryVersion());
             }
 
             if (pkg.getSdkLibraryName() != null) {
@@ -5634,7 +5636,7 @@
         @GuardedBy("mLock")
         // Tracking the mutations that haven't yet been written to legacy state.
         // This avoids unnecessary work when writing settings for multiple users.
-        private boolean mIsLegacyPermissionStateStale = false;
+        private AtomicBoolean mIsLegacyPermissionStateStale = new AtomicBoolean(false);
 
         @GuardedBy("mLock")
         // The mapping keys are user ids.
@@ -5725,8 +5727,8 @@
         }
 
         public void writeStateForUserAsync(int userId) {
+            mIsLegacyPermissionStateStale.set(true);
             synchronized (mLock) {
-                mIsLegacyPermissionStateStale = true;
                 final long currentTimeMillis = SystemClock.uptimeMillis();
                 final long writePermissionDelayMillis = nextWritePermissionDelayMillis();
 
@@ -5773,24 +5775,18 @@
             }
 
             Runnable writer = () -> {
-                final int version;
-                final String fingerprint;
-                final boolean isLegacyPermissionStateStale;
-                synchronized (mLock) {
-                    version = mVersions.get(userId, INITIAL_VERSION);
-                    fingerprint = mFingerprints.get(userId);
-                    isLegacyPermissionStateStale = mIsLegacyPermissionStateStale;
-                    mIsLegacyPermissionStateStale = false;
-                }
+                boolean isLegacyPermissionStateStale = mIsLegacyPermissionStateStale.getAndSet(
+                        false);
 
-                final RuntimePermissionsState runtimePermissions;
+                final Map<String, List<RuntimePermissionsState.PermissionState>>
+                        packagePermissions = new ArrayMap<>();
+                final Map<String, List<RuntimePermissionsState.PermissionState>>
+                        sharedUserPermissions = new ArrayMap<>();
                 synchronized (pmLock) {
                     if (sync || isLegacyPermissionStateStale) {
                         legacyPermissionDataProvider.writeLegacyPermissionStateTEMP();
                     }
 
-                    Map<String, List<RuntimePermissionsState.PermissionState>> packagePermissions =
-                            new ArrayMap<>();
                     int packagesSize = packageStates.size();
                     for (int i = 0; i < packagesSize; i++) {
                         String packageName = packageStates.keyAt(i);
@@ -5810,9 +5806,6 @@
                         }
                     }
 
-                    Map<String, List<RuntimePermissionsState.PermissionState>>
-                            sharedUserPermissions =
-                            new ArrayMap<>();
                     final int sharedUsersSize = sharedUsers.size();
                     for (int i = 0; i < sharedUsersSize; i++) {
                         String sharedUserName = sharedUsers.keyAt(i);
@@ -5822,11 +5815,13 @@
                                         sharedUserSetting.getLegacyPermissionState(), userId);
                         sharedUserPermissions.put(sharedUserName, permissions);
                     }
-
-                    runtimePermissions = new RuntimePermissionsState(version,
-                            fingerprint, packagePermissions, sharedUserPermissions);
                 }
                 synchronized (mLock) {
+                    int version = mVersions.get(userId, INITIAL_VERSION);
+                    String fingerprint = mFingerprints.get(userId);
+
+                    RuntimePermissionsState runtimePermissions = new RuntimePermissionsState(
+                            version, fingerprint, packagePermissions, sharedUserPermissions);
                     mPendingStatesToWrite.put(userId, runtimePermissions);
                 }
                 if (pmHandler != null) {
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index aa23d8d..d2ce23e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -358,7 +358,7 @@
                     continue;
                 }
 
-                if (ps.getPkg().isSystem()) {
+                if (ps.isSystem()) {
                     continue;
                 }
 
@@ -408,7 +408,7 @@
         final int versionCount = versionedLib.size();
         for (int i = 0; i < versionCount; i++) {
             final long libVersion = versionedLib.keyAt(i);
-            if (libVersion < pkg.getStaticSharedLibVersion()) {
+            if (libVersion < pkg.getStaticSharedLibraryVersion()) {
                 previousLibVersion = Math.max(previousLibVersion, libVersion);
             }
         }
@@ -468,7 +468,7 @@
                 }
             } else if (pkg.getStaticSharedLibraryName() != null) {
                 SharedLibraryInfo definedLibrary = getSharedLibraryInfo(
-                        pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibVersion());
+                        pkg.getStaticSharedLibraryName(), pkg.getStaticSharedLibraryVersion());
                 if (definedLibrary != null) {
                     action.accept(definedLibrary, libInfo);
                 }
@@ -722,8 +722,8 @@
                     // it is better for the user to reinstall than to be in an limbo
                     // state. Also libs disappearing under an app should never happen
                     // - just in case.
-                    if (!pkg.isSystem() || pkgSetting.getPkgState().isUpdatedSystemApp()) {
-                        final int flags = pkgSetting.getPkgState().isUpdatedSystemApp()
+                    if (!pkgSetting.isSystem() || pkgSetting.isUpdatedSystemApp()) {
+                        final int flags = pkgSetting.isUpdatedSystemApp()
                                 ? PackageManager.DELETE_KEEP_DATA : 0;
                         synchronized (mPm.mInstallLock) {
                             mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true,
@@ -842,13 +842,15 @@
         if (installRequest.getStaticSharedLibraryInfo() != null) {
             return Collections.singletonList(installRequest.getStaticSharedLibraryInfo());
         }
-        final boolean hasDynamicLibraries = parsedPackage != null && parsedPackage.isSystem()
+        boolean isSystemApp = installRequest.getScannedPackageSetting() != null
+                && installRequest.getScannedPackageSetting().isSystem();
+        final boolean hasDynamicLibraries = parsedPackage != null && isSystemApp
                 && installRequest.getDynamicSharedLibraryInfos() != null;
         if (!hasDynamicLibraries) {
             return null;
         }
         final boolean isUpdatedSystemApp = installRequest.getScannedPackageSetting() != null
-                && installRequest.getScannedPackageSetting().getPkgState().isUpdatedSystemApp();
+                && installRequest.getScannedPackageSetting().isUpdatedSystemApp();
         // We may not yet have disabled the updated package yet, so be sure to grab the
         // current setting if that's the case.
         final PackageSetting updatedSystemPs = isUpdatedSystemApp
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index fb2ba32..a037ae8 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -253,7 +253,7 @@
         }
         if (mDisabledPackages.size() == 1) {
             final AndroidPackage pkg = mDisabledPackages.valueAt(0).getPkg();
-            return pkg != null && pkg.isLeavingSharedUid();
+            return pkg != null && pkg.isLeavingSharedUser();
         }
         return true;
     }
@@ -284,7 +284,7 @@
             if ((ps == null) || (ps.getPkg() == null)) {
                 continue;
             }
-            final boolean isPrivileged = isPrivileged() | ps.getPkg().isPrivileged();
+            final boolean isPrivileged = isPrivileged() | ps.isPrivileged();
             ps.getPkgState().setOverrideSeInfo(SELinuxMMAC.getSeInfo(ps.getPkg(), isPrivileged,
                     seInfoTargetSdkVersion));
             onChanged();
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 4fddc9c..0362ddd 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -1470,15 +1470,9 @@
         }
 
         // 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--) {
-            int count = counts.valueAt(i);
-            service.enforceMaxActivityShortcuts(count);
-            total += count;
+            service.enforceMaxActivityShortcuts(counts.valueAt(i));
         }
-
-        // 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 12a33ee..83720f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -181,9 +181,6 @@
     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
@@ -260,11 +257,6 @@
         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";
@@ -337,14 +329,9 @@
             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 mMaxShortcutsPerApp;
+    private int mMaxShortcuts;
 
     /**
      * Max number of updating API calls that each application can make during the interval.
@@ -817,12 +804,9 @@
         mMaxUpdatesPerInterval = Math.max(0, (int) parser.getLong(
                 ConfigConstants.KEY_MAX_UPDATES_PER_INTERVAL, DEFAULT_MAX_UPDATES_PER_INTERVAL));
 
-        mMaxShortcutsPerActivity = Math.max(0, (int) parser.getLong(
+        mMaxShortcuts = 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,
@@ -1762,33 +1746,16 @@
      *                                  {@link #getMaxActivityShortcuts()}.
      */
     void enforceMaxActivityShortcuts(int numShortcuts) {
-        if (numShortcuts > mMaxShortcutsPerActivity) {
+        if (numShortcuts > mMaxShortcuts) {
             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 mMaxShortcutsPerActivity;
-    }
-
-    /**
-     * Return the max number of dynamic + manifest shortcuts for each launcher icon.
-     */
-    int getMaxAppShortcuts() {
-        return mMaxShortcutsPerApp;
+        return mMaxShortcuts;
     }
 
     /**
@@ -2221,8 +2188,6 @@
             ps.ensureNotImmutable(shortcut.getId(), /*ignoreInvisible=*/ true);
             fillInDefaultActivity(Arrays.asList(shortcut));
 
-            enforceMaxAppShortcuts(ps.getShortcutCount());
-
             if (!shortcut.hasRank()) {
                 shortcut.setRank(0);
             }
@@ -2610,7 +2575,7 @@
             throws RemoteException {
         verifyCaller(packageName, userId);
 
-        return mMaxShortcutsPerActivity;
+        return mMaxShortcuts;
     }
 
     @Override
@@ -4759,7 +4724,7 @@
                 pw.print("    maxUpdatesPerInterval: ");
                 pw.println(mMaxUpdatesPerInterval);
                 pw.print("    maxShortcutsPerActivity: ");
-                pw.println(mMaxShortcutsPerActivity);
+                pw.println(mMaxShortcuts);
                 pw.println();
 
                 mStatLogger.dump(pw, "  ");
@@ -5246,7 +5211,7 @@
 
     @VisibleForTesting
     int getMaxShortcutsForTest() {
-        return mMaxShortcutsPerActivity;
+        return mMaxShortcuts;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index e660046..6bac905 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1473,6 +1473,27 @@
         }
     }
 
+    @Override
+    public void revokeUserAdmin(@UserIdInt int userId) {
+        checkManageUserAndAcrossUsersFullPermission("revoke admin privileges");
+
+        synchronized (mPackagesLock) {
+            UserInfo info;
+            synchronized (mUsersLock) {
+                info = getUserInfoLU(userId);
+            }
+            if (info == null || !info.isAdmin()) {
+                // Exit if no user found with that id, or the user is not an Admin.
+                return;
+            }
+
+            info.flags ^= UserInfo.FLAG_ADMIN;
+            synchronized (mUsersLock) {
+                writeUserLP(getUserDataLU(info.id));
+            }
+        }
+    }
+
     /**
      * Evicts a user's CE key by stopping and restarting the user.
      *
@@ -2441,9 +2462,9 @@
             mGuestRestrictions.clear();
             mGuestRestrictions.putAll(restrictions);
             final List<UserInfo> guests = getGuestUsers();
-            for (UserInfo guest : guests) {
+            for (int i = 0; i < guests.size(); i++) {
                 synchronized (mRestrictionsLock) {
-                    updateUserRestrictionsInternalLR(mGuestRestrictions, guest.id);
+                    updateUserRestrictionsInternalLR(mGuestRestrictions, guests.get(i).id);
                 }
             }
         }
@@ -3688,7 +3709,8 @@
             }
             // DISALLOW_CONFIG_WIFI was made a default guest restriction some time during version 6.
             final List<UserInfo> guestUsers = getGuestUsers();
-            for (UserInfo guestUser : guestUsers) {
+            for (int i = 0; i < guestUsers.size(); i++) {
+                final UserInfo guestUser = guestUsers.get(i);
                 if (guestUser != null && !hasUserRestriction(
                         UserManager.DISALLOW_CONFIG_WIFI, guestUser.id)) {
                     setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, guestUser.id);
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 2d876ed..12c9e98 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -352,10 +352,11 @@
         final String notSystemFmt = "%s is allowlisted and present but not a system package.";
         final String overlayFmt = "%s is allowlisted unnecessarily since it's a static overlay.";
         for (String pkgName : allWhitelistedPackages) {
-            final AndroidPackage pkg = pmInt.getPackage(pkgName);
+            var packageState = pmInt.getPackageStateInternal(pkgName);
+            var pkg = packageState == null ? null : packageState.getAndroidPackage();
             if (pkg == null) {
                 warnings.add(String.format(notPresentFmt, pkgName));
-            } else if (!pkg.isSystem()) {
+            } else if (!packageState.isSystem()) {
                 warnings.add(String.format(notSystemFmt, pkgName));
             } else if (shouldUseOverlayTargetName(pkg)) {
                 warnings.add(String.format(overlayFmt, pkgName));
@@ -380,8 +381,9 @@
         // Check whether all system packages are indeed allowlisted.
         final String logMessageFmt = "System package %s is not whitelisted using "
                 + "'install-in-user-type' in SystemConfig for any user types!";
-        pmInt.forEachPackage(pkg -> {
-            if (!pkg.isSystem() || pkg.isApex()) return;
+        pmInt.forEachPackageState(packageState -> {
+            var pkg = packageState.getAndroidPackage();
+            if (pkg == null || !packageState.isSystem() || pkg.isApex()) return;
             final String pkgName = pkg.getManifestPackageName();
             if (!allWhitelistedPackages.contains(pkgName)
                     && !shouldUseOverlayTargetName(pmInt.getPackage(pkgName))) {
@@ -523,8 +525,9 @@
 
         final Set<String> installPackages = new ArraySet<>();
         final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-        pmInt.forEachPackage(pkg -> {
-            if (!pkg.isSystem()) {
+        pmInt.forEachPackageState(packageState -> {
+            var pkg = packageState.getAndroidPackage();
+            if (pkg == null || !packageState.isSystem()) {
                 return;
             }
             if (shouldInstallPackage(pkg, mWhitelistedPackagesForUserTypes,
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
index 40d87bc..d8e4dac 100644
--- a/services/core/java/com/android/server/pm/UserVisibilityMediator.java
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -100,20 +100,20 @@
 
     private final Object mLock = new Object();
 
-    private final boolean mUsersOnSecondaryDisplaysEnabled;
+    private final boolean mVisibleBackgroundUsersEnabled;
 
     @UserIdInt
     @GuardedBy("mLock")
     private int mCurrentUserId = INITIAL_CURRENT_USER_ID;
 
     /**
-     * Map of background users started on secondary displays.
+     * Map of background users started visible on displays (key is user id, value is display id).
      *
      * <p>Only set when {@code mUsersOnSecondaryDisplaysEnabled} is {@code true}.
      */
     @Nullable
     @GuardedBy("mLock")
-    private final SparseIntArray mUsersOnSecondaryDisplays;
+    private final SparseIntArray mUsersOnDisplaysMap;
 
     /**
      * Mapping from each started user to its profile group.
@@ -131,13 +131,13 @@
             new CopyOnWriteArrayList<>();
 
     UserVisibilityMediator(Handler handler) {
-        this(UserManager.isUsersOnSecondaryDisplaysEnabled(), handler);
+        this(UserManager.isVisibleBackgroundUsersEnabled(), handler);
     }
 
     @VisibleForTesting
-    UserVisibilityMediator(boolean usersOnSecondaryDisplaysEnabled, Handler handler) {
-        mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
-        mUsersOnSecondaryDisplays = mUsersOnSecondaryDisplaysEnabled ? new SparseIntArray() : null;
+    UserVisibilityMediator(boolean backgroundUsersOnDisplaysEnabled, Handler handler) {
+        mVisibleBackgroundUsersEnabled = backgroundUsersOnDisplaysEnabled;
+        mUsersOnDisplaysMap = mVisibleBackgroundUsersEnabled ? new SparseIntArray() : null;
         mHandler = handler;
         // TODO(b/242195409): might need to change this if boot logic is refactored for HSUM devices
         mStartedProfileGroupIds.put(INITIAL_CURRENT_USER_ID, INITIAL_CURRENT_USER_ID);
@@ -207,7 +207,7 @@
                     if (DBG) {
                         Slogf.d(TAG, "adding user / display mapping (%d -> %d)", userId, displayId);
                     }
-                    mUsersOnSecondaryDisplays.put(userId, displayId);
+                    mUsersOnDisplaysMap.put(userId, displayId);
                     break;
                 case SECONDARY_DISPLAY_MAPPING_NOT_NEEDED:
                     if (DBG) {
@@ -260,7 +260,7 @@
                         foreground, displayId);
                 return USER_ASSIGNMENT_RESULT_FAILURE;
             }
-            if (!mUsersOnSecondaryDisplaysEnabled) {
+            if (!mVisibleBackgroundUsersEnabled) {
                 Slogf.w(TAG, "getUserVisibilityOnStartLocked(%d, %d, %b, %d) failed: called on "
                         + "device that doesn't support multiple users on multiple displays",
                         userId, profileGroupId, foreground, displayId);
@@ -300,7 +300,7 @@
     private int canAssignUserToDisplayLocked(@UserIdInt int userId,
             @UserIdInt int profileGroupId, int displayId) {
         if (displayId == DEFAULT_DISPLAY
-                && (!mUsersOnSecondaryDisplaysEnabled || !isProfile(userId, profileGroupId))) {
+                && (!mVisibleBackgroundUsersEnabled || !isProfile(userId, profileGroupId))) {
             // Don't need to do anything because methods (such as isUserVisible()) already
             // know that the current user (and its profiles) is assigned to the default display.
             // But on MUMD devices, profiles are only supported in the default display, so it
@@ -341,9 +341,9 @@
         }
 
         // Check if display is available
-        for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-            int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
-            int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+        for (int i = 0; i < mUsersOnDisplaysMap.size(); i++) {
+            int assignedUserId = mUsersOnDisplaysMap.keyAt(i);
+            int assignedDisplayId = mUsersOnDisplaysMap.valueAt(i);
             if (DBG) {
                 Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
                         i, assignedUserId, assignedDisplayId);
@@ -388,7 +388,7 @@
         }
         mStartedProfileGroupIds.delete(userId);
 
-        if (!mUsersOnSecondaryDisplaysEnabled) {
+        if (!mVisibleBackgroundUsersEnabled) {
             // Don't need to do update mUsersOnSecondaryDisplays because methods (such as
             // isUserVisible()) already know that the current user (and their profiles) is
             // assigned to the default display.
@@ -396,9 +396,9 @@
         }
         if (DBG) {
             Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
-                    mUsersOnSecondaryDisplays);
+                    mUsersOnDisplaysMap);
         }
-        mUsersOnSecondaryDisplays.delete(userId);
+        mUsersOnDisplaysMap.delete(userId);
     }
 
     /**
@@ -415,7 +415,7 @@
 
         // Device doesn't support multiple users on multiple displays, so only users checked above
         // can be visible
-        if (!mUsersOnSecondaryDisplaysEnabled) {
+        if (!mVisibleBackgroundUsersEnabled) {
             if (DBG) {
                 Slogf.d(TAG, "isUserVisible(%d): false for non-current user on MUMD", userId);
             }
@@ -424,7 +424,7 @@
 
         boolean visible;
         synchronized (mLock) {
-            visible = mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+            visible = mUsersOnDisplaysMap.indexOfKey(userId) >= 0;
         }
         if (DBG) {
             Slogf.d(TAG, "isUserVisible(%d): %b from mapping", userId, visible);
@@ -440,7 +440,7 @@
             return false;
         }
 
-        if (!mUsersOnSecondaryDisplaysEnabled || displayId == Display.DEFAULT_DISPLAY) {
+        if (!mVisibleBackgroundUsersEnabled || displayId == Display.DEFAULT_DISPLAY) {
             // TODO(b/245939659): will need to move the displayId == Display.DEFAULT_DISPLAY outside
             // once it supports background users on DEFAULT_DISPLAY (for example, passengers in a
             // no-driver configuration)
@@ -448,7 +448,7 @@
         }
 
         synchronized (mLock) {
-            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
+            return mUsersOnDisplaysMap.get(userId, Display.INVALID_DISPLAY) == displayId;
         }
     }
 
@@ -460,12 +460,12 @@
             return Display.DEFAULT_DISPLAY;
         }
 
-        if (!mUsersOnSecondaryDisplaysEnabled) {
+        if (!mVisibleBackgroundUsersEnabled) {
             return Display.INVALID_DISPLAY;
         }
 
         synchronized (mLock) {
-            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
+            return mUsersOnDisplaysMap.get(userId, Display.INVALID_DISPLAY);
         }
     }
 
@@ -473,16 +473,16 @@
      * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
      */
     public int getUserAssignedToDisplay(@UserIdInt int displayId) {
-        if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
+        if (displayId == Display.DEFAULT_DISPLAY || !mVisibleBackgroundUsersEnabled) {
             return getCurrentUserId();
         }
 
         synchronized (mLock) {
-            for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-                if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
+            for (int i = 0; i < mUsersOnDisplaysMap.size(); i++) {
+                if (mUsersOnDisplaysMap.valueAt(i) != displayId) {
                     continue;
                 }
-                int userId = mUsersOnSecondaryDisplays.keyAt(i);
+                int userId = mUsersOnDisplaysMap.keyAt(i);
                 if (!isStartedProfile(userId)) {
                     return userId;
                 } else if (DBG) {
@@ -615,12 +615,11 @@
             dumpSparseIntArray(ipw, mStartedProfileGroupIds, "started user / profile group",
                     "u", "pg");
 
-            ipw.print("Supports background users on secondary displays: ");
-            ipw.println(mUsersOnSecondaryDisplaysEnabled);
+            ipw.print("Supports visible background users on displays: ");
+            ipw.println(mVisibleBackgroundUsersEnabled);
 
-            if (mUsersOnSecondaryDisplays != null) {
-                dumpSparseIntArray(ipw, mUsersOnSecondaryDisplays,
-                        "background user / secondary display", "u", "d");
+            if (mUsersOnDisplaysMap != null) {
+                dumpSparseIntArray(ipw, mUsersOnDisplaysMap, "user / display", "u", "d");
             }
             int numberListeners = mListeners.size();
             ipw.print("Number of listeners: ");
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 046db92..e19ebce 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -56,6 +56,7 @@
 import com.android.server.pm.PackageManagerServiceCompilerMapping;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
@@ -487,14 +488,14 @@
     /**
      * Compile layout resources in a given package.
      */
-    public boolean compileLayouts(AndroidPackage pkg) {
+    public boolean compileLayouts(@NonNull PackageState packageState, @NonNull AndroidPackage pkg) {
         try {
             final String packageName = pkg.getPackageName();
-            final String apkPath = pkg.getBaseApkPath();
+            final String apkPath = pkg.getSplits().get(0).getPath();
             // TODO(b/143971007): Use a cross-user directory
             File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId());
             final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
-            if (pkg.isPrivileged() || pkg.isUseEmbeddedDex()
+            if (packageState.isPrivileged() || pkg.isUseEmbeddedDex()
                     || pkg.isDefaultToDeviceProtectedStorage()) {
                 // Privileged apps prefer to load trusted code so they don't use compiled views.
                 // If the app is not privileged but prefers code integrity, also avoid compiling
diff --git a/services/core/java/com/android/server/pm/dex/ArtUtils.java b/services/core/java/com/android/server/pm/dex/ArtUtils.java
index 160add6..223469a 100644
--- a/services/core/java/com/android/server/pm/dex/ArtUtils.java
+++ b/services/core/java/com/android/server/pm/dex/ArtUtils.java
@@ -23,7 +23,7 @@
 import com.android.server.pm.PackageDexOptimizer;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.File;
 import java.util.Arrays;
@@ -39,18 +39,17 @@
      * Create the ART-representation of package info.
      */
     public static ArtPackageInfo createArtPackageInfo(
-            AndroidPackage pkg, PackageStateInternal pkgSetting) {
+            AndroidPackage pkg, PackageState packageState) {
         return new ArtPackageInfo(
                 pkg.getPackageName(),
-                Arrays.asList(getAppDexInstructionSets(pkgSetting.getPrimaryCpuAbi(),
-                        pkgSetting.getSecondaryCpuAbi())),
+                Arrays.asList(getAppDexInstructionSets(packageState.getPrimaryCpuAbi(),
+                        packageState.getSecondaryCpuAbi())),
                 AndroidPackageUtils.getAllCodePaths(pkg),
-                getOatDir(pkg, pkgSetting));
+                getOatDir(pkg, packageState));
     }
 
-    private static String getOatDir(AndroidPackage pkg, @NonNull PackageStateInternal pkgSetting) {
-        if (!AndroidPackageUtils.canHaveOatDir(pkg,
-                pkgSetting.getTransientState().isUpdatedSystemApp())) {
+    private static String getOatDir(AndroidPackage pkg, @NonNull PackageState packageState) {
+        if (!AndroidPackageUtils.canHaveOatDir(packageState, pkg)) {
             return null;
         }
         File codePath = new File(pkg.getPath());
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index a3fa25d..ff993ea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -131,7 +131,7 @@
         info.splitRevisionCodes = pkg.getSplitRevisionCodes();
         info.versionName = pkg.getVersionName();
         info.sharedUserId = pkg.getSharedUserId();
-        info.sharedUserLabel = pkg.getSharedUserLabel();
+        info.sharedUserLabel = pkg.getSharedUserLabelRes();
         info.applicationInfo = applicationInfo;
         info.installLocation = pkg.getInstallLocation();
         if ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
@@ -218,7 +218,7 @@
                     }
                 }
             }
-            if (pkg.areAttributionsUserVisible()) {
+            if (pkg.isAttributionsUserVisible()) {
                 info.applicationInfo.privateFlagsExt
                         |= ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
             } else {
@@ -274,7 +274,7 @@
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final ParsedActivity a = pkg.getActivities().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkg.isSystem(), pkg.isEnabled(), a,
+                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
                             flags)) {
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
                                 a.getName())) {
@@ -294,7 +294,7 @@
                 final ActivityInfo[] res = new ActivityInfo[size];
                 for (int i = 0; i < size; i++) {
                     final ParsedActivity a = pkg.getReceivers().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkg.isSystem(), pkg.isEnabled(), a,
+                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
                             flags)) {
                         res[num++] = generateActivityInfo(pkg, a, flags, state, applicationInfo,
                                 userId, pkgSetting);
@@ -310,7 +310,7 @@
                 final ServiceInfo[] res = new ServiceInfo[size];
                 for (int i = 0; i < size; i++) {
                     final ParsedService s = pkg.getServices().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkg.isSystem(), pkg.isEnabled(), s,
+                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), s,
                             flags)) {
                         res[num++] = generateServiceInfo(pkg, s, flags, state, applicationInfo,
                                 userId, pkgSetting);
@@ -327,7 +327,7 @@
                 for (int i = 0; i < size; i++) {
                     final ParsedProvider pr = pkg.getProviders()
                             .get(i);
-                    if (ComponentParseUtils.isMatch(state, pkg.isSystem(), pkg.isEnabled(), pr,
+                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), pr,
                             flags)) {
                         res[num++] = generateProviderInfo(pkg, pr, flags, state, applicationInfo,
                                 userId, pkgSetting);
@@ -425,7 +425,7 @@
         }
 
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)
-                || !AndroidPackageUtils.isMatchForSystemOnly(pkg, flags)) {
+                || !AndroidPackageUtils.isMatchForSystemOnly(pkgSetting, flags)) {
             return null;
         }
 
@@ -793,7 +793,7 @@
         // If available for the target user, or trying to match uninstalled packages and it's
         // a system app.
         return PackageUserStateUtils.isAvailable(state, flags)
-                || (pkg.isSystem()
+                || (pkgSetting.isSystem()
                 && ((flags & PackageManager.MATCH_KNOWN_PACKAGES) != 0
                 || (flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) != 0));
     }
@@ -869,7 +869,7 @@
     public static int appInfoFlags(AndroidPackage pkg, @Nullable PackageStateInternal pkgSetting) {
         // @formatter:off
         int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
-                | flag(pkg.isBaseHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
+                | flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
                 | flag(pkg.isAllowBackup(), ApplicationInfo.FLAG_ALLOW_BACKUP)
                 | flag(pkg.isKillAfterRestore(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
                 | flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
@@ -893,7 +893,7 @@
                 | flag(pkg.isSupportsExtraLargeScreens(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
                 | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS)
                 | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
-                | flag(pkg.isSystem(), ApplicationInfo.FLAG_SYSTEM)
+                | flag(AndroidPackageUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM)
                 | flag(pkg.isFactoryTest(), ApplicationInfo.FLAG_FACTORY_TEST);
 
         return appInfoFlags(pkgWithoutStateFlags, pkgSetting);
@@ -906,7 +906,7 @@
         // @formatter:off
         int flags = pkgWithoutStateFlags;
         if (pkgSetting != null) {
-            flags |= flag(pkgSetting.getTransientState().isUpdatedSystemApp(), ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
+            flags |= flag(pkgSetting.isUpdatedSystemApp(), ApplicationInfo.FLAG_UPDATED_SYSTEM_APP);
         }
         return flags;
         // @formatter:on
@@ -934,12 +934,12 @@
                 | flag(pkg.isCantSaveState(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
                 | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION)
                 | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING)
-                | flag(pkg.isSystemExt(), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
-                | flag(pkg.isPrivileged(), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                | flag(pkg.isOem(), ApplicationInfo.PRIVATE_FLAG_OEM)
-                | flag(pkg.isVendor(), ApplicationInfo.PRIVATE_FLAG_VENDOR)
-                | flag(pkg.isProduct(), ApplicationInfo.PRIVATE_FLAG_PRODUCT)
-                | flag(pkg.isOdm(), ApplicationInfo.PRIVATE_FLAG_ODM)
+                | flag(AndroidPackageUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
+                | flag(AndroidPackageUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+                | flag(AndroidPackageUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM)
+                | flag(AndroidPackageUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR)
+                | flag(AndroidPackageUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT)
+                | flag(AndroidPackageUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM)
                 | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY);
 
         Boolean resizeableActivity = pkg.getResizeableActivity();
@@ -972,7 +972,7 @@
         // @formatter:off
         int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
                 | flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
-                | flag(pkg.areAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
+                | flag(pkg.isAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
                 | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
                 | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
         return appInfoPrivateFlagsExt(pkgWithoutStateFlags, pkgSetting);
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
index ebb96bb..8b5719a 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidHidlUpdater.java
@@ -35,13 +35,13 @@
 public class AndroidHidlUpdater extends PackageSharedLibraryUpdater {
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         // This was the default <= P and is maintained for backwards compatibility.
         boolean isLegacy = parsedPackage.getTargetSdkVersion() <= Build.VERSION_CODES.P;
         // Only system apps use these libraries
-        boolean isSystem = parsedPackage.isSystem() || isUpdatedSystemApp;
 
-        if (isLegacy && isSystem) {
+        if (isLegacy && (isSystemApp || isUpdatedSystemApp)) {
             prefixRequiredLibrary(parsedPackage, ANDROID_HIDL_BASE);
             prefixRequiredLibrary(parsedPackage, ANDROID_HIDL_MANAGER);
         } else {
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
index 6cdd4df..adaa04c 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdater.java
@@ -29,7 +29,8 @@
     private static final String LIBRARY_NAME = "android.net.ipsec.ike";
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         removeLibrary(parsedPackage, LIBRARY_NAME);
     }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
index 7031dc3..3b29d1f 100644
--- a/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/AndroidTestBaseUpdater.java
@@ -18,6 +18,7 @@
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_BASE;
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER;
 
+import android.annotation.NonNull;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledAfter;
 import android.content.Context;
@@ -56,10 +57,10 @@
     @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
     private static final long REMOVE_ANDROID_TEST_BASE = 133396946L;
 
-    private static boolean isChangeEnabled(AndroidPackage pkg) {
+    private static boolean isChangeEnabled(@NonNull AndroidPackage pkg, boolean isSystemApp) {
         // Do not ask platform compat for system apps to prevent a boot time regression in tests.
         // b/142558883.
-        if (!pkg.isSystem()) {
+        if (!isSystemApp) {
             IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
                     ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
             try {
@@ -74,11 +75,12 @@
     }
 
     @Override
-    public void updatePackage(ParsedPackage pkg, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage pkg, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         // Packages targeted at <= Q expect the classes in the android.test.base library
         // to be accessible so this maintains backward compatibility by adding the
         // android.test.base library to those packages.
-        if (!isChangeEnabled(pkg)) {
+        if (!isChangeEnabled(pkg, isSystemApp)) {
             prefixRequiredLibrary(pkg, ANDROID_TEST_BASE);
         } else {
             // If a package already depends on android.test.runner then add a dependency on
diff --git a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
index 1a2ff26..041b77b 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ApexSharedLibraryUpdater.java
@@ -43,7 +43,8 @@
     }
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         final int builtInLibCount = mSharedLibraries.size();
         for (int i = 0; i < builtInLibCount; i++) {
             updateSharedLibraryForPackage(mSharedLibraries.valueAt(i), parsedPackage);
diff --git a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
index ee3c406..b47a768 100644
--- a/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdater.java
@@ -29,7 +29,8 @@
     private static final String LIBRARY_NAME = "com.google.android.maps";
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         parsedPackage.removeUsesLibrary(LIBRARY_NAME);
         parsedPackage.removeUsesOptionalLibrary(LIBRARY_NAME);
     }
diff --git a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
index 96fead2..ac65c8c 100644
--- a/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdater.java
@@ -37,7 +37,8 @@
     }
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         // Packages targeted at <= O_MR1 expect the classes in the org.apache.http.legacy library
         // to be accessible so this maintains backward compatibility by adding the
         // org.apache.http.legacy library to those packages.
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
index dc3bf78..3da7141 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageBackwardCompatibility.java
@@ -21,12 +21,12 @@
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ANDROID_TEST_RUNNER;
 import static com.android.server.pm.parsing.library.SharedLibraryNames.ORG_APACHE_HTTP_LEGACY;
 
-import com.android.server.pm.pkg.parsing.ParsingPackage;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackage;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -130,15 +130,16 @@
      * @param parsedPackage the {@link ParsedPackage} to modify.
      */
     @VisibleForTesting
-    public static void modifySharedLibraries(ParsedPackage parsedPackage,
+    public static void modifySharedLibraries(ParsedPackage parsedPackage, boolean isSystemApp,
             boolean isUpdatedSystemApp) {
-        INSTANCE.updatePackage(parsedPackage, isUpdatedSystemApp);
+        INSTANCE.updatePackage(parsedPackage, isSystemApp, isUpdatedSystemApp);
     }
 
     @Override
-    public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+    public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp) {
         for (PackageSharedLibraryUpdater packageUpdater : mPackageUpdaters) {
-            packageUpdater.updatePackage(parsedPackage, isUpdatedSystemApp);
+            packageUpdater.updatePackage(parsedPackage, isSystemApp, isUpdatedSystemApp);
         }
     }
 
@@ -163,7 +164,8 @@
     public static class AndroidTestRunnerSplitUpdater extends PackageSharedLibraryUpdater {
 
         @Override
-        public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+        public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+                boolean isUpdatedSystemApp) {
             // android.test.runner has a dependency on android.test.mock so if android.test.runner
             // is present but android.test.mock is not then add android.test.mock.
             prefixImplicitDependency(parsedPackage, ANDROID_TEST_RUNNER, ANDROID_TEST_MOCK);
@@ -179,7 +181,8 @@
             extends PackageSharedLibraryUpdater {
 
         @Override
-        public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+        public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+                boolean isUpdatedSystemApp) {
             removeLibrary(parsedPackage, ORG_APACHE_HTTP_LEGACY);
         }
 
@@ -194,7 +197,8 @@
             extends PackageSharedLibraryUpdater {
 
         @Override
-        public void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp) {
+        public void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+                boolean isUpdatedSystemApp) {
             removeLibrary(parsedPackage, ANDROID_TEST_BASE);
         }
     }
diff --git a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
index 123b808..a9c22d9 100644
--- a/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
+++ b/services/core/java/com/android/server/pm/parsing/library/PackageSharedLibraryUpdater.java
@@ -38,7 +38,8 @@
      *
      * @param parsedPackage the package to update.
      */
-    public abstract void updatePackage(ParsedPackage parsedPackage, boolean isUpdatedSystemApp);
+    public abstract void updatePackage(ParsedPackage parsedPackage, boolean isSystemApp,
+            boolean isUpdatedSystemApp);
 
     static void removeLibrary(ParsedPackage parsedPackage, String libraryName) {
         parsedPackage.removeUsesLibrary(libraryName)
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
index 876bf17..1fafdf9 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageHidden.java
@@ -59,4 +59,19 @@
 
     // TODO(b/135203078): Hide and enforce going through PackageInfoUtils
     ApplicationInfo toAppInfoWithoutState();
+
+    // TODO: Remove these booleans and store the value directly inside PackageState
+    boolean isSystem();
+
+    boolean isSystemExt();
+
+    boolean isPrivileged();
+
+    boolean isOem();
+
+    boolean isVendor();
+
+    boolean isProduct();
+
+    boolean isOdm();
 }
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 82b5fa2..f3ee531 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
@@ -103,7 +103,7 @@
         return new SharedLibraryInfo(null, pkg.getPackageName(),
                 AndroidPackageUtils.getAllCodePaths(pkg),
                 pkg.getStaticSharedLibraryName(),
-                pkg.getStaticSharedLibVersion(),
+                pkg.getStaticSharedLibraryVersion(),
                 SharedLibraryInfo.TYPE_STATIC,
                 new VersionedPackage(pkg.getManifestPackageName(),
                         pkg.getLongVersionCode()),
@@ -161,11 +161,12 @@
         );
     }
 
-    public static boolean canHaveOatDir(AndroidPackage pkg, boolean isUpdatedSystemApp) {
+    public static boolean canHaveOatDir(@NonNull PackageState packageState,
+            @NonNull AndroidPackage pkg) {
         // The following app types CANNOT have oat directory
         // - non-updated system apps,
         // - incrementally installed apps.
-        if (pkg.isSystem() && !isUpdatedSystemApp) {
+        if (packageState.isSystem() && !packageState.isUpdatedSystemApp()) {
             return false;
         }
         if (IncrementalManager.isIncrementalPath(pkg.getPath())) {
@@ -234,14 +235,14 @@
                 || !pkg.getLibraryNames().isEmpty();
     }
 
-    public static int getHiddenApiEnforcementPolicy(@Nullable AndroidPackage pkg,
-            @NonNull PackageStateInternal pkgSetting) {
+    public static int getHiddenApiEnforcementPolicy(@NonNull AndroidPackage pkg,
+            @NonNull PackageStateInternal packageState) {
         boolean isAllowedToUseHiddenApis;
         if (pkg == null) {
             isAllowedToUseHiddenApis = false;
         } else if (pkg.isSignedWithPlatformKey()) {
             isAllowedToUseHiddenApis = true;
-        } else if (pkg.isSystem() || pkgSetting.getTransientState().isUpdatedSystemApp()) {
+        } else if (packageState.isSystem()) {
             isAllowedToUseHiddenApis = pkg.isUsesNonSdkApi()
                     || SystemConfig.getInstance().getHiddenApiWhitelistedApps().contains(
                     pkg.getPackageName());
@@ -266,9 +267,9 @@
      * Returns false iff the provided flags include the {@link PackageManager#MATCH_SYSTEM_ONLY}
      * flag and the provided package is not a system package. Otherwise returns {@code true}.
      */
-    public static boolean isMatchForSystemOnly(AndroidPackage pkg, long flags) {
+    public static boolean isMatchForSystemOnly(@NonNull PackageState packageState, long flags) {
         if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-            return pkg.isSystem();
+            return packageState.isSystem();
         }
         return true;
     }
@@ -300,8 +301,8 @@
      * actually renamed.
      */
     @Nullable
-    public static String getRealPackageOrNull(AndroidPackage pkg) {
-        if (pkg.getOriginalPackages().isEmpty() || !pkg.isSystem()) {
+    public static String getRealPackageOrNull(@NonNull AndroidPackage pkg, boolean isSystem) {
+        if (pkg.getOriginalPackages().isEmpty() || !isSystem) {
             return null;
         }
 
@@ -312,4 +313,60 @@
         info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
         info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
     }
+
+    /**
+     * @deprecated Use {@link PackageState#isSystem}
+     */
+    @Deprecated
+    public static boolean isSystem(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isSystem();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isSystemExt}
+     */
+    @Deprecated
+    public static boolean isSystemExt(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isSystemExt();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isPrivileged}
+     */
+    @Deprecated
+    public static boolean isPrivileged(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isPrivileged();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isOem}
+     */
+    @Deprecated
+    public static boolean isOem(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isOem();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isVendor}
+     */
+    @Deprecated
+    public static boolean isVendor(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isVendor();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isProduct}
+     */
+    @Deprecated
+    public static boolean isProduct(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isProduct();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isOdm}
+     */
+    @Deprecated
+    public static boolean isOdm(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isOdm();
+    }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
index ba36ab7..e361c93 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
@@ -717,7 +717,7 @@
     }
 
     @Override
-    public boolean areAttributionsUserVisible() {
+    public boolean isAttributionsUserVisible() {
         return getBoolean(Booleans.ATTRIBUTIONS_ARE_USER_VISIBLE);
     }
 
@@ -869,7 +869,7 @@
     }
 
     @Override
-    public int getBanner() {
+    public int getBannerRes() {
         return banner;
     }
 
@@ -897,7 +897,7 @@
 
     @Nullable
     @Override
-    public String getClassName() {
+    public String getApplicationClassName() {
         return className;
     }
 
@@ -924,7 +924,7 @@
     }
 
     @Override
-    public int getDataExtractionRules() {
+    public int getDataExtractionRulesRes() {
         return dataExtractionRules;
     }
 
@@ -940,7 +940,7 @@
     }
 
     @Override
-    public int getFullBackupContent() {
+    public int getFullBackupContentRes() {
         return fullBackupContent;
     }
 
@@ -1006,7 +1006,7 @@
     }
 
     @Override
-    public int getLogo() {
+    public int getLogoRes() {
         return logo;
     }
 
@@ -1277,7 +1277,7 @@
     }
 
     @Override
-    public int getSharedUserLabel() {
+    public int getSharedUserLabelRes() {
         return sharedUserLabel;
     }
 
@@ -1330,7 +1330,7 @@
     }
 
     @Override
-    public long getStaticSharedLibVersion() {
+    public long getStaticSharedLibraryVersion() {
         return staticSharedLibVersion;
     }
 
@@ -1356,7 +1356,7 @@
     }
 
     @Override
-    public int getTheme() {
+    public int getThemeRes() {
         return theme;
     }
 
@@ -1519,8 +1519,8 @@
     }
 
     @Override
-    public boolean isBaseHardwareAccelerated() {
-        return getBoolean(Booleans.BASE_HARDWARE_ACCELERATED);
+    public boolean isHardwareAccelerated() {
+        return getBoolean(Booleans.HARDWARE_ACCELERATED);
     }
 
     @Override
@@ -1609,7 +1609,7 @@
     }
 
     @Override
-    public boolean isLeavingSharedUid() {
+    public boolean isLeavingSharedUser() {
         return getBoolean(Booleans.LEAVING_SHARED_UID);
     }
 
@@ -1840,14 +1840,14 @@
     }
 
     @Override
-    public PackageImpl setBanner(int value) {
+    public PackageImpl setBannerRes(int value) {
         banner = value;
         return this;
     }
 
     @Override
-    public PackageImpl setBaseHardwareAccelerated(boolean value) {
-        return setBoolean(Booleans.BASE_HARDWARE_ACCELERATED, value);
+    public PackageImpl setHardwareAccelerated(boolean value) {
+        return setBoolean(Booleans.HARDWARE_ACCELERATED, value);
     }
 
     @Override
@@ -1874,7 +1874,7 @@
     }
 
     @Override
-    public PackageImpl setClassName(@Nullable String className) {
+    public PackageImpl setApplicationClassName(@Nullable String className) {
         this.className = className == null ? null : className.trim();
         return this;
     }
@@ -1903,7 +1903,7 @@
     }
 
     @Override
-    public PackageImpl setDataExtractionRules(int value) {
+    public PackageImpl setDataExtractionRulesRes(int value) {
         dataExtractionRules = value;
         return this;
     }
@@ -1940,7 +1940,7 @@
     }
 
     @Override
-    public PackageImpl setFullBackupContent(int value) {
+    public PackageImpl setFullBackupContentRes(int value) {
         fullBackupContent = value;
         return this;
     }
@@ -2022,7 +2022,7 @@
     }
 
     @Override
-    public PackageImpl setLeavingSharedUid(boolean value) {
+    public PackageImpl setLeavingSharedUser(boolean value) {
         return setBoolean(Booleans.LEAVING_SHARED_UID, value);
     }
 
@@ -2033,7 +2033,7 @@
     }
 
     @Override
-    public PackageImpl setLogo(int value) {
+    public PackageImpl setLogoRes(int value) {
         logo = value;
         return this;
     }
@@ -2292,7 +2292,7 @@
     }
 
     @Override
-    public PackageImpl setSharedUserLabel(int value) {
+    public PackageImpl setSharedUserLabelRes(int value) {
         sharedUserLabel = value;
         return this;
     }
@@ -2318,7 +2318,7 @@
     }
 
     @Override
-    public PackageImpl setStaticSharedLibVersion(long value) {
+    public PackageImpl setStaticSharedLibraryVersion(long value) {
         staticSharedLibVersion = value;
         return this;
     }
@@ -2397,7 +2397,7 @@
     }
 
     @Override
-    public PackageImpl setTheme(int value) {
+    public PackageImpl setThemeRes(int value) {
         theme = value;
         return this;
     }
@@ -3529,7 +3529,7 @@
     private static class Booleans {
         @LongDef({
                 EXTERNAL_STORAGE,
-                BASE_HARDWARE_ACCELERATED,
+                HARDWARE_ACCELERATED,
                 ALLOW_BACKUP,
                 KILL_AFTER_RESTORE,
                 RESTORE_ANY_VERSION,
@@ -3593,7 +3593,7 @@
         public @interface Flags {}
 
         private static final long EXTERNAL_STORAGE = 1L;
-        private static final long BASE_HARDWARE_ACCELERATED = 1L << 1;
+        private static final long HARDWARE_ACCELERATED = 1L << 1;
         private static final long ALLOW_BACKUP = 1L << 2;
         private static final long KILL_AFTER_RESTORE = 1L << 3;
         private static final long RESTORE_ANY_VERSION = 1L << 4;
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 165c52d..74b178e 100644
--- a/services/core/java/com/android/server/pm/permission/Permission.java
+++ b/services/core/java/com/android/server/pm/permission/Permission.java
@@ -30,6 +30,7 @@
 
 import com.android.server.pm.PackageManagerService;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.component.ParsedPermission;
 
 import libcore.util.EmptyArray;
@@ -403,31 +404,31 @@
         if (!permission.mReconciled) {
             return false;
         }
-        final AndroidPackage currentPackage = packageManagerInternal.getPackage(
+        var currentPackageState = packageManagerInternal.getPackageStateInternal(
                 permission.mPermissionInfo.packageName);
-        if (currentPackage == null) {
+        if (currentPackageState == null) {
             return false;
         }
-        return currentPackage.isSystem();
+        return currentPackageState.isSystem();
     }
 
     @NonNull
     public static Permission createOrUpdate(@Nullable Permission permission,
-            @NonNull PermissionInfo permissionInfo, @NonNull AndroidPackage pkg,
+            @NonNull PermissionInfo permissionInfo, @NonNull PackageState packageState,
             @NonNull Collection<Permission> permissionTrees, boolean isOverridingSystemPermission) {
         // Allow system apps to redefine non-system permissions
         boolean ownerChanged = false;
         if (permission != null && !Objects.equals(permission.mPermissionInfo.packageName,
                 permissionInfo.packageName)) {
-            if (pkg.isSystem()) {
+            if (packageState.isSystem()) {
                 if (permission.mType == Permission.TYPE_CONFIG && !permission.mReconciled) {
                     // It's a built-in permission and no owner, take ownership now
                     permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED;
                     permission.mPermissionInfo = permissionInfo;
                     permission.mReconciled = true;
-                    permission.mUid = pkg.getUid();
+                    permission.mUid = packageState.getAppId();
                 } else if (!isOverridingSystemPermission) {
-                    Slog.w(TAG, "New decl " + pkg + " of permission  "
+                    Slog.w(TAG, "New decl " + packageState + " of permission  "
                             + permissionInfo.name + " is system; overriding "
                             + permission.mPermissionInfo.packageName);
                     ownerChanged = true;
@@ -453,7 +454,7 @@
                     permissionInfo.flags |= PermissionInfo.FLAG_INSTALLED;
                     permission.mPermissionInfo = permissionInfo;
                     permission.mReconciled = true;
-                    permission.mUid = pkg.getUid();
+                    permission.mUid = packageState.getAppId();
                     if (PackageManagerService.DEBUG_PACKAGE_SCANNING) {
                         if (r == null) {
                             r = new StringBuilder(256);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index cefe9cd..a949f75 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -75,6 +75,7 @@
 import com.android.server.pm.UserManagerService;
 import com.android.server.pm.permission.PermissionManagerServiceInternal.HotwordDetectionServiceProvider;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -681,9 +682,9 @@
         }
 
         @Override
-        public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+        public void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
                 @Nullable AndroidPackage oldPkg) {
-            mPermissionManagerServiceImpl.onPackageAdded(pkg, isInstantApp, oldPkg);
+            mPermissionManagerServiceImpl.onPackageAdded(packageState, isInstantApp, oldPkg);
         }
 
         @Override
@@ -714,9 +715,9 @@
 
         @Override
         public void onPackageUninstalled(@NonNull String packageName, int appId,
-                @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-                @UserIdInt int userId) {
-            mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId,
+                @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
+                @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) {
+            mPermissionManagerServiceImpl.onPackageUninstalled(packageName, appId, packageState,
                     pkg, sharedUserPkgs, userId);
         }
 
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 d2d3c3c..f6362f2 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -133,6 +133,7 @@
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
 import com.android.server.pm.pkg.component.ParsedPermission;
@@ -2337,7 +2338,8 @@
         }
     }
 
-    private List<String> addAllPermissionsInternal(@NonNull AndroidPackage pkg) {
+    private List<String> addAllPermissionsInternal(@NonNull PackageState packageState,
+                    @NonNull AndroidPackage pkg) {
         final int N = ArrayUtils.size(pkg.getPermissions());
         ArrayList<String> definitionChangedPermissions = new ArrayList<>();
         for (int i=0; i<N; i++) {
@@ -2375,7 +2377,7 @@
                     oldPermission, permissionInfo, mPackageManagerInt);
             synchronized (mLock) {
                 final Permission permission = Permission.createOrUpdate(oldPermission,
-                        permissionInfo, pkg, mRegistry.getPermissionTrees(),
+                        permissionInfo, packageState, mRegistry.getPermissionTrees(),
                         isOverridingSystemPermission);
                 if (p.isTree()) {
                     mRegistry.addPermissionTree(permission);
@@ -2976,7 +2978,7 @@
 
                 if ((installPermissionsChangedForUser || replace)
                         && !userState.areInstallPermissionsFixed(ps.getPackageName())
-                        && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) {
+                        && !ps.isSystem() || ps.isUpdatedSystemApp()) {
                     // This is the first that we have heard about this package, so the
                     // permissions we have now selected are fixed until explicitly
                     // changed.
@@ -3304,7 +3306,7 @@
         if (Objects.equals(packageName, PLATFORM_PACKAGE_NAME)) {
             return true;
         }
-        if (!pkg.isPrivileged()) {
+        if (!packageSetting.isPrivileged()) {
             return true;
         }
         if (!mPrivilegedPermissionAllowlistSourcePackageNames
@@ -3314,13 +3316,13 @@
         final String permissionName = permission.getName();
         final String containingApexPackageName =
                 mApexManager.getActiveApexPackageNameContainingPackage(packageName);
-        final Boolean allowlistState = getPrivilegedPermissionAllowlistState(pkg, permissionName,
-                containingApexPackageName);
+        final Boolean allowlistState = getPrivilegedPermissionAllowlistState(packageSetting,
+                permissionName, containingApexPackageName);
         if (allowlistState != null) {
             return allowlistState;
         }
         // Updated system apps do not need to be allowlisted
-        if (packageSetting.getTransientState().isUpdatedSystemApp()) {
+        if (packageSetting.isUpdatedSystemApp()) {
             // Let shouldGrantPermissionByProtectionFlags() decide whether the privileged permission
             // can be granted, because an updated system app may be in a shared UID, and in case a
             // new privileged permission is requested by the updated system app but not the factory
@@ -3354,18 +3356,18 @@
     }
 
     @Nullable
-    private Boolean getPrivilegedPermissionAllowlistState(@NonNull AndroidPackage pkg,
+    private Boolean getPrivilegedPermissionAllowlistState(@NonNull PackageState packageState,
             @NonNull String permissionName, String containingApexPackageName) {
         final PermissionAllowlist permissionAllowlist =
                 SystemConfig.getInstance().getPermissionAllowlist();
-        final String packageName = pkg.getPackageName();
-        if (pkg.isVendor()) {
+        final String packageName = packageState.getPackageName();
+        if (packageState.isVendor()) {
             return permissionAllowlist.getVendorPrivilegedAppAllowlistState(packageName,
                     permissionName);
-        } else if (pkg.isProduct()) {
+        } else if (packageState.isProduct()) {
             return permissionAllowlist.getProductPrivilegedAppAllowlistState(packageName,
                     permissionName);
-        } else if (pkg.isSystemExt()) {
+        } else if (packageState.isSystemExt()) {
             return permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(packageName,
                     permissionName);
         } else if (containingApexPackageName != null) {
@@ -3374,7 +3376,7 @@
             if (nonApexAllowlistState != null) {
                 // TODO(andreionea): Remove check as soon as all apk-in-apex
                 // permission allowlists are migrated.
-                Slog.w(TAG, "Package " + pkg.getPackageName() + " is an APK in APEX,"
+                Slog.w(TAG, "Package " + packageName + " is an APK in APEX,"
                         + " but has permission allowlist on the system image. Please bundle the"
                         + " allowlist in the " + containingApexPackageName + " APEX instead.");
             }
@@ -3425,17 +3427,17 @@
         boolean allowed = false;
         final boolean isPrivilegedPermission = bp.isPrivileged();
         final boolean isOemPermission = bp.isOem();
-        if (!allowed && (isPrivilegedPermission || isOemPermission) && pkg.isSystem()) {
+        if (!allowed && (isPrivilegedPermission || isOemPermission) && pkgSetting.isSystem()) {
             final String permissionName = bp.getName();
             // For updated system applications, a privileged/oem permission
             // is granted only if it had been defined by the original application.
-            if (pkgSetting.getTransientState().isUpdatedSystemApp()) {
+            if (pkgSetting.isUpdatedSystemApp()) {
                 final PackageStateInternal disabledPs = mPackageManagerInt
                         .getDisabledSystemPackage(pkg.getPackageName());
                 final AndroidPackage disabledPkg = disabledPs == null ? null : disabledPs.getPkg();
                 if (disabledPkg != null
-                        && ((isPrivilegedPermission && disabledPkg.isPrivileged())
-                        || (isOemPermission && canGrantOemPermission(disabledPkg,
+                        && ((isPrivilegedPermission && disabledPs.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(disabledPs,
                                 permissionName)))) {
                     if (disabledPkg.getRequestedPermissions().contains(permissionName)) {
                         allowed = true;
@@ -3447,13 +3449,14 @@
                     }
                 }
             } else {
-                allowed = (isPrivilegedPermission && pkg.isPrivileged())
-                        || (isOemPermission && canGrantOemPermission(pkg, permissionName));
+                allowed = (isPrivilegedPermission && pkgSetting.isPrivileged())
+                        || (isOemPermission && canGrantOemPermission(pkgSetting, permissionName));
             }
             // In any case, don't grant a privileged permission to privileged vendor apps, if
             // the permission's protectionLevel does not have the extra 'vendorPrivileged'
             // flag.
-            if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged() && pkg.isVendor()) {
+            if (allowed && isPrivilegedPermission && !bp.isVendorPrivileged()
+                    && pkgSetting.isVendor()) {
                 Slog.w(TAG, "Permission " + permissionName
                         + " cannot be granted to privileged vendor apk " + pkg.getPackageName()
                         + " because it isn't a 'vendorPrivileged' permission.");
@@ -3488,8 +3491,7 @@
             // this app is a verifier, then it gets the permission.
             allowed = true;
         }
-        if (!allowed && bp.isPreInstalled()
-                && pkg.isSystem()) {
+        if (!allowed && bp.isPreInstalled() && pkgSetting.isSystem()) {
             // Any pre-installed system app is allowed to get this permission.
             allowed = true;
         }
@@ -3586,16 +3588,18 @@
         return mPackageManagerInt.getPackageStateInternal(sourcePackageName);
     }
 
-    private static boolean canGrantOemPermission(AndroidPackage pkg, String permission) {
-        if (!pkg.isOem()) {
+    private static boolean canGrantOemPermission(@NonNull PackageState packageState,
+            String permission) {
+        if (!packageState.isOem()) {
             return false;
         }
+        var packageName = packageState.getPackageName();
         // all oem permissions must explicitly be granted or denied
         final Boolean granted = SystemConfig.getInstance().getPermissionAllowlist()
-                .getOemAppAllowlistState(pkg.getPackageName(), permission);
+                .getOemAppAllowlistState(packageState.getPackageName(), permission);
         if (granted == null) {
             throw new IllegalStateException("OEM permission " + permission
-                    + " requested by package " + pkg.getPackageName()
+                    + " requested by package " + packageName
                     + " must be explicitly declared granted or not");
         }
         return Boolean.TRUE == granted;
@@ -4648,8 +4652,8 @@
         }
     }
 
-    private void onPackageAddedInternal(@NonNull AndroidPackage pkg, boolean isInstantApp,
-            @Nullable AndroidPackage oldPkg) {
+    private void onPackageAddedInternal(@NonNull PackageState packageState,
+            @NonNull AndroidPackage pkg, boolean isInstantApp, @Nullable AndroidPackage oldPkg) {
         if (!pkg.getAdoptPermissions().isEmpty()) {
             // This package wants to adopt ownership of permissions from
             // another package.
@@ -4682,7 +4686,7 @@
             Slog.w(TAG, "Permissions from package " + pkg.getPackageName()
                     + " ignored: instant apps cannot define new permissions.");
         } else {
-            permissionsWithChangedDefinition = addAllPermissionsInternal(pkg);
+            permissionsWithChangedDefinition = addAllPermissionsInternal(packageState, pkg);
         }
 
         boolean hasOldPkg = oldPkg != null;
@@ -5016,14 +5020,13 @@
     }
 
     private void onPackageUninstalledInternal(@NonNull String packageName, int appId,
-            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-            @UserIdInt int[] userIds) {
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
+            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int[] userIds) {
         // TODO: Handle the case when a system app upgrade is uninstalled and need to rejoin
         //  a shared UID permission state.
 
-        // TODO: Move these checks to check PackageState to be more reliable.
         // System packages should always have an available APK.
-        if (pkg != null && pkg.isSystem()
+        if (packageState.isSystem() && pkg != null
                 // We may be fully removing invalid system packages during boot, and in that case we
                 // do want to remove their permission state. So make sure that the package is only
                 // being marked as uninstalled instead of fully removed.
@@ -5046,8 +5049,8 @@
                 // or packages running under the shared user of the removed
                 // package if revoking the permissions requested only by the removed
                 // package is successful and this causes a change in gids.
-                revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId,
-                        sharedUserPkgs, userId);
+                revokeSharedUserPermissionsForLeavingPackageInternal(pkg, appId, sharedUserPkgs,
+                        userId);
             }
         }
     }
@@ -5229,10 +5232,12 @@
     }
 
     @Override
-    public void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
-            @Nullable AndroidPackage oldPkg) {
+    public void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
+                    @Nullable AndroidPackage oldPkg) {
+        Objects.requireNonNull(packageState);
+        var pkg = packageState.getAndroidPackage();
         Objects.requireNonNull(pkg);
-        onPackageAddedInternal(pkg, isInstantApp, oldPkg);
+        onPackageAddedInternal(packageState, pkg, isInstantApp, oldPkg);
     }
 
     @Override
@@ -5256,15 +5261,17 @@
 
     @Override
     public void onPackageUninstalled(@NonNull String packageName, int appId,
-            @Nullable AndroidPackage pkg, @NonNull List<AndroidPackage> sharedUserPkgs,
-            @UserIdInt int userId) {
+            @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
+            @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId) {
+        Objects.requireNonNull(packageState, "packageState");
         Objects.requireNonNull(packageName, "packageName");
         Objects.requireNonNull(sharedUserPkgs, "sharedUserPkgs");
         Preconditions.checkArgument(userId >= UserHandle.USER_SYSTEM
                 || userId == UserHandle.USER_ALL, "userId");
         final int[] userIds = userId == UserHandle.USER_ALL ? getAllUserIds()
                 : new int[] { userId };
-        onPackageUninstalledInternal(packageName, appId, pkg, sharedUserPkgs, userIds);
+        onPackageUninstalledInternal(packageName, appId, packageState, pkg, sharedUserPkgs,
+                userIds);
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index d9caec7..08938a5 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -29,6 +29,7 @@
 import android.permission.PermissionManagerInternal;
 
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -567,11 +568,11 @@
     /**
      * Callback when a package has been added.
      *
-     * @param pkg the added package
+     * @param packageState the added package
      * @param isInstantApp whether the added package is an instant app
      * @param oldPkg the old package, or {@code null} if none
      */
-    void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
+    void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
             @Nullable AndroidPackage oldPkg);
 
     /**
@@ -598,16 +599,16 @@
      * Callback when a package has been uninstalled.
      * <p>
      * The package may have been fully removed from the system, or only marked as uninstalled for
-     * this user but still instlaled for other users.
-     *
-     * TODO: Pass PackageState instead.
+     * this user but still installed for other users.
      *
      * @param packageName the name of the uninstalled package
      * @param appId the app ID of the uninstalled package
-     * @param pkg the uninstalled package, or {@code null} if unavailable
+     * @param packageState the uninstalled package
+     * @param pkg the uninstalled package
      * @param sharedUserPkgs the packages that are in the same shared user
      * @param userId the user ID the package is uninstalled for
      */
-    void onPackageUninstalled(@NonNull String packageName, int appId, @Nullable AndroidPackage pkg,
+    void onPackageUninstalled(@NonNull String packageName, int appId,
+            @NonNull PackageState packageState, @NonNull AndroidPackage pkg,
             @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 97ac749..fdcf765 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -24,6 +24,7 @@
 import android.permission.PermissionManagerInternal;
 
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -249,13 +250,13 @@
     /**
      * Callback when a package has been added.
      *
-     * @param pkg the added package
+     * @param packageState the added package
      * @param isInstantApp whether the added package is an instant app
      * @param oldPkg the old package, or {@code null} if none
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    void onPackageAdded(@NonNull AndroidPackage pkg, boolean isInstantApp,
-            @Nullable AndroidPackage oldPkg);
+    void onPackageAdded(@NonNull PackageState packageState,
+            boolean isInstantApp, @Nullable AndroidPackage oldPkg);
 
     /**
      * Callback when a package has been installed for a user.
@@ -285,16 +286,15 @@
      * The package may have been fully removed from the system, or only marked as uninstalled for
      * this user but still instlaled for other users.
      *
-     * TODO: Pass PackageState instead.
-     *
      * @param packageName the name of the uninstalled package
      * @param appId the app ID of the uninstalled package
-     * @param pkg the uninstalled package, or {@code null} if unavailable
+     * @param packageState the uninstalled package, or {@code null} if unavailable
      * @param sharedUserPkgs the packages that are in the same shared user
      * @param userId the user ID the package is uninstalled for
      */
     //@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
-    void onPackageUninstalled(@NonNull String packageName, int appId, @Nullable AndroidPackage pkg,
+    void onPackageUninstalled(@NonNull String packageName, int appId,
+            @Nullable PackageState packageState, @Nullable AndroidPackage pkg,
             @NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
 
     /**
diff --git a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
index 84907a5..49f85e9 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -16,9 +16,14 @@
 
 package com.android.server.pm.pkg;
 
+import android.annotation.Dimension;
+import android.annotation.DrawableRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
 import android.annotation.SystemApi;
+import android.annotation.XmlRes;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -86,6 +91,109 @@
 public interface AndroidPackage {
 
     /**
+     * @see ApplicationInfo#className
+     * @see R.styleable#AndroidManifestApplication_name
+     */
+    @Nullable
+    String getApplicationClassName();
+
+    /**
+     * @see ApplicationInfo#appComponentFactory
+     * @see R.styleable#AndroidManifestApplication_appComponentFactory
+     */
+    @Nullable
+    String getAppComponentFactory();
+
+    /**
+     * @see ApplicationInfo#backupAgentName
+     * @see R.styleable#AndroidManifestApplication_backupAgent
+     */
+    @Nullable
+    String getBackupAgentName();
+
+    /**
+     * @see ApplicationInfo#banner
+     * @see R.styleable#AndroidManifestApplication_banner
+     */
+    @DrawableRes
+    int getBannerRes();
+
+    /**
+     * @see PackageInfo#baseRevisionCode
+     * @see R.styleable#AndroidManifest_revisionCode
+     */
+    int getBaseRevisionCode();
+
+    /**
+     * @see ApplicationInfo#category
+     * @see R.styleable#AndroidManifestApplication_appCategory
+     */
+    int getCategory();
+
+    /**
+     * @see ApplicationInfo#classLoaderName
+     * @see R.styleable#AndroidManifestApplication_classLoader
+     */
+    @Nullable
+    String getClassLoaderName();
+
+    /**
+     * @see ApplicationInfo#compatibleWidthLimitDp
+     * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getCompatibleWidthLimitDp();
+
+    /**
+     * @see ApplicationInfo#dataExtractionRulesRes
+     * @see R.styleable#AndroidManifestApplication_dataExtractionRules
+     */
+    @XmlRes
+    int getDataExtractionRulesRes();
+
+    /**
+     * @see ApplicationInfo#descriptionRes
+     * @see R.styleable#AndroidManifestApplication_description
+     */
+    @StringRes // This is actually format="reference"
+    int getDescriptionRes();
+
+    /**
+     * @see ApplicationInfo#fullBackupContent
+     * @see R.styleable#AndroidManifestApplication_fullBackupContent
+     */
+    @XmlRes
+    int getFullBackupContentRes();
+
+    /**
+     * @see ApplicationInfo#getGwpAsanMode()
+     * @see R.styleable#AndroidManifestApplication_gwpAsanMode
+     */
+    @ApplicationInfo.GwpAsanMode
+    int getGwpAsanMode();
+
+    /**
+     * @see ApplicationInfo#iconRes
+     * @see R.styleable#AndroidManifestApplication_icon
+     */
+    @DrawableRes
+    int getIconRes();
+
+    /**
+     * @see ApplicationInfo#labelRes
+     * @see R.styleable#AndroidManifestApplication_label
+     */
+    @StringRes
+    int getLabelRes();
+
+    /**
+     * @see ApplicationInfo#largestWidthLimitDp
+     * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getLargestWidthLimitDp();
+
+    /**
      * Library names this package is declared as, for use by other packages with "uses-library".
      *
      * @see R.styleable#AndroidManifestLibrary
@@ -94,12 +202,104 @@
     List<String> getLibraryNames();
 
     /**
+     * @see ApplicationInfo#logo
+     * @see R.styleable#AndroidManifestApplication_logo
+     */
+    @DrawableRes
+    int getLogoRes();
+
+    /**
+     * The resource ID used to provide the application's locales configuration.
+     *
+     * @see R.styleable#AndroidManifestApplication_localeConfig
+     */
+    @XmlRes
+    int getLocaleConfigRes();
+
+    /**
+     * @see PackageInfo#getLongVersionCode()
+     * @see R.styleable#AndroidManifest_versionCode
+     * @see R.styleable#AndroidManifest_versionCodeMajor
+     */
+    long getLongVersionCode();
+
+    /**
+     * @see ApplicationInfo#maxAspectRatio
+     * @see R.styleable#AndroidManifestApplication_maxAspectRatio
+     */
+    float getMaxAspectRatio();
+
+    /**
+     * @see ApplicationInfo#minAspectRatio
+     * @see R.styleable#AndroidManifestApplication_minAspectRatio
+     */
+    float getMinAspectRatio();
+
+    /**
+     * @see ApplicationInfo#getNativeHeapZeroInitialized()
+     * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
+     */
+    @ApplicationInfo.NativeHeapZeroInitialized
+    int getNativeHeapZeroInitialized();
+
+    /**
+     * @see ApplicationInfo#networkSecurityConfigRes
+     * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
+     */
+    @XmlRes
+    int getNetworkSecurityConfigRes();
+
+    /**
+     * @see PackageInfo#requiredAccountType
+     * @see R.styleable#AndroidManifestApplication_requiredAccountType
+     */
+    @Nullable
+    String getRequiredAccountType();
+
+    /**
+     * @see ApplicationInfo#requiresSmallestWidthDp
+     * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
+     */
+    @Dimension(unit = Dimension.DP)
+    int getRequiresSmallestWidthDp();
+
+    /**
+     * The restricted account authenticator type that is used by this application.
+     *
+     * @see PackageInfo#restrictedAccountType
+     * @see R.styleable#AndroidManifestApplication_restrictedAccountType
+     */
+    @Nullable
+    String getRestrictedAccountType();
+
+    /**
+     * @see ApplicationInfo#roundIconRes
+     * @see R.styleable#AndroidManifestApplication_roundIcon
+     */
+    @DrawableRes
+    int getRoundIconRes();
+
+    /**
      * @see R.styleable#AndroidManifestSdkLibrary_name
      */
     @Nullable
     String getSdkLibraryName();
 
     /**
+     * @see PackageInfo#sharedUserId
+     * @see R.styleable#AndroidManifest_sharedUserId
+     */
+    @Nullable
+    String getSharedUserId();
+
+    /**
+     * @see PackageInfo#sharedUserLabel
+     * @see R.styleable#AndroidManifest_sharedUserLabel
+     */
+    @StringRes
+    int getSharedUserLabelRes();
+
+    /**
      * @return List of all splits for a package. Note that base.apk is considered a
      * split and will be provided as index 0 of the list.
      */
@@ -113,6 +313,12 @@
     String getStaticSharedLibraryName();
 
     /**
+     * @see R.styleable#AndroidManifestStaticLibrary_version
+     * @hide
+     */
+    long getStaticSharedLibraryVersion();
+
+    /**
      * @return The {@link UUID} for use with {@link StorageManager} APIs identifying where this
      * package was installed.
      */
@@ -126,23 +332,319 @@
     int getTargetSdkVersion();
 
     /**
+     * @see ApplicationInfo#theme
+     * @see R.styleable#AndroidManifestApplication_theme
+     */
+    @StyleRes
+    int getThemeRes();
+
+    /**
+     * @see ApplicationInfo#uiOptions
+     * @see R.styleable#AndroidManifestApplication_uiOptions
+     */
+    int getUiOptions();
+
+    /**
+     * @see PackageInfo#versionName
+     */
+    @Nullable
+    String getVersionName();
+
+    /**
+     * @see ApplicationInfo#zygotePreloadName
+     * @see R.styleable#AndroidManifestApplication_zygotePreloadName
+     */
+    @Nullable
+    String getZygotePreloadName();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
+     * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
+     */
+    boolean isAllowAudioPlaybackCapture();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_BACKUP
+     * @see R.styleable#AndroidManifestApplication_allowBackup
+     */
+    boolean isAllowBackup();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
+     * @see R.styleable#AndroidManifestApplication_allowClearUserData
+     */
+    boolean isAllowClearUserData();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
+     * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
+     */
+    boolean isAllowClearUserDataOnFailedRestore();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
+     * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
+     */
+    boolean isAllowNativeHeapPointerTagging();
+
+    /**
+     * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
+     * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
+     */
+    boolean isAllowTaskReparenting();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
+     * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
+     */
+    boolean isAnyDensity();
+
+    /**
+     * @see ApplicationInfo#areAttributionsUserVisible()
+     * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
+     */
+    boolean isAttributionsUserVisible();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
+     * @see R.styleable#AndroidManifestApplication_backupInForeground
+     */
+    boolean isBackupInForeground();
+
+    /**
+     * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
+     * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
+     */
+    boolean isHardwareAccelerated();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
+     * @see R.styleable#AndroidManifestApplication_cantSaveState
+     */
+    boolean isCantSaveState();
+
+    /**
+     * @see PackageInfo#coreApp
+     */
+    boolean isCoreApp();
+
+    /**
+     * @see ApplicationInfo#crossProfile
+     * @see R.styleable#AndroidManifestApplication_crossProfile
+     */
+    boolean isCrossProfile();
+
+    /**
      * @see ApplicationInfo#FLAG_DEBUGGABLE
      * @see R.styleable#AndroidManifestApplication_debuggable
      */
     boolean isDebuggable();
 
     /**
+     * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
+     * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
+     */
+    boolean isDefaultToDeviceProtectedStorage();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
+     * @see R.styleable#AndroidManifestApplication_directBootAware
+     */
+    boolean isDirectBootAware();
+
+    /**
+     * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
+     * @see R.styleable#AndroidManifestApplication_extractNativeLibs
+     */
+    boolean isExtractNativeLibs();
+
+    /**
+     * @see ApplicationInfo#FLAG_FACTORY_TEST
+     */
+    boolean isFactoryTest();
+
+    /**
+     * @see R.styleable#AndroidManifestApplication_forceQueryable
+     */
+    boolean isForceQueryable();
+
+    /**
+     * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
+     * @see R.styleable#AndroidManifestApplication_fullBackupOnly
+     */
+    boolean isFullBackupOnly();
+
+    /**
+     * @see ApplicationInfo#FLAG_HAS_CODE
+     * @see R.styleable#AndroidManifestApplication_hasCode
+     */
+    boolean isHasCode();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
+     * @see R.styleable#AndroidManifestApplication_hasFragileUserData
+     */
+    boolean isHasFragileUserData();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_ISOLATED_SPLIT_LOADING
      * @see R.styleable#AndroidManifest_isolatedSplits
      */
     boolean isIsolatedSplitLoading();
 
     /**
+     * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
+     * @see R.styleable#AndroidManifestApplication_killAfterRestore
+     */
+    boolean isKillAfterRestore();
+
+    /**
+     * @see ApplicationInfo#FLAG_LARGE_HEAP
+     * @see R.styleable#AndroidManifestApplication_largeHeap
+     */
+    boolean isLargeHeap();
+
+    /**
+     * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
+     * smaller than the current SDK version, indicating the package wants to leave its declared
+     * {@link #getSharedUserId()}. This only occurs on new installs, pretending the app never
+     * declared one.
+     *
+     * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
+     */
+    boolean isLeavingSharedUser();
+
+    /**
+     * @see ApplicationInfo#FLAG_MULTIARCH
+     * @see R.styleable#AndroidManifestApplication_multiArch
+     */
+    boolean isMultiArch();
+
+    /**
+     * @see ApplicationInfo#nativeLibraryRootRequiresIsa
+     */
+    boolean isNativeLibraryRootRequiresIsa();
+
+    /**
+     * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
+     */
+    boolean isOnBackInvokedCallbackEnabled();
+
+    /**
+     * @see ApplicationInfo#FLAG_PERSISTENT
+     * @see R.styleable#AndroidManifestApplication_persistent
+     */
+    boolean isPersistent();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
+     * @see R.styleable#AndroidManifestProfileable
+     */
+    boolean isProfileable();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
+     * @see R.styleable#AndroidManifestProfileable_shell
+     */
+    boolean isProfileableByShell();
+
+    /**
+     * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
+     * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
+     */
+    boolean isRequestLegacyExternalStorage();
+
+    /**
+     * @see PackageInfo#requiredForAllUsers
+     * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
+     */
+    boolean isRequiredForAllUsers();
+
+    /**
+     * Whether the enabled settings of components in the application should be reset to the default,
+     * when the application's user data is cleared.
+     *
+     * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
+     */
+    boolean isResetEnabledSettingsOnAppDataCleared();
+
+    /**
+     * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
+     * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
+     */
+    boolean isRestoreAnyVersion();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY
      */
     boolean isSignedWithPlatformKey();
 
     /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#GINGERBREAD}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
+     */
+    boolean isSupportsExtraLargeScreens();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
+     */
+    boolean isSupportsLargeScreens();
+
+    /**
+     * If omitted from manifest, returns true.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
+     */
+    boolean isSupportsNormalScreens();
+
+    /**
+     * @see ApplicationInfo#FLAG_SUPPORTS_RTL
+     * @see R.styleable#AndroidManifestApplication_supportsRtl
+     */
+    boolean isSupportsRtl();
+
+    /**
+     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
+     * android.os.Build.VERSION_CODES#DONUT}.
+     *
+     * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
+     * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
+     */
+    boolean isSupportsSmallScreens();
+
+    /**
+     * @see ApplicationInfo#FLAG_TEST_ONLY
+     * @see R.styleable#AndroidManifestApplication_testOnly
+     */
+    boolean isTestOnly();
+
+    /**
+     * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
+     * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
+     * cpuAbiOverride is also set.
+     *
+     * @see R.attr#use32bitAbi
+     */
+    boolean isUse32BitAbi();
+
+    /**
+     * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
+     * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
+     */
+    boolean isUsesCleartextTraffic();
+
+    /**
      * @see ApplicationInfo#PRIVATE_FLAG_USE_EMBEDDED_DEX
      * @see R.styleable#AndroidManifestApplication_useEmbeddedDex
      */
@@ -163,14 +665,6 @@
     // Methods below this comment are not yet exposed as API
 
     /**
-     * @see ApplicationInfo#areAttributionsUserVisible()
-     * @see R.styleable#AndroidManifestApplication_attributionsAreUserVisible
-     * @hide
-     */
-    @Nullable
-    boolean areAttributionsUserVisible();
-
-    /**
      * Set of Activities parsed from the manifest.
      * <p>
      * This contains minimal system state and does not
@@ -207,14 +701,6 @@
     List<ParsedApexSystemService> getApexSystemServices();
 
     /**
-     * @see ApplicationInfo#appComponentFactory
-     * @see R.styleable#AndroidManifestApplication_appComponentFactory
-     * @hide
-     */
-    @Nullable
-    String getAppComponentFactory();
-
-    /**
      * @see R.styleable#AndroidManifestAttribution
      * @hide
      */
@@ -232,66 +718,18 @@
     int getAutoRevokePermissions();
 
     /**
-     * @see ApplicationInfo#backupAgentName
-     * @see R.styleable#AndroidManifestApplication_backupAgent
-     * @hide
-     */
-    @Nullable
-    String getBackupAgentName();
-
-    /**
-     * @see ApplicationInfo#banner
-     * @see R.styleable#AndroidManifestApplication_banner
-     * @hide
-     */
-    int getBanner();
-
-    /**
      * @see ApplicationInfo#sourceDir
      * @see ApplicationInfo#getBaseCodePath
+     *
+     * @deprecated Use {@link #getSplits()}[0].{@link AndroidPackageSplit#getPath() getPath()}
+     *
      * @hide
      */
+    @Deprecated
     @NonNull
     String getBaseApkPath();
 
     /**
-     * @see PackageInfo#baseRevisionCode
-     * @see R.styleable#AndroidManifest_revisionCode
-     * @hide
-     */
-    int getBaseRevisionCode();
-
-    /**
-     * @see ApplicationInfo#category
-     * @see R.styleable#AndroidManifestApplication_appCategory
-     * @hide
-     */
-    int getCategory();
-
-    /**
-     * @see ApplicationInfo#classLoaderName
-     * @see R.styleable#AndroidManifestApplication_classLoader
-     * @hide
-     */
-    @Nullable
-    String getClassLoaderName();
-
-    /**
-     * @see ApplicationInfo#className
-     * @see R.styleable#AndroidManifestApplication_name
-     * @hide
-     */
-    @Nullable
-    String getClassName();
-
-    /**
-     * @see ApplicationInfo#compatibleWidthLimitDp
-     * @see R.styleable#AndroidManifestSupportsScreens_compatibleWidthLimitDp
-     * @hide
-     */
-    int getCompatibleWidthLimitDp();
-
-    /**
      * @see ApplicationInfo#compileSdkVersion
      * @see R.styleable#AndroidManifest_compileSdkVersion
      * @hide
@@ -316,20 +754,6 @@
     List<ConfigurationInfo> getConfigPreferences();
 
     /**
-     * @see ApplicationInfo#dataExtractionRulesRes
-     * @see R.styleable#AndroidManifestApplication_dataExtractionRules
-     * @hide
-     */
-    int getDataExtractionRules();
-
-    /**
-     * @see ApplicationInfo#descriptionRes
-     * @see R.styleable#AndroidManifestApplication_description
-     * @hide
-     */
-    int getDescriptionRes();
-
-    /**
      * @see PackageInfo#featureGroups
      * @see R.styleable#AndroidManifestUsesFeature
      * @hide
@@ -339,28 +763,6 @@
     List<FeatureGroupInfo> getFeatureGroups();
 
     /**
-     * @see ApplicationInfo#fullBackupContent
-     * @see R.styleable#AndroidManifestApplication_fullBackupContent
-     * @hide
-     */
-    int getFullBackupContent();
-
-    /**
-     * @see ApplicationInfo#getGwpAsanMode()
-     * @see R.styleable#AndroidManifestApplication_gwpAsanMode
-     * @hide
-     */
-    @ApplicationInfo.GwpAsanMode
-    int getGwpAsanMode();
-
-    /**
-     * @see ApplicationInfo#iconRes
-     * @see R.styleable#AndroidManifestApplication_icon
-     * @hide
-     */
-    int getIconRes();
-
-    /**
      * Permissions requested but not in the manifest. These may have been split or migrated from
      * previous versions/definitions.
      * @hide
@@ -407,43 +809,6 @@
     Set<String> getKnownActivityEmbeddingCerts();
 
     /**
-     * @see ApplicationInfo#labelRes
-     * @see R.styleable#AndroidManifestApplication_label
-     * @hide
-     */
-    int getLabelRes();
-
-    /**
-     * @see ApplicationInfo#largestWidthLimitDp
-     * @see R.styleable#AndroidManifestSupportsScreens_largestWidthLimitDp
-     * @hide
-     */
-    int getLargestWidthLimitDp();
-
-    /**
-     * The resource ID used to provide the application's locales configuration.
-     *
-     * @see R.styleable#AndroidManifestApplication_localeConfig
-     * @hide
-     */
-    int getLocaleConfigRes();
-
-    /**
-     * @see ApplicationInfo#logo
-     * @see R.styleable#AndroidManifestApplication_logo
-     * @hide
-     */
-    int getLogo();
-
-    /**
-     * @see PackageInfo#getLongVersionCode()
-     * @see R.styleable#AndroidManifest_versionCode
-     * @see R.styleable#AndroidManifest_versionCodeMajor
-     * @hide
-     */
-    long getLongVersionCode();
-
-    /**
      * @see ApplicationInfo#manageSpaceActivityName
      * @see R.styleable#AndroidManifestApplication_manageSpaceActivity
      * @hide
@@ -460,13 +825,6 @@
     String getManifestPackageName();
 
     /**
-     * @see ApplicationInfo#maxAspectRatio
-     * @see R.styleable#AndroidManifestApplication_maxAspectRatio
-     * @hide
-     */
-    float getMaxAspectRatio();
-
-    /**
      * @see R.styleable#AndroidManifestUsesSdk_maxSdkVersion
      * @hide
      */
@@ -497,13 +855,6 @@
     Set<String> getMimeGroups();
 
     /**
-     * @see ApplicationInfo#minAspectRatio
-     * @see R.styleable#AndroidManifestApplication_minAspectRatio
-     * @hide
-     */
-    float getMinAspectRatio();
-
-    /**
      * @see R.styleable#AndroidManifestExtensionSdk
      * @hide
      */
@@ -519,14 +870,6 @@
     int getMinSdkVersion();
 
     /**
-     * @see ApplicationInfo#getNativeHeapZeroInitialized()
-     * @see R.styleable#AndroidManifestApplication_nativeHeapZeroInitialized
-     * @hide
-     */
-    @ApplicationInfo.NativeHeapZeroInitialized
-    int getNativeHeapZeroInitialized();
-
-    /**
      * @see ApplicationInfo#nativeLibraryDir
      * @hide
      */
@@ -541,13 +884,6 @@
     String getNativeLibraryRootDir();
 
     /**
-     * @see ApplicationInfo#networkSecurityConfigRes
-     * @see R.styleable#AndroidManifestApplication_networkSecurityConfig
-     * @hide
-     */
-    int getNetworkSecurityConfigRes();
-
-    /**
      * If {@link R.styleable#AndroidManifestApplication_label} is a string literal, this is it.
      * Otherwise, it's stored as {@link #getLabelRes()}.
      *
@@ -783,21 +1119,6 @@
     List<String> getRequestedPermissions();
 
     /**
-     * @see PackageInfo#requiredAccountType
-     * @see R.styleable#AndroidManifestApplication_requiredAccountType
-     * @hide
-     */
-    @Nullable
-    String getRequiredAccountType();
-
-    /**
-     * @see ApplicationInfo#requiresSmallestWidthDp
-     * @see R.styleable#AndroidManifestSupportsScreens_requiresSmallestWidthDp
-     * @hide
-     */
-    int getRequiresSmallestWidthDp();
-
-    /**
      * Whether or not the app requested explicitly resizeable Activities. Null value means nothing
      * was explicitly requested.
      *
@@ -820,23 +1141,6 @@
     byte[] getRestrictUpdateHash();
 
     /**
-     * The restricted account authenticator type that is used by this application.
-     *
-     * @see PackageInfo#restrictedAccountType
-     * @see R.styleable#AndroidManifestApplication_restrictedAccountType
-     * @hide
-     */
-    @Nullable
-    String getRestrictedAccountType();
-
-    /**
-     * @see ApplicationInfo#roundIconRes
-     * @see R.styleable#AndroidManifestApplication_roundIcon
-     * @hide
-     */
-    int getRoundIconRes();
-
-    /**
      * @see R.styleable#AndroidManifestSdkLibrary_versionMajor
      * @hide
      */
@@ -868,21 +1172,6 @@
     List<ParsedService> getServices();
 
     /**
-     * @see PackageInfo#sharedUserId
-     * @see R.styleable#AndroidManifest_sharedUserId
-     * @hide
-     */
-    @Nullable
-    String getSharedUserId();
-
-    /**
-     * @see PackageInfo#sharedUserLabel
-     * @see R.styleable#AndroidManifest_sharedUserLabel
-     * @hide
-     */
-    int getSharedUserLabel();
-
-    /**
      * The signature data of all APKs in this package, which must be exactly the same across the
      * base and splits.
      * @hide
@@ -945,12 +1234,6 @@
     int[] getSplitRevisionCodes();
 
     /**
-     * @see R.styleable#AndroidManifestStaticLibrary_version
-     * @hide
-     */
-    long getStaticSharedLibVersion();
-
-    /**
      * @see ApplicationInfo#targetSandboxVersion
      * @see R.styleable#AndroidManifest_targetSandboxVersion
      * @hide
@@ -966,20 +1249,6 @@
     String getTaskAffinity();
 
     /**
-     * @see ApplicationInfo#theme
-     * @see R.styleable#AndroidManifestApplication_theme
-     * @hide
-     */
-    int getTheme();
-
-    /**
-     * @see ApplicationInfo#uiOptions
-     * @see R.styleable#AndroidManifestApplication_uiOptions
-     * @hide
-     */
-    int getUiOptions();
-
-    /**
      * This is an appId, the {@link ApplicationInfo#uid} if the user ID is
      * {@link android.os.UserHandle#SYSTEM}.
      *
@@ -1091,27 +1360,12 @@
     long[] getUsesStaticLibrariesVersions();
 
     /**
-     * @see PackageInfo#versionName
-     * @hide
-     */
-    @Nullable
-    String getVersionName();
-
-    /**
      * @see ApplicationInfo#volumeUuid
      * @hide
      */
     @Nullable
     String getVolumeUuid();
 
-    /**
-     * @see ApplicationInfo#zygotePreloadName
-     * @see R.styleable#AndroidManifestApplication_zygotePreloadName
-     * @hide
-     */
-    @Nullable
-    String getZygotePreloadName();
-
     /** @hide */
     boolean hasPreserveLegacyExternalStorage();
 
@@ -1129,110 +1383,10 @@
      */
     Boolean hasRequestRawExternalStorageAccess();
 
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE
-     * @see R.styleable#AndroidManifestApplication_allowAudioPlaybackCapture
-     * @hide
-     */
-    boolean isAllowAudioPlaybackCapture();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_BACKUP
-     * @see R.styleable#AndroidManifestApplication_allowBackup
-     * @hide
-     */
-    boolean isAllowBackup();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_CLEAR_USER_DATA
-     * @see R.styleable#AndroidManifestApplication_allowClearUserData
-     * @hide
-     */
-    boolean isAllowClearUserData();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE
-     * @see R.styleable#AndroidManifestApplication_allowClearUserDataOnFailedRestore
-     * @hide
-     */
-    boolean isAllowClearUserDataOnFailedRestore();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING
-     * @see R.styleable#AndroidManifestApplication_allowNativeHeapPointerTagging
-     * @hide
-     */
-    boolean isAllowNativeHeapPointerTagging();
-
-    /**
-     * @see ApplicationInfo#FLAG_ALLOW_TASK_REPARENTING
-     * @see R.styleable#AndroidManifestApplication_allowTaskReparenting
-     * @hide
-     */
-    boolean isAllowTaskReparenting();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_anyDensity
-     * @see ApplicationInfo#FLAG_SUPPORTS_SCREEN_DENSITIES
-     * @hide
-     */
-    boolean isAnyDensity();
-
     /** @hide */
     boolean isApex();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_BACKUP_IN_FOREGROUND
-     * @see R.styleable#AndroidManifestApplication_backupInForeground
-     * @hide
-     */
-    boolean isBackupInForeground();
-
-    /**
-     * @see ApplicationInfo#FLAG_HARDWARE_ACCELERATED
-     * @see R.styleable#AndroidManifestApplication_hardwareAccelerated
-     * @hide
-     */
-    boolean isBaseHardwareAccelerated();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_CANT_SAVE_STATE
-     * @see R.styleable#AndroidManifestApplication_cantSaveState
-     * @hide
-     */
-    boolean isCantSaveState();
-
-    /**
-     * @see PackageInfo#coreApp
-     * @hide
-     */
-    boolean isCoreApp();
-
-    /**
-     * @see ApplicationInfo#crossProfile
-     * @see R.styleable#AndroidManifestApplication_crossProfile
-     * @hide
-     */
-    boolean isCrossProfile();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE
-     * @see R.styleable#AndroidManifestApplication_defaultToDeviceProtectedStorage
-     * @hide
-     */
-    boolean isDefaultToDeviceProtectedStorage();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_DIRECT_BOOT_AWARE
-     * @see R.styleable#AndroidManifestApplication_directBootAware
-     * @hide
-     */
-    boolean isDirectBootAware();
-
-    /**
      * @see ApplicationInfo#enabled
      * @see R.styleable#AndroidManifestApplication_enabled
      * @hide
@@ -1246,32 +1400,6 @@
     boolean isExternalStorage();
 
     /**
-     * @see ApplicationInfo#FLAG_EXTRACT_NATIVE_LIBS
-     * @see R.styleable#AndroidManifestApplication_extractNativeLibs
-     * @hide
-     */
-    boolean isExtractNativeLibs();
-
-    /**
-     * @see ApplicationInfo#FLAG_FACTORY_TEST
-     * @hide
-     */
-    boolean isFactoryTest();
-
-    /**
-     * @see R.styleable#AndroidManifestApplication_forceQueryable
-     * @hide
-     */
-    boolean isForceQueryable();
-
-    /**
-     * @see ApplicationInfo#FLAG_FULL_BACKUP_ONLY
-     * @see R.styleable#AndroidManifestApplication_fullBackupOnly
-     * @hide
-     */
-    boolean isFullBackupOnly();
-
-    /**
      * @see ApplicationInfo#FLAG_IS_GAME
      * @see R.styleable#AndroidManifestApplication_isGame
      * @hide
@@ -1280,13 +1408,6 @@
     boolean isGame();
 
     /**
-     * @see ApplicationInfo#FLAG_HAS_CODE
-     * @see R.styleable#AndroidManifestApplication_hasCode
-     * @hide
-     */
-    boolean isHasCode();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_HAS_DOMAIN_URLS
      * @see R.styleable#AndroidManifestIntentFilter
      * @hide
@@ -1294,67 +1415,6 @@
     boolean isHasDomainUrls();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_HAS_FRAGILE_USER_DATA
-     * @see R.styleable#AndroidManifestApplication_hasFragileUserData
-     * @hide
-     */
-    boolean isHasFragileUserData();
-
-    /**
-     * @see ApplicationInfo#FLAG_KILL_AFTER_RESTORE
-     * @see R.styleable#AndroidManifestApplication_killAfterRestore
-     * @hide
-     */
-    boolean isKillAfterRestore();
-
-    /**
-     * @see ApplicationInfo#FLAG_LARGE_HEAP
-     * @see R.styleable#AndroidManifestApplication_largeHeap
-     * @hide
-     */
-    boolean isLargeHeap();
-
-    /**
-     * Returns true if R.styleable#AndroidManifest_sharedUserMaxSdkVersion is set to a value
-     * smaller than the current SDK version.
-     *
-     * @see R.styleable#AndroidManifest_sharedUserMaxSdkVersion
-     * @hide
-     */
-    boolean isLeavingSharedUid();
-
-    /**
-     * @see ApplicationInfo#FLAG_MULTIARCH
-     * @see R.styleable#AndroidManifestApplication_multiArch
-     * @hide
-     */
-    boolean isMultiArch();
-
-    /**
-     * @see ApplicationInfo#nativeLibraryRootRequiresIsa
-     * @hide
-     */
-    boolean isNativeLibraryRootRequiresIsa();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ODM
-     * @hide
-     */
-    boolean isOdm();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_OEM
-     * @hide
-     */
-    boolean isOem();
-
-    /**
-     * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
-     * @hide
-     */
-    boolean isOnBackInvokedCallbackEnabled();
-
-    /**
      * @see ApplicationInfo#PRIVATE_FLAG_IS_RESOURCE_OVERLAY
      * @see ApplicationInfo#isResourceOverlay()
      * @see R.styleable#AndroidManifestResourceOverlay
@@ -1379,62 +1439,6 @@
     boolean isPartiallyDirectBootAware();
 
     /**
-     * @see ApplicationInfo#FLAG_PERSISTENT
-     * @see R.styleable#AndroidManifestApplication_persistent
-     * @hide
-     */
-    boolean isPersistent();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_PRIVILEGED
-     * @hide
-     */
-    boolean isPrivileged();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_PRODUCT
-     * @hide
-     */
-    boolean isProduct();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_EXT_PROFILEABLE
-     * @see R.styleable#AndroidManifestProfileable
-     * @hide
-     */
-    boolean isProfileable();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_PROFILEABLE_BY_SHELL
-     * @see R.styleable#AndroidManifestProfileable_shell
-     * @hide
-     */
-    boolean isProfileableByShell();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE
-     * @see R.styleable#AndroidManifestApplication_requestLegacyExternalStorage
-     * @hide
-     */
-    boolean isRequestLegacyExternalStorage();
-
-    /**
-     * @see PackageInfo#requiredForAllUsers
-     * @see R.styleable#AndroidManifestApplication_requiredForAllUsers
-     * @hide
-     */
-    boolean isRequiredForAllUsers();
-
-    /**
-     * Whether the enabled settings of components in the application should be reset to the default,
-     * when the application's user data is cleared.
-     *
-     * @see R.styleable#AndroidManifestApplication_resetEnabledSettingsOnAppDataCleared
-     * @hide
-     */
-    boolean isResetEnabledSettingsOnAppDataCleared();
-
-    /**
      * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
      * android.os.Build.VERSION_CODES#DONUT}.
      *
@@ -1452,13 +1456,6 @@
     boolean isResizeableActivityViaSdkVersion();
 
     /**
-     * @see ApplicationInfo#FLAG_RESTORE_ANY_VERSION
-     * @see R.styleable#AndroidManifestApplication_restoreAnyVersion
-     * @hide
-     */
-    boolean isRestoreAnyVersion();
-
-    /**
      * True means that this package/app contains an SDK library.
      * @see R.styleable#AndroidManifestSdkLibrary
      * @hide
@@ -1479,94 +1476,6 @@
     boolean isStub();
 
     /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#GINGERBREAD}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_xlargeScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_XLARGE_SCREENS
-     * @hide
-     */
-    boolean isSupportsExtraLargeScreens();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_largeScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_LARGE_SCREENS
-     * @hide
-     */
-    boolean isSupportsLargeScreens();
-
-    /**
-     * If omitted from manifest, returns true.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_normalScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_NORMAL_SCREENS
-     * @hide
-     */
-    boolean isSupportsNormalScreens();
-
-    /**
-     * @see ApplicationInfo#FLAG_SUPPORTS_RTL
-     * @see R.styleable#AndroidManifestApplication_supportsRtl
-     * @hide
-     */
-    boolean isSupportsRtl();
-
-    /**
-     * If omitted from manifest, returns true if {@link #getTargetSdkVersion()} >= {@link
-     * android.os.Build.VERSION_CODES#DONUT}.
-     *
-     * @see R.styleable#AndroidManifestSupportsScreens_smallScreens
-     * @see ApplicationInfo#FLAG_SUPPORTS_SMALL_SCREENS
-     * @hide
-     */
-    boolean isSupportsSmallScreens();
-
-    /**
-     * @see ApplicationInfo#FLAG_SYSTEM
-     * @hide
-     */
-    boolean isSystem();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_SYSTEM_EXT
-     * @hide
-     */
-    boolean isSystemExt();
-
-    /**
-     * @see ApplicationInfo#FLAG_TEST_ONLY
-     * @see R.styleable#AndroidManifestApplication_testOnly
-     * @hide
-     */
-    boolean isTestOnly();
-
-    /**
-     * The install time abi override to choose 32bit abi's when multiple abi's are present. This is
-     * only meaningful for multiarch applications. The use32bitAbi attribute is ignored if
-     * cpuAbiOverride is also set.
-     *
-     * @see R.attr#use32bitAbi
-     * @hide
-     */
-    boolean isUse32BitAbi();
-
-    /**
-     * @see ApplicationInfo#FLAG_USES_CLEARTEXT_TRAFFIC
-     * @see R.styleable#AndroidManifestApplication_usesCleartextTraffic
-     * @hide
-     */
-    boolean isUsesCleartextTraffic();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_VENDOR
-     * @hide
-     */
-    boolean isVendor();
-
-    /**
      * Set if the any of components are visible to instant applications.
      *
      * @see R.styleable#AndroidManifestActivity_visibleToInstantApps
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 67b7647..7335a46 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -164,12 +164,12 @@
     List<SharedLibrary> getUsesLibraries();
 
     /**
-     * @see AndroidPackage#isPrivileged()
+     * @see ApplicationInfo#PRIVATE_FLAG_PRIVILEGED
      */
     boolean isPrivileged();
 
     /**
-     * @see AndroidPackage#isSystem()
+     * @see ApplicationInfo#FLAG_SYSTEM
      */
     boolean isSystem();
 
@@ -367,19 +367,19 @@
     boolean isInstallPermissionsFixed();
 
     /**
-     * @see AndroidPackage#isOdm()
+     * @see ApplicationInfo#PRIVATE_FLAG_ODM
      * @hide
      */
     boolean isOdm();
 
     /**
-     * @see AndroidPackage#isOem()
+     * @see ApplicationInfo#PRIVATE_FLAG_OEM
      * @hide
      */
     boolean isOem();
 
     /**
-     * @see AndroidPackage#isProduct()
+     * @see ApplicationInfo#PRIVATE_FLAG_PRODUCT
      * @hide
      */
     boolean isProduct();
@@ -391,7 +391,7 @@
     boolean isRequiredForSystemUser();
 
     /**
-     * @see AndroidPackage#isSystemExt()
+     * @see ApplicationInfo#PRIVATE_FLAG_SYSTEM_EXT
      * @hide
      */
     boolean isSystemExt();
@@ -410,7 +410,7 @@
     boolean isApkInUpdatedApex();
 
     /**
-     * @see AndroidPackage#isVendor()
+     * @see ApplicationInfo#PRIVATE_FLAG_VENDOR
      * @hide
      */
     boolean isVendor();
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
index ea791e1..3bd0e0d 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
@@ -144,7 +144,7 @@
                                 | flag(ActivityInfo.FLAG_SYSTEM_USER_ONLY, R.styleable.AndroidManifestActivity_systemUserOnly, sa)));
 
             if (!receiver) {
-                activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isBaseHardwareAccelerated(), sa)
+                activity.setFlags(activity.getFlags() | (flag(ActivityInfo.FLAG_HARDWARE_ACCELERATED, R.styleable.AndroidManifestActivity_hardwareAccelerated, pkg.isHardwareAccelerated(), sa)
                                         | flag(ActivityInfo.FLAG_ALLOW_EMBEDDED, R.styleable.AndroidManifestActivity_allowEmbedded, sa)
                                         | flag(ActivityInfo.FLAG_ALWAYS_FOCUSABLE, R.styleable.AndroidManifestActivity_alwaysFocusable, sa)
                                         | flag(ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS, R.styleable.AndroidManifestActivity_autoRemoveFromRecents, sa)
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
index 1940eb5..a812257 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
@@ -100,6 +100,8 @@
                             R.styleable.AndroidManifestService_externalService, sa)
                             | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE,
                             R.styleable.AndroidManifestService_useAppZygote, sa)
+                            | flag(ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS,
+                            R.styleable.AndroidManifestService_allowSharedIsolatedProcess, sa)
                             | flag(ServiceInfo.FLAG_SINGLE_USER,
                             R.styleable.AndroidManifestService_singleUser, sa)));
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
index 16f5d16..12dfef4 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackage.java
@@ -155,7 +155,7 @@
 
     ParsingPackage setUiOptions(int uiOptions);
 
-    ParsingPackage setBaseHardwareAccelerated(boolean baseHardwareAccelerated);
+    ParsingPackage setHardwareAccelerated(boolean hardwareAccelerated);
 
     ParsingPackage setResizeableActivity(Boolean resizeable);
 
@@ -255,13 +255,13 @@
 
     ParsingPackage setBackupAgentName(String backupAgentName);
 
-    ParsingPackage setBanner(int banner);
+    ParsingPackage setBannerRes(int banner);
 
     ParsingPackage setCategory(int category);
 
     ParsingPackage setClassLoaderName(String classLoaderName);
 
-    ParsingPackage setClassName(String className);
+    ParsingPackage setApplicationClassName(String className);
 
     ParsingPackage setCompatibleWidthLimitDp(int compatibleWidthLimitDp);
 
@@ -281,9 +281,9 @@
 
     ParsingPackage setCrossProfile(boolean crossProfile);
 
-    ParsingPackage setFullBackupContent(int fullBackupContent);
+    ParsingPackage setFullBackupContentRes(int fullBackupContentRes);
 
-    ParsingPackage setDataExtractionRules(int dataExtractionRules);
+    ParsingPackage setDataExtractionRulesRes(int dataExtractionRulesRes);
 
     ParsingPackage setHasDomainUrls(boolean hasDomainUrls);
 
@@ -292,13 +292,13 @@
     ParsingPackage setInstallLocation(int installLocation);
 
     /** @see R#styleable.AndroidManifest_sharedUserMaxSdkVersion */
-    ParsingPackage setLeavingSharedUid(boolean leavingSharedUid);
+    ParsingPackage setLeavingSharedUser(boolean leavingSharedUser);
 
     ParsingPackage setLabelRes(int labelRes);
 
     ParsingPackage setLargestWidthLimitDp(int largestWidthLimitDp);
 
-    ParsingPackage setLogo(int logo);
+    ParsingPackage setLogoRes(int logo);
 
     ParsingPackage setManageSpaceActivityName(String manageSpaceActivityName);
 
@@ -336,13 +336,13 @@
 
     ParsingPackage setRoundIconRes(int roundIconRes);
 
-    ParsingPackage setSharedUserLabel(int sharedUserLabel);
+    ParsingPackage setSharedUserLabelRes(int sharedUserLabelRes);
 
     ParsingPackage setSigningDetails(@NonNull SigningDetails signingDetails);
 
     ParsingPackage setSplitClassLoaderName(int splitIndex, String classLoaderName);
 
-    ParsingPackage setStaticSharedLibVersion(long staticSharedLibVersion);
+    ParsingPackage setStaticSharedLibraryVersion(long staticSharedLibraryVersion);
 
     ParsingPackage setSupportsLargeScreens(int supportsLargeScreens);
 
@@ -354,7 +354,7 @@
 
     ParsingPackage setTargetSandboxVersion(int targetSandboxVersion);
 
-    ParsingPackage setTheme(int theme);
+    ParsingPackage setThemeRes(int theme);
 
     ParsingPackage setRequestForegroundServiceExemption(boolean requestForegroundServiceExemption);
 
@@ -512,7 +512,7 @@
 
     boolean isAnyDensity();
 
-    boolean isBaseHardwareAccelerated();
+    boolean isHardwareAccelerated();
 
     boolean isCantSaveState();
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index c6e1793..2a2640d 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -1044,9 +1044,9 @@
         }
 
         return input.success(pkg
-                .setLeavingSharedUid(leaving)
+                .setLeavingSharedUser(leaving)
                 .setSharedUserId(str.intern())
-                .setSharedUserLabel(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
+                .setSharedUserLabelRes(resId(R.styleable.AndroidManifest_sharedUserLabel, sa)));
     }
 
     private static ParseResult<ParsingPackage> parseKeySets(ParseInput input,
@@ -1869,7 +1869,7 @@
                     return input.error("Empty class name in package " + packageName);
                 }
 
-                pkg.setClassName(outInfoName);
+                pkg.setApplicationClassName(outInfoName);
             }
 
             TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
@@ -1939,7 +1939,7 @@
                         fullBackupContent = v.data == 0 ? -1 : 0;
                     }
 
-                    pkg.setFullBackupContent(fullBackupContent);
+                    pkg.setFullBackupContentRes(fullBackupContent);
                 }
                 if (DEBUG_BACKUP) {
                     Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
@@ -2247,7 +2247,7 @@
                 .setOnBackInvokedCallbackEnabled(bool(false, R.styleable.AndroidManifestApplication_enableOnBackInvokedCallback, sa))
                 // targetSdkVersion gated
                 .setAllowAudioPlaybackCapture(bool(targetSdk >= Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_allowAudioPlaybackCapture, sa))
-                .setBaseHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
+                .setHardwareAccelerated(bool(targetSdk >= Build.VERSION_CODES.ICE_CREAM_SANDWICH, R.styleable.AndroidManifestApplication_hardwareAccelerated, sa))
                 .setRequestLegacyExternalStorage(bool(targetSdk < Build.VERSION_CODES.Q, R.styleable.AndroidManifestApplication_requestLegacyExternalStorage, sa))
                 .setUsesCleartextTraffic(bool(targetSdk < Build.VERSION_CODES.P, R.styleable.AndroidManifestApplication_usesCleartextTraffic, sa))
                 // Ints Default 0
@@ -2258,14 +2258,14 @@
                 .setMaxAspectRatio(aFloat(R.styleable.AndroidManifestApplication_maxAspectRatio, sa))
                 .setMinAspectRatio(aFloat(R.styleable.AndroidManifestApplication_minAspectRatio, sa))
                 // Resource ID
-                .setBanner(resId(R.styleable.AndroidManifestApplication_banner, sa))
+                .setBannerRes(resId(R.styleable.AndroidManifestApplication_banner, sa))
                 .setDescriptionRes(resId(R.styleable.AndroidManifestApplication_description, sa))
                 .setIconRes(resId(R.styleable.AndroidManifestApplication_icon, sa))
-                .setLogo(resId(R.styleable.AndroidManifestApplication_logo, sa))
+                .setLogoRes(resId(R.styleable.AndroidManifestApplication_logo, sa))
                 .setNetworkSecurityConfigRes(resId(R.styleable.AndroidManifestApplication_networkSecurityConfig, sa))
                 .setRoundIconRes(resId(R.styleable.AndroidManifestApplication_roundIcon, sa))
-                .setTheme(resId(R.styleable.AndroidManifestApplication_theme, sa))
-                .setDataExtractionRules(
+                .setThemeRes(resId(R.styleable.AndroidManifestApplication_theme, sa))
+                .setDataExtractionRulesRes(
                         resId(R.styleable.AndroidManifestApplication_dataExtractionRules, sa))
                 .setLocaleConfigRes(resId(R.styleable.AndroidManifestApplication_localeConfig, sa))
                 // Strings
@@ -2399,7 +2399,7 @@
             }
 
             return input.success(pkg.setStaticSharedLibraryName(lname.intern())
-                    .setStaticSharedLibVersion(
+                    .setStaticSharedLibraryVersion(
                             PackageInfo.composeLongVersionCode(versionMajor, version))
                     .setStaticSharedLibrary(true));
         } finally {
@@ -2694,7 +2694,7 @@
         // Build custom App Details activity info instead of parsing it from xml
         return input.success(ParsedActivity.makeAppDetailsActivity(packageName,
                 pkg.getProcessName(), pkg.getUiOptions(), taskAffinity,
-                pkg.isBaseHardwareAccelerated()));
+                pkg.isHardwareAccelerated()));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index a19beea..842f685 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -498,9 +498,9 @@
         }
 
         String packageName = activity.getPackageName();
-        AndroidPackage pkg = computer.getPackage(packageName);
+        var packageState = computer.getPackageStateInternal(packageName);
 
-        final boolean privilegedApp = pkg.isPrivileged();
+        final boolean privilegedApp = packageState.isPrivileged();
         String className = activity.getClassName();
         if (!privilegedApp) {
             // non-privileged applications can never define a priority >0
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
index 75d7162..9115775 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolverBase.java
@@ -290,7 +290,7 @@
                 continue;
             }
 
-            if (safeMode && !pkg.isSystem()) {
+            if (safeMode && !ps.isSystem()) {
                 continue;
             }
             if (appInfoGenerator == null) {
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index d6cac33..db44e14 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -1130,18 +1130,18 @@
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptorCallback.ActivityInterceptResult intercept(
-                            ActivityInterceptorInfo info) {
+                    public ActivityInterceptorCallback.ActivityInterceptResult
+                            onInterceptActivityLaunch(@NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
 
                     @Override
                     public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
                             ActivityInterceptorInfo info) {
-                        super.onActivityLaunched(taskInfo, activityInfo, info);
                         if (!shouldShowNotificationDialogOrClearFlags(taskInfo,
-                                activityInfo.packageName, info.callingPackage, info.intent,
-                                info.checkedOptions, activityInfo.name, true)
+                                activityInfo.packageName, info.getCallingPackage(),
+                                info.getIntent(), info.getCheckedOptions(), activityInfo.name,
+                                true)
                                 || isNoDisplayActivity(activityInfo)) {
                             return;
                         }
@@ -1318,12 +1318,12 @@
                     ACTION_REQUEST_PERMISSIONS_FOR_OTHER);
             grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, pkgName);
 
-            final boolean remoteAnimation = info != null && info.checkedOptions != null
-                    && info.checkedOptions.getAnimationType() == ANIM_REMOTE_ANIMATION
-                    && info.clearOptionsAnimation != null;
+            final boolean remoteAnimation = info != null && info.getCheckedOptions() != null
+                    && info.getCheckedOptions().getAnimationType() == ANIM_REMOTE_ANIMATION
+                    && info.getClearOptionsAnimationRunnable() != null;
             ActivityOptions options = remoteAnimation ? ActivityOptions.makeRemoteAnimation(
-                        info.checkedOptions.getRemoteAnimationAdapter(),
-                        info.checkedOptions.getRemoteTransition())
+                        info.getCheckedOptions().getRemoteAnimationAdapter(),
+                        info.getCheckedOptions().getRemoteTransition())
                     : new ActivityOptions(new Bundle());
             options.setTaskOverlay(true, false);
             options.setLaunchTaskId(taskId);
@@ -1333,7 +1333,7 @@
                 // animation from the intercepted activity and its siblings to prevent duplication.
                 // This should trigger ActivityRecord#clearOptionsAnimationForSiblings for the
                 // intercepted activity.
-                info.clearOptionsAnimation.run();
+                info.getClearOptionsAnimationRunnable().run();
             }
             try {
                 mContext.startActivityAsUser(grantPermission, options.toBundle(), user);
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 81b4865..fe0fe29 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -617,7 +617,6 @@
     private final com.android.internal.policy.LogDecelerateInterpolator mLogDecelerateInterpolator
             = new LogDecelerateInterpolator(100, 0);
 
-    private boolean mPerDisplayFocusEnabled = false;
     private volatile int mTopFocusedDisplayId = INVALID_DISPLAY;
 
     private int mPowerButtonSuppressionDelayMillis = POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS;
@@ -1056,13 +1055,6 @@
             return;
         }
 
-        // Don't dream if the user isn't user zero.
-        // TODO(b/261907079): Move this check to DreamManagerService#canStartDreamingInternal().
-        if (ActivityManager.getCurrentUser() != UserHandle.USER_SYSTEM) {
-            noDreamAction.run();
-            return;
-        }
-
         final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
         if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
             noDreamAction.run();
@@ -2125,9 +2117,6 @@
         mHandleVolumeKeysInWM = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_handleVolumeKeysInWindowManager);
 
-        mPerDisplayFocusEnabled = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_perDisplayFocusEnabled);
-
         mWakeUpToLastStateTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_wakeUpToLastStateTimeoutMillis);
 
@@ -4321,23 +4310,20 @@
             wakeUpFromWakeKey(event);
         }
 
-        if ((result & ACTION_PASS_TO_USER) != 0 && !mPerDisplayFocusEnabled
-                && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
-            // If the key event is targeted to a specific display, then the user is interacting with
-            // that display. Therefore, give focus to the display that the user is interacting with,
-            // unless that display maintains its own focus.
-            Display display = mDisplayManager.getDisplay(displayId);
-            if ((display.getFlags() & Display.FLAG_OWN_FOCUS) == 0) {
-                // An event is targeting a non-focused display. Move the display to top so that
-                // it can become the focused display to interact with the user.
-                // This should be done asynchronously, once the focus logic is fully moved to input
-                // from windowmanager. Currently, we need to ensure the setInputWindows completes,
-                // which would force the focus event to be queued before the current key event.
-                // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
-                Log.i(TAG, "Moving non-focused display " + displayId + " to top "
-                        + "because a key is targeting it");
-                mWindowManagerFuncs.moveDisplayToTop(displayId);
-            }
+        // If the key event is targeted to a specific display, then the user is interacting with
+        // that display. Therefore, try to give focus to the display that the user is interacting
+        // with.
+        if ((result & ACTION_PASS_TO_USER) != 0 && displayId != INVALID_DISPLAY
+                && displayId != mTopFocusedDisplayId) {
+            // An event is targeting a non-focused display. Move the display to top so that
+            // it can become the focused display to interact with the user.
+            // This should be done asynchronously, once the focus logic is fully moved to input
+            // from windowmanager. Currently, we need to ensure the setInputWindows completes,
+            // which would force the focus event to be queued before the current key event.
+            // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
+            Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+                    + "because a key is targeting it");
+            mWindowManagerFuncs.moveDisplayToTopIfAllowed(displayId);
         }
 
         return result;
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index f5ce461..94fb840 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -136,10 +136,6 @@
     @IntDef({NAV_BAR_LEFT, NAV_BAR_RIGHT, NAV_BAR_BOTTOM})
     @interface NavigationBarPosition {}
 
-    @Retention(SOURCE)
-    @IntDef({ALT_BAR_UNKNOWN, ALT_BAR_LEFT, ALT_BAR_RIGHT, ALT_BAR_BOTTOM, ALT_BAR_TOP})
-    @interface AltBarPosition {}
-
     /**
      * Pass this event to the user / app.  To be returned from
      * {@link #interceptKeyBeforeQueueing}.
@@ -334,8 +330,11 @@
         /**
          * Hint to window manager that the user is interacting with a display that should be treated
          * as the top display.
+         *
+         * Calling this method does not guarantee that the display will be moved to top. The window
+         * manager will make the final decision whether or not to move the display.
          */
-        void moveDisplayToTop(int displayId);
+        void moveDisplayToTopIfAllowed(int displayId);
 
         /**
          * Return whether the app transition state is idle.
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 09a7b29..326d709 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -638,7 +638,7 @@
         } catch (RemoteException ex) {
             // Ignore
         }
-        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason);
+        FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_WAKE_REPORTED, reason, reasonUid);
     }
 
     /**
diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java
index 6b2c6e3..514caf2 100644
--- a/services/core/java/com/android/server/power/ThermalManagerService.java
+++ b/services/core/java/com/android/server/power/ThermalManagerService.java
@@ -746,6 +746,8 @@
                         }
                         ret.add(new Temperature(t.value, t.type, t.name, t.throttlingStatus));
                     }
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getCurrentTemperatures, reconnecting", e);
                     connectToHal();
@@ -776,6 +778,8 @@
                         }
                         ret.add(new CoolingDevice(t.value, t.type, t.name));
                     }
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getCurrentCoolingDevices due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getCurrentCoolingDevices, reconnecting", e);
                     connectToHal();
@@ -799,6 +803,8 @@
 
                     return Arrays.stream(halRet).filter(t -> t.type == type).collect(
                             Collectors.toList());
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    Slog.e(TAG, "Couldn't getTemperatureThresholds due to invalid status", e);
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Couldn't getTemperatureThresholds, reconnecting...", e);
                     connectToHal();
@@ -824,15 +830,30 @@
                     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;
                     }
+                    if (mInstance != null) {
+                        registerThermalChangedCallback();
+                    }
                 }
             }
         }
 
+        @VisibleForTesting
+        void registerThermalChangedCallback() {
+            try {
+                mInstance.registerThermalChangedCallback(mThermalChangedCallback);
+            } catch (IllegalArgumentException | IllegalStateException e) {
+                Slog.e(TAG, "Couldn't registerThermalChangedCallback due to invalid status",
+                        e);
+            } 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) {
diff --git a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerCalculator.java b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerCalculator.java
index ae4bad5..d14197f 100644
--- a/services/core/java/com/android/server/power/stats/AmbientDisplayPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/AmbientDisplayPowerCalculator.java
@@ -52,12 +52,12 @@
     @Override
     public void calculate(BatteryUsageStats.Builder builder, BatteryStats batteryStats,
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
-        final long measuredEnergyUC = batteryStats.getScreenDozeMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(measuredEnergyUC, query);
+        final long energyConsumerUC = batteryStats.getScreenDozeEnergyConsumptionUC();
+        final int powerModel = getPowerModel(energyConsumerUC, query);
         final long durationMs = calculateDuration(batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah = calculateTotalPower(powerModel, batteryStats, rawRealtimeUs,
-                measuredEnergyUC);
+                energyConsumerUC);
         builder.getAggregateBatteryConsumerBuilder(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY, durationMs)
@@ -72,7 +72,7 @@
     private double calculateTotalPower(@BatteryConsumer.PowerModel int powerModel,
             BatteryStats batteryStats, long rawRealtimeUs, long consumptionUC) {
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
                 return uCtoMah(consumptionUC);
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
             default:
diff --git a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
index 49ac559..60dbbdd 100644
--- a/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/power/stats/BatteryExternalStatsWorker.java
@@ -43,7 +43,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 
@@ -156,11 +156,13 @@
      * unless it is of {@link EnergyConsumer#type}=={@link EnergyConsumerType#OTHER}
      */
     @GuardedBy("mWorkerLock")
-    private @Nullable SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
+    @Nullable
+    private SparseArray<int[]> mEnergyConsumerTypeToIdMap = null;
 
-    /** Snapshot of measured energies, or null if no measured energies are supported. */
+    /** Snapshot of energy consumers, or null if no EnergyConsumers are supported. */
     @GuardedBy("mWorkerLock")
-    private @Nullable MeasuredEnergySnapshot mMeasuredEnergySnapshot = null;
+    @Nullable
+    private EnergyConsumerSnapshot mEnergyConsumerSnapshot = null;
 
     /**
      * Timestamp at which all external stats were last collected in
@@ -218,14 +220,14 @@
                 final SparseArray<EnergyConsumer> idToConsumer
                         = populateEnergyConsumerSubsystemMapsLocked();
                 if (idToConsumer != null) {
-                    mMeasuredEnergySnapshot = new MeasuredEnergySnapshot(idToConsumer);
+                    mEnergyConsumerSnapshot = new EnergyConsumerSnapshot(idToConsumer);
                     try {
                         final EnergyConsumerResult[] initialEcrs = getEnergyConsumptionData().get(
                                 EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
                         // According to spec, initialEcrs will include 0s for consumers that haven't
                         // used any energy yet, as long as they are supported; however,
                         // attributed uid energies will be absent if their energy is 0.
-                        mMeasuredEnergySnapshot.updateAndGetDelta(initialEcrs, voltageMv);
+                        mEnergyConsumerSnapshot.updateAndGetDelta(initialEcrs, voltageMv);
                     } catch (TimeoutException | InterruptedException e) {
                         Slog.w(TAG, "timeout or interrupt reading initial getEnergyConsumedAsync: "
                                 + e);
@@ -235,12 +237,12 @@
                                 + e.getCause());
                         // Continue running, later attempts to query may be successful.
                     }
-                    customBucketNames = mMeasuredEnergySnapshot.getOtherOrdinalNames();
+                    customBucketNames = mEnergyConsumerSnapshot.getOtherOrdinalNames();
                     supportedStdBuckets = getSupportedEnergyBuckets(idToConsumer);
                 }
             }
             synchronized (mStats) {
-                mStats.initMeasuredEnergyStatsLocked(supportedStdBuckets, customBucketNames);
+                mStats.initEnergyConsumerStatsLocked(supportedStdBuckets, customBucketNames);
             }
         }
     }
@@ -520,7 +522,8 @@
         CompletableFuture<ModemActivityInfo> modemFuture = CompletableFuture.completedFuture(null);
         boolean railUpdated = false;
 
-        CompletableFuture<EnergyConsumerResult[]> futureECRs = getMeasuredEnergyLocked(updateFlags);
+        CompletableFuture<EnergyConsumerResult[]> futureECRs =
+                getEnergyConsumersLocked(updateFlags);
 
         if ((updateFlags & BatteryStatsImpl.ExternalStatsSync.UPDATE_WIFI) != 0) {
             // We were asked to fetch WiFi data.
@@ -626,9 +629,9 @@
             Slog.w(TAG, "exception reading modem stats: " + e.getCause());
         }
 
-        final MeasuredEnergySnapshot.MeasuredEnergyDeltaData measuredEnergyDeltas;
-        if (mMeasuredEnergySnapshot == null || futureECRs == null) {
-            measuredEnergyDeltas = null;
+        final EnergyConsumerSnapshot.EnergyConsumerDeltaData energyConsumerDeltas;
+        if (mEnergyConsumerSnapshot == null || futureECRs == null) {
+            energyConsumerDeltas = null;
         } else {
             final int voltageMv;
             synchronized (mStats) {
@@ -639,7 +642,7 @@
             try {
                 ecrs = futureECRs.get(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
             } catch (TimeoutException | InterruptedException e) {
-                // TODO (b/180519623): Invalidate the MeasuredEnergy derived data until next reset.
+                // TODO (b/180519623): Invalidate the EnergyConsumer derived data until next reset.
                 Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync: " + e);
                 ecrs = null;
             } catch (ExecutionException e) {
@@ -647,7 +650,7 @@
                 ecrs = null;
             }
 
-            measuredEnergyDeltas = mMeasuredEnergySnapshot.updateAndGetDelta(ecrs, voltageMv);
+            energyConsumerDeltas = mEnergyConsumerSnapshot.updateAndGetDelta(ecrs, voltageMv);
         }
 
         final long elapsedRealtime = SystemClock.elapsedRealtime();
@@ -663,9 +666,10 @@
                     BatteryStats.HistoryItem.EVENT_COLLECT_EXTERNAL_STATS,
                     reason, 0);
 
-            if (measuredEnergyDeltas != null && !measuredEnergyDeltas.isEmpty()) {
-                mStats.recordMeasuredEnergyDetailsLocked(elapsedRealtime, uptime,
-                        mMeasuredEnergySnapshot.getMeasuredEnergyDetails(measuredEnergyDeltas));
+            if (energyConsumerDeltas != null && !energyConsumerDeltas.isEmpty()
+                    && mStats.isUsageHistoryEnabled()) {
+                mStats.recordEnergyConsumerDetailsLocked(elapsedRealtime, uptime,
+                        mEnergyConsumerSnapshot.getEnergyConsumerDetails(energyConsumerDeltas));
             }
 
             if ((updateFlags & UPDATE_CPU) != 0) {
@@ -675,10 +679,10 @@
                 }
 
                 final long[] cpuClusterChargeUC;
-                if (measuredEnergyDeltas == null) {
+                if (energyConsumerDeltas == null) {
                     cpuClusterChargeUC = null;
                 } else {
-                    cpuClusterChargeUC = measuredEnergyDeltas.cpuClusterChargeUC;
+                    cpuClusterChargeUC = energyConsumerDeltas.cpuClusterChargeUC;
                 }
                 mStats.updateCpuTimeLocked(onBattery, onBatteryScreenOff, cpuClusterChargeUC);
             }
@@ -692,37 +696,37 @@
                 mStats.updateRpmStatsLocked(elapsedRealtimeUs);
             }
 
-            // Inform mStats about each applicable measured energy (unless addressed elsewhere).
-            if (measuredEnergyDeltas != null) {
-                final long[] displayChargeUC = measuredEnergyDeltas.displayChargeUC;
+            // Inform mStats about each applicable energy consumers (unless addressed elsewhere).
+            if (energyConsumerDeltas != null) {
+                final long[] displayChargeUC = energyConsumerDeltas.displayChargeUC;
                 if (displayChargeUC != null && displayChargeUC.length > 0) {
                     // If updating, pass in what BatteryExternalStatsWorker thinks
                     // displayScreenStates is.
-                    mStats.updateDisplayMeasuredEnergyStatsLocked(displayChargeUC,
+                    mStats.updateDisplayEnergyConsumerStatsLocked(displayChargeUC,
                             displayScreenStates, elapsedRealtime);
                 }
 
-                final long gnssChargeUC = measuredEnergyDeltas.gnssChargeUC;
-                if (gnssChargeUC != MeasuredEnergySnapshot.UNAVAILABLE) {
-                    mStats.updateGnssMeasuredEnergyStatsLocked(gnssChargeUC, elapsedRealtime);
+                final long gnssChargeUC = energyConsumerDeltas.gnssChargeUC;
+                if (gnssChargeUC != EnergyConsumerSnapshot.UNAVAILABLE) {
+                    mStats.updateGnssEnergyConsumerStatsLocked(gnssChargeUC, elapsedRealtime);
                 }
             }
             // Inform mStats about each applicable custom energy bucket.
-            if (measuredEnergyDeltas != null
-                    && measuredEnergyDeltas.otherTotalChargeUC != null) {
+            if (energyConsumerDeltas != null
+                    && energyConsumerDeltas.otherTotalChargeUC != null) {
                 // Iterate over the custom (EnergyConsumerType.OTHER) ordinals.
-                for (int ord = 0; ord < measuredEnergyDeltas.otherTotalChargeUC.length; ord++) {
-                    long totalEnergy = measuredEnergyDeltas.otherTotalChargeUC[ord];
-                    SparseLongArray uidEnergies = measuredEnergyDeltas.otherUidChargesUC[ord];
-                    mStats.updateCustomMeasuredEnergyStatsLocked(ord, totalEnergy, uidEnergies);
+                for (int ord = 0; ord < energyConsumerDeltas.otherTotalChargeUC.length; ord++) {
+                    long totalEnergy = energyConsumerDeltas.otherTotalChargeUC[ord];
+                    SparseLongArray uidEnergies = energyConsumerDeltas.otherUidChargesUC[ord];
+                    mStats.updateCustomEnergyConsumerStatsLocked(ord, totalEnergy, uidEnergies);
                 }
             }
 
             if (bluetoothInfo != null) {
                 if (bluetoothInfo.isValid()) {
-                    final long btChargeUC = measuredEnergyDeltas != null
-                            ? measuredEnergyDeltas.bluetoothChargeUC
-                            : MeasuredEnergySnapshot.UNAVAILABLE;
+                    final long btChargeUC = energyConsumerDeltas != null
+                            ? energyConsumerDeltas.bluetoothChargeUC
+                            : EnergyConsumerSnapshot.UNAVAILABLE;
                     mStats.updateBluetoothStateLocked(bluetoothInfo,
                             btChargeUC, elapsedRealtime, uptime);
                 } else {
@@ -736,8 +740,9 @@
 
         if (wifiInfo != null) {
             if (wifiInfo.isValid()) {
-                final long wifiChargeUC = measuredEnergyDeltas != null ?
-                        measuredEnergyDeltas.wifiChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
+                final long wifiChargeUC =
+                        energyConsumerDeltas != null ? energyConsumerDeltas.wifiChargeUC
+                                : EnergyConsumerSnapshot.UNAVAILABLE;
                 final NetworkStatsManager networkStatsManager = mInjector.getSystemService(
                         NetworkStatsManager.class);
                 mStats.updateWifiState(extractDeltaLocked(wifiInfo),
@@ -748,8 +753,8 @@
         }
 
         if (modemInfo != null) {
-            final long mobileRadioChargeUC = measuredEnergyDeltas != null
-                    ? measuredEnergyDeltas.mobileRadioChargeUC : MeasuredEnergySnapshot.UNAVAILABLE;
+            final long mobileRadioChargeUC = energyConsumerDeltas != null
+                    ? energyConsumerDeltas.mobileRadioChargeUC : EnergyConsumerSnapshot.UNAVAILABLE;
             final NetworkStatsManager networkStatsManager = mInjector.getSystemService(
                     NetworkStatsManager.class);
             mStats.noteModemControllerActivity(modemInfo, mobileRadioChargeUC, elapsedRealtime,
@@ -864,7 +869,7 @@
 
     /**
      * Map the {@link EnergyConsumerType}s in the given energyArray to
-     * their corresponding {@link MeasuredEnergyStats.StandardPowerBucket}s.
+     * their corresponding {@link EnergyConsumerStats.StandardPowerBucket}s.
      * Does not include custom energy buckets (which are always, by definition, supported).
      *
      * @return array with true for index i if standard energy bucket i is supported.
@@ -874,30 +879,30 @@
         if (idToConsumer == null) {
             return null;
         }
-        final boolean[] buckets = new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+        final boolean[] buckets = new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
         final int size = idToConsumer.size();
         for (int idx = 0; idx < size; idx++) {
             final EnergyConsumer consumer = idToConsumer.valueAt(idx);
             switch (consumer.type) {
                 case EnergyConsumerType.BLUETOOTH:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_BLUETOOTH] = true;
                     break;
                 case EnergyConsumerType.CPU_CLUSTER:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
                     break;
                 case EnergyConsumerType.GNSS:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_GNSS] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_GNSS] = true;
                     break;
                 case EnergyConsumerType.MOBILE_RADIO:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO] = true;
                     break;
                 case EnergyConsumerType.DISPLAY:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON] = true;
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE] = true;
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_SCREEN_OTHER] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_SCREEN_ON] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_SCREEN_DOZE] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_SCREEN_OTHER] = true;
                     break;
                 case EnergyConsumerType.WIFI:
-                    buckets[MeasuredEnergyStats.POWER_BUCKET_WIFI] = true;
+                    buckets[EnergyConsumerStats.POWER_BUCKET_WIFI] = true;
                     break;
             }
         }
@@ -925,9 +930,9 @@
     @VisibleForTesting
     @GuardedBy("mWorkerLock")
     @Nullable
-    public CompletableFuture<EnergyConsumerResult[]> getMeasuredEnergyLocked(
+    public CompletableFuture<EnergyConsumerResult[]> getEnergyConsumersLocked(
             @ExternalUpdateFlag int flags) {
-        if (mMeasuredEnergySnapshot == null || mPowerStatsInternal == null) return null;
+        if (mEnergyConsumerSnapshot == null || mPowerStatsInternal == null) return null;
 
         if (flags == UPDATE_ALL) {
             // Gotta catch 'em all... including custom (non-specific) subsystems
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 855fcaf..c559436 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -123,8 +123,8 @@
 import com.android.internal.os.PowerProfile;
 import com.android.internal.os.RailStats;
 import com.android.internal.os.RpmStats;
-import com.android.internal.power.MeasuredEnergyStats;
-import com.android.internal.power.MeasuredEnergyStats.StandardPowerBucket;
+import com.android.internal.power.EnergyConsumerStats;
+import com.android.internal.power.EnergyConsumerStats.StandardPowerBucket;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.XmlUtils;
@@ -211,7 +211,7 @@
     public static final int RESET_REASON_CORRUPT_FILE = 1;
     public static final int RESET_REASON_ADB_COMMAND = 2;
     public static final int RESET_REASON_FULL_CHARGE = 3;
-    public static final int RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE = 4;
+    public static final int RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE = 4;
 
     protected Clock mClock;
 
@@ -278,10 +278,10 @@
     }
 
     private static final int[] SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS = {
-            MeasuredEnergyStats.POWER_BUCKET_CPU,
-            MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
-            MeasuredEnergyStats.POWER_BUCKET_WIFI,
-            MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
+            EnergyConsumerStats.POWER_BUCKET_CPU,
+            EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO,
+            EnergyConsumerStats.POWER_BUCKET_WIFI,
+            EnergyConsumerStats.POWER_BUCKET_BLUETOOTH,
     };
 
     // TimeInState counters need NUM_PROCESS_STATE states in order to accommodate
@@ -384,7 +384,7 @@
     }
 
     /** interface to update rail information for power monitor */
-    public interface MeasuredEnergyRetriever {
+    public interface EnergyStatsRetriever {
         /** Function to fill the map for the rail data stats
          * Used for power monitoring feature
          * @param railStats
@@ -426,7 +426,7 @@
         }
     };
 
-    public final MeasuredEnergyRetriever mMeasuredEnergyRetriever;
+    public final EnergyStatsRetriever mEnergyConsumerRetriever;
 
     /**
      * This handler is running on {@link BackgroundThread}.
@@ -839,7 +839,7 @@
         public StopwatchTimer[] screenBrightnessTimers =
                 new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
         /**
-         * Per display screen state the last time {@link #updateDisplayMeasuredEnergyStatsLocked}
+         * Per display screen state the last time {@link #updateDisplayEnergyConsumerStatsLocked}
          * was called.
          */
         public int screenStateAtLastEnergyMeasurement = Display.STATE_UNKNOWN;
@@ -1340,7 +1340,7 @@
 
     @GuardedBy("this")
     @VisibleForTesting
-    protected @Nullable MeasuredEnergyStats.Config mMeasuredEnergyStatsConfig;
+    protected @Nullable EnergyConsumerStats.Config mEnergyConsumerStatsConfig;
 
     /**
      * Accumulated global (generally, device-wide total) charge consumption of various consumers
@@ -1353,14 +1353,15 @@
      */
     @GuardedBy("this")
     @VisibleForTesting
-    protected @Nullable MeasuredEnergyStats mGlobalMeasuredEnergyStats;
-    /** Bluetooth Power calculator for attributing measured bluetooth charge consumption to uids */
+    @Nullable
+    protected EnergyConsumerStats mGlobalEnergyConsumerStats;
+    /** Bluetooth Power calculator for attributing bluetooth EnergyConsumer to uids */
     @Nullable BluetoothPowerCalculator mBluetoothPowerCalculator = null;
-    /** Cpu Power calculator for attributing measured cpu charge consumption to uids */
+    /** Cpu Power calculator for attributing cpu EnergyConsumer to uids */
     @Nullable CpuPowerCalculator mCpuPowerCalculator = null;
-    /** Mobile Radio Power calculator for attributing measured radio charge consumption to uids */
+    /** Mobile Radio Power calculator for attributing radio EnergyConsumer to uids */
     @Nullable MobileRadioPowerCalculator mMobileRadioPowerCalculator = null;
-    /** Wifi Power calculator for attributing measured wifi charge consumption to uids */
+    /** Wifi Power calculator for attributing wifi EnergyConsumer to uids */
     @Nullable WifiPowerCalculator mWifiPowerCalculator = null;
 
     /**
@@ -1635,7 +1636,7 @@
                     mConstants.MAX_HISTORY_BUFFER, mStepDetailsCalculator, mClock);
         }
         mPlatformIdleStateCallback = null;
-        mMeasuredEnergyRetriever = null;
+        mEnergyConsumerRetriever = null;
         mUserInfoProvider = null;
     }
 
@@ -5237,10 +5238,10 @@
             }
 
             if (shouldScheduleSync
-                    && mGlobalMeasuredEnergyStats != null
-                    && mGlobalMeasuredEnergyStats.isStandardBucketSupported(
-                    MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON)) {
-                // Display measured energy stats is available. Prepare to schedule an
+                    && mGlobalEnergyConsumerStats != null
+                    && mGlobalEnergyConsumerStats.isStandardBucketSupported(
+                    EnergyConsumerStats.POWER_BUCKET_SCREEN_ON)) {
+                // Display energy consumption stats is available. Prepare to schedule an
                 // external sync.
                 externalUpdateFlag |= ExternalStatsSync.UPDATE_DISPLAY;
             }
@@ -7373,44 +7374,44 @@
 
     @GuardedBy("this")
     @Override
-    public long getBluetoothMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
+    public long getBluetoothEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_BLUETOOTH);
     }
 
     @GuardedBy("this")
     @Override
-    public long getCpuMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
+    public long getCpuEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CPU);
     }
 
     @GuardedBy("this")
     @Override
-    public long getGnssMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
+    public long getGnssEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_GNSS);
     }
 
     @GuardedBy("this")
     @Override
-    public long getMobileRadioMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
+    public long getMobileRadioEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO);
     }
 
     @GuardedBy("this")
     @Override
-    public long getScreenOnMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
+    public long getScreenOnEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_SCREEN_ON);
     }
 
     @GuardedBy("this")
     @Override
-    public long getScreenDozeMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_DOZE);
+    public long getScreenDozeEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_SCREEN_DOZE);
     }
 
     @GuardedBy("this")
     @Override
-    public long getWifiMeasuredBatteryConsumptionUC() {
-        return getPowerBucketConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
+    public long getWifiEnergyConsumptionUC() {
+        return getPowerBucketConsumptionUC(EnergyConsumerStats.POWER_BUCKET_WIFI);
     }
 
     /**
@@ -7422,19 +7423,19 @@
      */
     @GuardedBy("this")
     private long getPowerBucketConsumptionUC(@StandardPowerBucket int bucket) {
-        if (mGlobalMeasuredEnergyStats == null) {
+        if (mGlobalEnergyConsumerStats == null) {
             return POWER_DATA_UNAVAILABLE;
         }
-        return mGlobalMeasuredEnergyStats.getAccumulatedStandardBucketCharge(bucket);
+        return mGlobalEnergyConsumerStats.getAccumulatedStandardBucketCharge(bucket);
     }
 
     @GuardedBy("this")
     @Override
-    public @Nullable long[] getCustomConsumerMeasuredBatteryConsumptionUC() {
-        if (mGlobalMeasuredEnergyStats == null) {
+    public @Nullable long[] getCustomEnergyConsumerBatteryConsumptionUC() {
+        if (mGlobalEnergyConsumerStats == null) {
             return null;
         }
-        return mGlobalMeasuredEnergyStats.getAccumulatedCustomBucketCharges();
+        return mGlobalEnergyConsumerStats.getAccumulatedCustomBucketCharges();
     }
 
     /**
@@ -7443,10 +7444,10 @@
     @GuardedBy("this")
     @Override
     public @NonNull String[] getCustomEnergyConsumerNames() {
-        if (mMeasuredEnergyStatsConfig == null) {
+        if (mEnergyConsumerStatsConfig == null) {
             return new String[0];
         }
-        final String[] names = mMeasuredEnergyStatsConfig.getCustomBucketNames();
+        final String[] names = mEnergyConsumerStatsConfig.getCustomBucketNames();
         for (int i = 0; i < names.length; i++) {
             if (TextUtils.isEmpty(names[i])) {
                 names[i] = "CUSTOM_" + BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i;
@@ -7456,14 +7457,14 @@
     }
 
     /**
-     * Adds measured energy delta to battery history.
+     * Adds energy consumer delta to battery history.
      */
     @GuardedBy("this")
-    public void recordMeasuredEnergyDetailsLocked(long elapsedRealtimeMs,
-            long uptimeMs, MeasuredEnergyDetails measuredEnergyDetails) {
+    public void recordEnergyConsumerDetailsLocked(long elapsedRealtimeMs,
+            long uptimeMs, EnergyConsumerDetails energyConsumerDetails) {
         if (isUsageHistoryEnabled()) {
-            mHistory.recordMeasuredEnergyDetails(elapsedRealtimeMs, uptimeMs,
-                    measuredEnergyDetails);
+            mHistory.recordEnergyConsumerDetails(elapsedRealtimeMs, uptimeMs,
+                    energyConsumerDetails);
         }
     }
 
@@ -7815,16 +7816,16 @@
         private final ArraySet<BinderCallStats> mBinderCallStats = new ArraySet<>();
 
         /**
-         * Measured charge consumption by this uid while on battery.
+         * EnergyConsumer consumption by this uid while on battery.
          * Its '<b>custom</b> power buckets' correspond to the
          * {@link android.hardware.power.stats.EnergyConsumer.ordinal}s of (custom) energy consumer
          * type {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
          *
          * Will be null if energy consumer data is completely unavailable (in which case
-         * {@link #mGlobalMeasuredEnergyStats} will also be null) or if the power usage by this uid
+         * {@link #mGlobalEnergyConsumerStats} will also be null) or if the power usage by this uid
          * is 0 for every bucket.
          */
-        private MeasuredEnergyStats mUidMeasuredEnergyStats;
+        private EnergyConsumerStats mUidEnergyConsumerStats;
 
         /**
          * Estimated total time spent by the system server handling requests from this uid.
@@ -7905,8 +7906,8 @@
             if (bluetoothControllerActivity != null) {
                 bluetoothControllerActivity.setState(batteryConsumerProcessState, elapsedTimeMs);
             }
-            final MeasuredEnergyStats energyStats =
-                    getOrCreateMeasuredEnergyStatsIfSupportedLocked();
+            final EnergyConsumerStats energyStats =
+                    getOrCreateEnergyConsumerStatsIfSupportedLocked();
             if (energyStats != null) {
                 energyStats.setState(batteryConsumerProcessState, elapsedTimeMs);
             }
@@ -8281,34 +8282,34 @@
         }
 
         @GuardedBy("mBsi")
-        private MeasuredEnergyStats getOrCreateMeasuredEnergyStatsLocked() {
-            if (mUidMeasuredEnergyStats == null) {
-                mUidMeasuredEnergyStats = new MeasuredEnergyStats(mBsi.mMeasuredEnergyStatsConfig);
+        private EnergyConsumerStats getOrCreateEnergyConsumerStatsLocked() {
+            if (mUidEnergyConsumerStats == null) {
+                mUidEnergyConsumerStats = new EnergyConsumerStats(mBsi.mEnergyConsumerStatsConfig);
             }
-            return mUidMeasuredEnergyStats;
+            return mUidEnergyConsumerStats;
         }
 
         @GuardedBy("mBsi")
-        private MeasuredEnergyStats getOrCreateMeasuredEnergyStatsIfSupportedLocked() {
-            if (mUidMeasuredEnergyStats == null && mBsi.mMeasuredEnergyStatsConfig != null) {
-                mUidMeasuredEnergyStats = new MeasuredEnergyStats(mBsi.mMeasuredEnergyStatsConfig);
+        private EnergyConsumerStats getOrCreateEnergyConsumerStatsIfSupportedLocked() {
+            if (mUidEnergyConsumerStats == null && mBsi.mEnergyConsumerStatsConfig != null) {
+                mUidEnergyConsumerStats = new EnergyConsumerStats(mBsi.mEnergyConsumerStatsConfig);
             }
-            return mUidMeasuredEnergyStats;
+            return mUidEnergyConsumerStats;
         }
 
         /** Adds the given charge to the given standard power bucket for this uid. */
         @GuardedBy("mBsi")
         private void addChargeToStandardBucketLocked(long chargeDeltaUC,
                 @StandardPowerBucket int powerBucket, long timestampMs) {
-            final MeasuredEnergyStats measuredEnergyStats =
-                    getOrCreateMeasuredEnergyStatsLocked();
-            measuredEnergyStats.updateStandardBucket(powerBucket, chargeDeltaUC, timestampMs);
+            final EnergyConsumerStats energyConsumerStats =
+                    getOrCreateEnergyConsumerStatsLocked();
+            energyConsumerStats.updateStandardBucket(powerBucket, chargeDeltaUC, timestampMs);
         }
 
         /** Adds the given charge to the given custom power bucket for this uid. */
         @GuardedBy("mBsi")
         private void addChargeToCustomBucketLocked(long chargeDeltaUC, int powerBucket) {
-            getOrCreateMeasuredEnergyStatsLocked().updateCustomBucket(powerBucket, chargeDeltaUC,
+            getOrCreateEnergyConsumerStatsLocked().updateCustomBucket(powerBucket, chargeDeltaUC,
                     mBsi.mClock.elapsedRealtime());
         }
 
@@ -8319,15 +8320,15 @@
          * @return consumption (in microcolombs) used by this uid for this power bucket
          */
         @GuardedBy("mBsi")
-        public long getMeasuredBatteryConsumptionUC(@StandardPowerBucket int bucket) {
-            if (mBsi.mGlobalMeasuredEnergyStats == null
-                    || !mBsi.mGlobalMeasuredEnergyStats.isStandardBucketSupported(bucket)) {
+        public long getEnergyConsumptionUC(@StandardPowerBucket int bucket) {
+            if (mBsi.mGlobalEnergyConsumerStats == null
+                    || !mBsi.mGlobalEnergyConsumerStats.isStandardBucketSupported(bucket)) {
                 return POWER_DATA_UNAVAILABLE;
             }
-            if (mUidMeasuredEnergyStats == null) {
+            if (mUidEnergyConsumerStats == null) {
                 return 0L; // It is supported, but was never filled, so it must be 0
             }
-            return mUidMeasuredEnergyStats.getAccumulatedStandardBucketCharge(bucket);
+            return mUidEnergyConsumerStats.getAccumulatedStandardBucketCharge(bucket);
         }
 
         /**
@@ -8335,94 +8336,94 @@
          * bucket and a process state, such as Uid.PROCESS_STATE_TOP.
          */
         @GuardedBy("mBsi")
-        public long getMeasuredBatteryConsumptionUC(@StandardPowerBucket int bucket,
+        public long getEnergyConsumptionUC(@StandardPowerBucket int bucket,
                 int processState) {
-            if (mBsi.mGlobalMeasuredEnergyStats == null
-                    || !mBsi.mGlobalMeasuredEnergyStats.isStandardBucketSupported(bucket)) {
+            if (mBsi.mGlobalEnergyConsumerStats == null
+                    || !mBsi.mGlobalEnergyConsumerStats.isStandardBucketSupported(bucket)) {
                 return POWER_DATA_UNAVAILABLE;
             }
-            if (mUidMeasuredEnergyStats == null) {
+            if (mUidEnergyConsumerStats == null) {
                 return 0L; // It is supported, but was never filled, so it must be 0
             }
-            return mUidMeasuredEnergyStats.getAccumulatedStandardBucketCharge(bucket, processState);
+            return mUidEnergyConsumerStats.getAccumulatedStandardBucketCharge(bucket, processState);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long[] getCustomConsumerMeasuredBatteryConsumptionUC() {
-            if (mBsi.mGlobalMeasuredEnergyStats == null) {
+        public long[] getCustomEnergyConsumerBatteryConsumptionUC() {
+            if (mBsi.mGlobalEnergyConsumerStats == null) {
                 return null;
             }
-            if (mUidMeasuredEnergyStats == null) {
+            if (mUidEnergyConsumerStats == null) {
                 // Custom buckets may exist. But all values for this uid are 0 so we report all 0s.
-                return new long[mBsi.mGlobalMeasuredEnergyStats.getNumberCustomPowerBuckets()];
+                return new long[mBsi.mGlobalEnergyConsumerStats.getNumberCustomPowerBuckets()];
             }
-            return mUidMeasuredEnergyStats.getAccumulatedCustomBucketCharges();
+            return mUidEnergyConsumerStats.getAccumulatedCustomBucketCharges();
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getBluetoothMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH);
+        public long getBluetoothEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_BLUETOOTH);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getBluetoothMeasuredBatteryConsumptionUC(
+        public long getBluetoothEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState) {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_BLUETOOTH,
                     processState);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getCpuMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU);
+        public long getCpuEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CPU);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getCpuMeasuredBatteryConsumptionUC(
+        public long getCpuEnergyConsumptionUC(
                 @BatteryConsumer.ProcessState int processState) {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_CPU,
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_CPU,
                     processState);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getGnssMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_GNSS);
+        public long getGnssEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_GNSS);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getMobileRadioMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO);
+        public long getMobileRadioEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getMobileRadioMeasuredBatteryConsumptionUC(int processState) {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
+        public long getMobileRadioEnergyConsumptionUC(int processState) {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO,
                     processState);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getScreenOnMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON);
+        public long getScreenOnEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_SCREEN_ON);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getWifiMeasuredBatteryConsumptionUC() {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI);
+        public long getWifiEnergyConsumptionUC() {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_WIFI);
         }
 
         @GuardedBy("mBsi")
         @Override
-        public long getWifiMeasuredBatteryConsumptionUC(int processState) {
-            return getMeasuredBatteryConsumptionUC(MeasuredEnergyStats.POWER_BUCKET_WIFI,
+        public long getWifiEnergyConsumptionUC(int processState) {
+            return getEnergyConsumptionUC(EnergyConsumerStats.POWER_BUCKET_WIFI,
                     processState);
         }
 
@@ -9222,10 +9223,10 @@
             resetIfNotNull(mBluetoothControllerActivity, false, realtimeUs);
             resetIfNotNull(mModemControllerActivity, false, realtimeUs);
 
-            if (resetReason == RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE) {
-                mUidMeasuredEnergyStats = null;
+            if (resetReason == RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE) {
+                mUidEnergyConsumerStats = null;
             } else {
-                MeasuredEnergyStats.resetIfNotNull(mUidMeasuredEnergyStats);
+                EnergyConsumerStats.resetIfNotNull(mUidEnergyConsumerStats);
             }
 
             resetIfNotNull(mUserCpuTime, false, realtimeUs);
@@ -10365,8 +10366,8 @@
                             elapsedRealtimeMs);
                 }
 
-                final MeasuredEnergyStats energyStats =
-                        getOrCreateMeasuredEnergyStatsIfSupportedLocked();
+                final EnergyConsumerStats energyStats =
+                        getOrCreateEnergyConsumerStatsIfSupportedLocked();
                 if (energyStats != null) {
                     energyStats.setState(batteryConsumerProcessState, elapsedRealtimeMs);
                 }
@@ -10701,12 +10702,12 @@
     }
 
     public BatteryStatsImpl(File systemDir, Handler handler, PlatformIdleStateCallback cb,
-            MeasuredEnergyRetriever energyStatsCb, UserInfoProvider userInfoProvider) {
+            EnergyStatsRetriever energyStatsCb, UserInfoProvider userInfoProvider) {
         this(Clock.SYSTEM_CLOCK, systemDir, handler, cb, energyStatsCb, userInfoProvider);
     }
 
     private BatteryStatsImpl(Clock clock, File systemDir, Handler handler,
-            PlatformIdleStateCallback cb, MeasuredEnergyRetriever energyStatsCb,
+            PlatformIdleStateCallback cb, EnergyStatsRetriever energyStatsCb,
             UserInfoProvider userInfoProvider) {
         init(clock);
 
@@ -10733,7 +10734,7 @@
         initDischarge(realtimeUs);
         updateDailyDeadlineLocked();
         mPlatformIdleStateCallback = cb;
-        mMeasuredEnergyRetriever = energyStatsCb;
+        mEnergyConsumerRetriever = energyStatsCb;
         mUserInfoProvider = userInfoProvider;
 
         // Notify statsd that the system is initially not in doze.
@@ -11477,7 +11478,7 @@
 
         mTmpRailStats.reset();
 
-        MeasuredEnergyStats.resetIfNotNull(mGlobalMeasuredEnergyStats);
+        EnergyConsumerStats.resetIfNotNull(mGlobalEnergyConsumerStats);
 
         resetIfNotNull(mBinderThreadCpuTimesUs, false, elapsedRealtimeUs);
 
@@ -11636,7 +11637,7 @@
             }
 
             final SparseDoubleArray uidEstimatedConsumptionMah =
-                    (mGlobalMeasuredEnergyStats != null
+                    (mGlobalEnergyConsumerStats != null
                             && mWifiPowerCalculator != null && consumedChargeUC > 0) ?
                             new SparseDoubleArray() : null;
             double totalEstimatedConsumptionMah = 0;
@@ -11948,10 +11949,10 @@
                 }
             }
 
-            // Update the MeasuredEnergyStats information.
+            // Update the EnergyConsumerStats information.
             if (uidEstimatedConsumptionMah != null) {
-                mGlobalMeasuredEnergyStats.updateStandardBucket(
-                        MeasuredEnergyStats.POWER_BUCKET_WIFI, consumedChargeUC);
+                mGlobalEnergyConsumerStats.updateStandardBucket(
+                        EnergyConsumerStats.POWER_BUCKET_WIFI, consumedChargeUC);
 
                 // Now calculate the consumption for each uid, according to its proportional usage.
                 if (!mHasWifiReporting) {
@@ -11961,7 +11962,7 @@
                     totalEstimatedConsumptionMah = mWifiPowerCalculator
                             .calcGlobalPowerWithoutControllerDataMah(globalTimeMs);
                 }
-                distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_WIFI,
+                distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_WIFI,
                         consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedConsumptionMah,
                         elapsedRealtimeMs);
             }
@@ -12003,9 +12004,9 @@
 
             final SparseDoubleArray uidEstimatedConsumptionMah;
             if (consumedChargeUC > 0 && mMobileRadioPowerCalculator != null
-                    && mGlobalMeasuredEnergyStats != null) {
-                mGlobalMeasuredEnergyStats.updateStandardBucket(
-                        MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
+                    && mGlobalEnergyConsumerStats != null) {
+                mGlobalEnergyConsumerStats.updateStandardBucket(
+                        EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO, consumedChargeUC);
                 uidEstimatedConsumptionMah = new SparseDoubleArray();
             } else {
                 uidEstimatedConsumptionMah = null;
@@ -12120,7 +12121,7 @@
                                 (totalAppRadioTimeUs * appPackets) / totalPackets;
                         u.noteMobileRadioActiveTimeLocked(appRadioTimeUs, elapsedRealtimeMs);
 
-                        // Distribute measured mobile radio charge consumption based on app radio
+                        // Distribute mobile radio charge consumption based on app radio
                         // active time
                         if (uidEstimatedConsumptionMah != null) {
                             uidEstimatedConsumptionMah.incrementValue(u.getUid(),
@@ -12164,7 +12165,7 @@
                 }
 
 
-                // Update the MeasuredEnergyStats information.
+                // Update the EnergyConsumerStats information.
                 if (uidEstimatedConsumptionMah != null) {
                     double totalEstimatedConsumptionMah = 0.0;
 
@@ -12197,7 +12198,7 @@
                     totalEstimatedConsumptionMah +=
                             mMobileRadioPowerCalculator.calcScanTimePowerMah(scanTimeMs);
 
-                    distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO,
+                    distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO,
                             consumedChargeUC, uidEstimatedConsumptionMah,
                             totalEstimatedConsumptionMah, elapsedRealtimeMs);
                 }
@@ -12407,7 +12408,7 @@
         }
 
         final SparseDoubleArray uidEstimatedConsumptionMah =
-                (mGlobalMeasuredEnergyStats != null
+                (mGlobalEnergyConsumerStats != null
                         && mBluetoothPowerCalculator != null && consumedChargeUC > 0) ?
                         new SparseDoubleArray() : null;
 
@@ -12590,15 +12591,15 @@
             mBluetoothActivity.getPowerCounter().addCountLocked((long) controllerMaMs);
         }
 
-        // Update the MeasuredEnergyStats information.
+        // Update the EnergyConsumerStats information.
         if (uidEstimatedConsumptionMah != null) {
-            mGlobalMeasuredEnergyStats.updateStandardBucket(
-                    MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH, consumedChargeUC);
+            mGlobalEnergyConsumerStats.updateStandardBucket(
+                    EnergyConsumerStats.POWER_BUCKET_BLUETOOTH, consumedChargeUC);
 
             double totalEstimatedMah
                     = mBluetoothPowerCalculator.calculatePowerMah(rxTimeMs, txTimeMs, idleTimeMs);
             totalEstimatedMah = Math.max(totalEstimatedMah, controllerMaMs / MILLISECONDS_IN_HOUR);
-            distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH,
+            distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_BLUETOOTH,
                     consumedChargeUC, uidEstimatedConsumptionMah, totalEstimatedMah,
                     elapsedRealtimeMs);
         }
@@ -12686,12 +12687,12 @@
      */
     @GuardedBy("this")
     @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToStandardBucketLocked
-    private void updateCpuMeasuredEnergyStatsLocked(@NonNull long[] clusterChargeUC,
+    private void updateCpuEnergyConsumerStatsLocked(@NonNull long[] clusterChargeUC,
             @NonNull CpuDeltaPowerAccumulator accumulator) {
         if (DEBUG_ENERGY) {
             Slog.d(TAG, "Updating cpu cluster stats: " + Arrays.toString(clusterChargeUC));
         }
-        if (mGlobalMeasuredEnergyStats == null) {
+        if (mGlobalEnergyConsumerStats == null) {
             return;
         }
 
@@ -12704,10 +12705,10 @@
 
         final long timestampMs = mClock.elapsedRealtime();
 
-        mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_CPU,
+        mGlobalEnergyConsumerStats.updateStandardBucket(EnergyConsumerStats.POWER_BUCKET_CPU,
                 totalCpuChargeUC, timestampMs);
 
-        // Calculate the measured microcoulombs/calculated milliamp-hour charge ratio for each
+        // Calculate the microcoulombs/milliamp-hour charge ratio for each
         // cluster to normalize  each uid's estimated power usage against actual power usage for
         // a given cluster.
         final double[] clusterChargeRatio = new double[numClusters];
@@ -12730,14 +12731,14 @@
             final Uid uid = accumulator.perUidCpuClusterChargesMah.keyAt(i);
             final double[] uidClusterChargesMah = accumulator.perUidCpuClusterChargesMah.valueAt(i);
 
-            // Iterate each cpu cluster and sum the proportional measured cpu cluster charge to
+            // Iterate each cpu cluster and sum the proportional cpu cluster charge to
             // get the total cpu charge consumed by a uid.
             long uidCpuChargeUC = 0;
             for (int cluster = 0; cluster < numClusters; cluster++) {
                 final double uidClusterChargeMah = uidClusterChargesMah[cluster];
 
-                // Proportionally allocate the measured cpu cluster charge to a uid using the
-                // measured charge/calculated charge ratio. Add 0.5 to round the proportional
+                // Proportionally allocate the cpu cluster charge to a uid using the
+                // cluster charge/charge ratio. Add 0.5 to round the proportional
                 // charge double to the nearest long value.
                 final long uidClusterChargeUC =
                         (long) (uidClusterChargeMah * clusterChargeRatio[cluster]
@@ -12747,14 +12748,13 @@
             }
 
             if (uidCpuChargeUC < 0) {
-                Slog.wtf(TAG,
-                        "Unexpected proportional measured charge (" + uidCpuChargeUC + ") for uid "
-                                + uid.mUid);
+                Slog.wtf(TAG, "Unexpected proportional EnergyConsumer charge "
+                        + "(" + uidCpuChargeUC + ") for uid " + uid.mUid);
                 continue;
             }
 
             uid.addChargeToStandardBucketLocked(uidCpuChargeUC,
-                    MeasuredEnergyStats.POWER_BUCKET_CPU, timestampMs);
+                    EnergyConsumerStats.POWER_BUCKET_CPU, timestampMs);
         }
     }
 
@@ -12770,10 +12770,10 @@
      * @param screenStates each screen state at the time this data collection was scheduled
      */
     @GuardedBy("this")
-    public void updateDisplayMeasuredEnergyStatsLocked(long[] chargesUC, int[] screenStates,
+    public void updateDisplayEnergyConsumerStatsLocked(long[] chargesUC, int[] screenStates,
             long elapsedRealtimeMs) {
         if (DEBUG_ENERGY) Slog.d(TAG, "Updating display stats: " + Arrays.toString(chargesUC));
-        if (mGlobalMeasuredEnergyStats == null) {
+        if (mGlobalEnergyConsumerStats == null) {
             return;
         }
 
@@ -12825,9 +12825,9 @@
             }
 
             final @StandardPowerBucket int powerBucket =
-                    MeasuredEnergyStats.getDisplayPowerBucket(oldScreenStates[i]);
-            mGlobalMeasuredEnergyStats.updateStandardBucket(powerBucket, chargeUC);
-            if (powerBucket == MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON) {
+                    EnergyConsumerStats.getDisplayPowerBucket(oldScreenStates[i]);
+            mGlobalEnergyConsumerStats.updateStandardBucket(powerBucket, chargeUC);
+            if (powerBucket == EnergyConsumerStats.POWER_BUCKET_SCREEN_ON) {
                 totalScreenOnChargeUC += chargeUC;
             }
         }
@@ -12853,7 +12853,7 @@
             if (fgTimeUs == 0) continue;
             fgTimeUsArray.put(uid.getUid(), (double) fgTimeUs);
         }
-        distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON,
+        distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_SCREEN_ON,
                 totalScreenOnChargeUC, fgTimeUsArray, 0, elapsedRealtimeMs);
     }
 
@@ -12863,9 +12863,9 @@
      * @param chargeUC amount of charge (microcoulombs) used by GNSS since this was last called.
      */
     @GuardedBy("this")
-    public void updateGnssMeasuredEnergyStatsLocked(long chargeUC, long elapsedRealtimeMs) {
+    public void updateGnssEnergyConsumerStatsLocked(long chargeUC, long elapsedRealtimeMs) {
         if (DEBUG_ENERGY) Slog.d(TAG, "Updating gnss stats: " + chargeUC);
-        if (mGlobalMeasuredEnergyStats == null) {
+        if (mGlobalEnergyConsumerStats == null) {
             return;
         }
 
@@ -12884,7 +12884,7 @@
             return;
         }
 
-        mGlobalMeasuredEnergyStats.updateStandardBucket(MeasuredEnergyStats.POWER_BUCKET_GNSS,
+        mGlobalEnergyConsumerStats.updateStandardBucket(EnergyConsumerStats.POWER_BUCKET_GNSS,
                 chargeUC);
 
         // Collect the per uid time since mark so that we can normalize power.
@@ -12897,7 +12897,7 @@
             if (gnssTimeUs == 0) continue;
             gnssTimeUsArray.put(uid.getUid(), (double) gnssTimeUs);
         }
-        distributeEnergyToUidsLocked(MeasuredEnergyStats.POWER_BUCKET_GNSS, chargeUC,
+        distributeEnergyToUidsLocked(EnergyConsumerStats.POWER_BUCKET_GNSS, chargeUC,
                 gnssTimeUsArray, 0, elapsedRealtimeMs);
     }
 
@@ -12911,18 +12911,18 @@
      */
     @GuardedBy("this")
     @SuppressWarnings("GuardedBy") // errorprone false positive on u.addChargeToCustomBucketLocked
-    public void updateCustomMeasuredEnergyStatsLocked(int customPowerBucket,
+    public void updateCustomEnergyConsumerStatsLocked(int customPowerBucket,
             long totalChargeUC, @Nullable SparseLongArray uidCharges) {
         if (DEBUG_ENERGY) {
-            Slog.d(TAG, "Updating attributed measured charge stats for custom bucket "
+            Slog.d(TAG, "Updating attributed EnergyConsumer stats for custom bucket "
                     + customPowerBucket
                     + " with total charge " + totalChargeUC
-                    + " and uid charges " + String.valueOf(uidCharges));
+                    + " and uid charges " + uidCharges);
         }
-        if (mGlobalMeasuredEnergyStats == null) return;
+        if (mGlobalEnergyConsumerStats == null) return;
         if (!mOnBatteryInternal || mIgnoreNextExternalStats || totalChargeUC <= 0) return;
 
-        mGlobalMeasuredEnergyStats.updateCustomBucket(customPowerBucket, totalChargeUC,
+        mGlobalEnergyConsumerStats.updateCustomBucket(customPowerBucket, totalChargeUC,
                 mClock.elapsedRealtime());
 
         if (uidCharges == null) return;
@@ -12942,8 +12942,9 @@
                 // Recently removed uids (especially common for isolated uids) can reach this path
                 // and are ignored.
                 if (!Process.isIsolated(uidInt)) {
-                    Slog.w(TAG, "Received measured charge " + totalChargeUC + " for custom bucket "
-                            + customPowerBucket + " for non-existent uid " + uidInt);
+                    Slog.w(TAG, "Received EnergyConsumer charge " + totalChargeUC
+                            + " for custom bucket " + customPowerBucket + " for non-existent uid "
+                            + uidInt);
                 }
             }
         }
@@ -12993,10 +12994,10 @@
      * Read and record Rail Energy data.
      */
     public void updateRailStatsLocked() {
-        if (mMeasuredEnergyRetriever == null || !mTmpRailStats.isRailStatsAvailable()) {
+        if (mEnergyConsumerRetriever == null || !mTmpRailStats.isRailStatsAvailable()) {
             return;
         }
-        mMeasuredEnergyRetriever.fillRailDataStats(mTmpRailStats);
+        mEnergyConsumerRetriever.fillRailDataStats(mTmpRailStats);
     }
 
     /** Informs that external stats data has been completely flushed. */
@@ -13166,7 +13167,7 @@
      */
     @GuardedBy("this")
     public void updateCpuTimeLocked(boolean onBattery, boolean onBatteryScreenOff,
-            long[] measuredCpuClusterChargeUC) {
+            long[] cpuClusterChargeUC) {
         if (mPowerProfile == null) {
             return;
         }
@@ -13222,16 +13223,16 @@
                 ? null : new SparseLongArray();
 
         final CpuDeltaPowerAccumulator powerAccumulator;
-        if (mGlobalMeasuredEnergyStats != null
-                && mGlobalMeasuredEnergyStats.isStandardBucketSupported(
-                MeasuredEnergyStats.POWER_BUCKET_CPU) && mCpuPowerCalculator != null) {
-            if (measuredCpuClusterChargeUC == null) {
+        if (mGlobalEnergyConsumerStats != null
+                && mGlobalEnergyConsumerStats.isStandardBucketSupported(
+                EnergyConsumerStats.POWER_BUCKET_CPU) && mCpuPowerCalculator != null) {
+            if (cpuClusterChargeUC == null) {
                 Slog.wtf(TAG,
-                        "POWER_BUCKET_CPU supported but no measured Cpu Cluster charge reported "
-                                + "on updateCpuTimeLocked!");
+                        "POWER_BUCKET_CPU supported but no EnergyConsumer Cpu Cluster charge "
+                                + "reported on updateCpuTimeLocked!");
                 powerAccumulator = null;
             } else {
-                // Cpu Measured Energy is supported, create an object to accumulate the estimated
+                // Cpu EnergyConsumer is supported, create an object to accumulate the estimated
                 // charge consumption since the last cpu update
                 final int numClusters = mPowerProfile.getNumCpuClusters();
                 powerAccumulator = new CpuDeltaPowerAccumulator(mCpuPowerCalculator, numClusters);
@@ -13250,7 +13251,7 @@
                 powerAccumulator);
         mNumAllUidCpuTimeReads += 2;
         if (mConstants.TRACK_CPU_ACTIVE_CLUSTER_TIME) {
-            // Cpu Active times do not get any info ony how to attribute measured Cpu Cluster
+            // Cpu Active times do not get any info ony how to attribute Cpu Cluster
             // charge, so not need to provide the powerAccumulator
             readKernelUidCpuActiveTimesLocked(onBattery);
             readKernelUidCpuClusterTimesLocked(onBattery, powerAccumulator);
@@ -13260,7 +13261,7 @@
         updateSystemServerThreadStats();
 
         if (powerAccumulator != null) {
-            updateCpuMeasuredEnergyStatsLocked(measuredCpuClusterChargeUC, powerAccumulator);
+            updateCpuEnergyConsumerStatsLocked(cpuClusterChargeUC, powerAccumulator);
         }
     }
 
@@ -14691,7 +14692,7 @@
     }
 
     @GuardedBy("this")
-    private boolean isUsageHistoryEnabled() {
+    boolean isUsageHistoryEnabled() {
         return mConstants.RECORD_USAGE_HISTORY;
     }
 
@@ -14702,7 +14703,7 @@
     }
 
     /**
-     * Initialize the measured charge stats data structures.
+     * Initialize the EnergyConsumer stats data structures.
      *
      * @param supportedStandardBuckets boolean array indicating which {@link StandardPowerBucket}s
      *                                 are currently supported. If null, none are supported
@@ -14710,7 +14711,7 @@
      * @param customBucketNames        names of custom (OTHER) EnergyConsumers on this device
      */
     @GuardedBy("this")
-    public void initMeasuredEnergyStatsLocked(@Nullable boolean[] supportedStandardBuckets,
+    public void initEnergyConsumerStatsLocked(@Nullable boolean[] supportedStandardBuckets,
             String[] customBucketNames) {
         final int numDisplays = mPerDisplayBatteryStats.length;
         for (int i = 0; i < numDisplays; i++) {
@@ -14720,44 +14721,44 @@
 
         final boolean compatibleConfig;
         if (supportedStandardBuckets != null) {
-            final MeasuredEnergyStats.Config config = new MeasuredEnergyStats.Config(
+            final EnergyConsumerStats.Config config = new EnergyConsumerStats.Config(
                     supportedStandardBuckets, customBucketNames,
                     SUPPORTED_PER_PROCESS_STATE_STANDARD_ENERGY_BUCKETS,
                     getBatteryConsumerProcessStateNames());
 
-            if (mMeasuredEnergyStatsConfig == null) {
+            if (mEnergyConsumerStatsConfig == null) {
                 compatibleConfig = true;
             } else {
-                compatibleConfig = mMeasuredEnergyStatsConfig.isCompatible(config);
+                compatibleConfig = mEnergyConsumerStatsConfig.isCompatible(config);
             }
 
-            mMeasuredEnergyStatsConfig = config;
-            mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(config);
+            mEnergyConsumerStatsConfig = config;
+            mGlobalEnergyConsumerStats = new EnergyConsumerStats(config);
 
-            if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_BLUETOOTH]) {
+            if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_BLUETOOTH]) {
                 mBluetoothPowerCalculator = new BluetoothPowerCalculator(mPowerProfile);
             }
-            if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU]) {
+            if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_CPU]) {
                 mCpuPowerCalculator = new CpuPowerCalculator(mPowerProfile);
             }
-            if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_MOBILE_RADIO]) {
+            if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_MOBILE_RADIO]) {
                 mMobileRadioPowerCalculator = new MobileRadioPowerCalculator(mPowerProfile);
             }
-            if (supportedStandardBuckets[MeasuredEnergyStats.POWER_BUCKET_WIFI]) {
+            if (supportedStandardBuckets[EnergyConsumerStats.POWER_BUCKET_WIFI]) {
                 mWifiPowerCalculator = new WifiPowerCalculator(mPowerProfile);
             }
         } else {
-            compatibleConfig = (mMeasuredEnergyStatsConfig == null);
-            // Measured energy no longer supported, wipe out the existing data.
-            mMeasuredEnergyStatsConfig = null;
-            mGlobalMeasuredEnergyStats = null;
+            compatibleConfig = (mEnergyConsumerStatsConfig == null);
+            // EnergyConsumer no longer supported, wipe out the existing data.
+            mEnergyConsumerStatsConfig = null;
+            mGlobalEnergyConsumerStats = null;
         }
 
         if (!compatibleConfig) {
             // Supported power buckets changed since last boot.
             // Existing data is no longer reliable.
             resetAllStatsLocked(SystemClock.uptimeMillis(), SystemClock.elapsedRealtime(),
-                    RESET_REASON_MEASURED_ENERGY_BUCKETS_CHANGE);
+                    RESET_REASON_ENERGY_CONSUMER_BUCKETS_CHANGE);
         }
     }
 
@@ -15059,31 +15060,31 @@
     }
 
     /**
-     * Dump measured charge stats
+     * Dump EnergyConsumer stats
      */
     @GuardedBy("this")
-    public void dumpMeasuredEnergyStatsLocked(PrintWriter pw) {
-        pw.printf("On battery measured charge stats (microcoulombs) \n");
-        if (mGlobalMeasuredEnergyStats == null) {
+    public void dumpEnergyConsumerStatsLocked(PrintWriter pw) {
+        pw.printf("On-battery energy consumer stats (microcoulombs) \n");
+        if (mGlobalEnergyConsumerStats == null) {
             pw.printf("    Not supported on this device.\n");
             return;
         }
 
-        dumpMeasuredEnergyStatsLocked(pw, "global usage", mGlobalMeasuredEnergyStats);
+        dumpEnergyConsumerStatsLocked(pw, "global usage", mGlobalEnergyConsumerStats);
 
         int size = mUidStats.size();
         for (int i = 0; i < size; i++) {
             final int u = mUidStats.keyAt(i);
             final Uid uid = mUidStats.get(u);
             final String name = "uid " + uid.mUid;
-            dumpMeasuredEnergyStatsLocked(pw, name, uid.mUidMeasuredEnergyStats);
+            dumpEnergyConsumerStatsLocked(pw, name, uid.mUidEnergyConsumerStats);
         }
     }
 
-    /** Dump measured charge stats for the given uid */
+    /** Dump EnergyConsumer stats for the given uid */
     @GuardedBy("this")
-    private void dumpMeasuredEnergyStatsLocked(PrintWriter pw, String name,
-            MeasuredEnergyStats stats) {
+    private void dumpEnergyConsumerStatsLocked(PrintWriter pw, String name,
+            EnergyConsumerStats stats) {
         if (stats == null) return;
         final IndentingPrintWriter iPw = new IndentingPrintWriter(pw, "    ");
         iPw.increaseIndent();
@@ -15292,17 +15293,17 @@
         mNextMaxDailyDeadlineMs = in.readLong();
         mBatteryTimeToFullSeconds = in.readLong();
 
-        final MeasuredEnergyStats.Config config = MeasuredEnergyStats.Config.createFromParcel(in);
-        final MeasuredEnergyStats measuredEnergyStats =
-                MeasuredEnergyStats.createAndReadSummaryFromParcel(mMeasuredEnergyStatsConfig, in);
+        final EnergyConsumerStats.Config config = EnergyConsumerStats.Config.createFromParcel(in);
+        final EnergyConsumerStats energyConsumerStats =
+                EnergyConsumerStats.createAndReadSummaryFromParcel(mEnergyConsumerStatsConfig, in);
         if (config != null && Arrays.equals(config.getStateNames(),
                 getBatteryConsumerProcessStateNames())) {
             /**
              * WARNING: Supported buckets may have changed across boots. Bucket mismatch is handled
-             *          later when {@link #initMeasuredEnergyStatsLocked} is called.
+             *          later when {@link #initEnergyConsumerStatsLocked} is called.
              */
-            mMeasuredEnergyStatsConfig = config;
-            mGlobalMeasuredEnergyStats = measuredEnergyStats;
+            mEnergyConsumerStatsConfig = config;
+            mGlobalEnergyConsumerStats = energyConsumerStats;
         }
 
         mStartCount++;
@@ -15631,8 +15632,8 @@
                 u.mWifiRadioApWakeupCount = null;
             }
 
-            u.mUidMeasuredEnergyStats = MeasuredEnergyStats.createAndReadSummaryFromParcel(
-                    mMeasuredEnergyStatsConfig, in);
+            u.mUidEnergyConsumerStats = EnergyConsumerStats.createAndReadSummaryFromParcel(
+                    mEnergyConsumerStatsConfig, in);
 
             int NW = in.readInt();
             if (NW > (MAX_WAKELOCKS_PER_UID+1)) {
@@ -15806,8 +15807,8 @@
         out.writeLong(mNextMaxDailyDeadlineMs);
         out.writeLong(mBatteryTimeToFullSeconds);
 
-        MeasuredEnergyStats.Config.writeToParcel(mMeasuredEnergyStatsConfig, out);
-        MeasuredEnergyStats.writeSummaryToParcel(mGlobalMeasuredEnergyStats, out);
+        EnergyConsumerStats.Config.writeToParcel(mEnergyConsumerStatsConfig, out);
+        EnergyConsumerStats.writeSummaryToParcel(mGlobalEnergyConsumerStats, out);
 
         mScreenOnTimer.writeSummaryFromParcelLocked(out, nowRealtime);
         mScreenDozeTimer.writeSummaryFromParcelLocked(out, nowRealtime);
@@ -16157,7 +16158,7 @@
                 out.writeInt(0);
             }
 
-            MeasuredEnergyStats.writeSummaryToParcel(u.mUidMeasuredEnergyStats, out);
+            EnergyConsumerStats.writeSummaryToParcel(u.mUidEnergyConsumerStats, out);
 
             final ArrayMap<String, Uid.Wakelock> wakeStats = u.mWakelockStats.getMap();
             int NW = wakeStats.size();
@@ -16393,7 +16394,7 @@
         dumpCpuPowerBracketsLocked(pw);
 
         pw.println();
-        dumpMeasuredEnergyStatsLocked(pw);
+        dumpEnergyConsumerStatsLocked(pw);
     }
 
     @Override
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 7da9197..ebd4aec 100644
--- a/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
+++ b/services/core/java/com/android/server/power/stats/BatteryUsageStatsProvider.java
@@ -91,7 +91,7 @@
                 mPowerCalculators.add(new ScreenPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new AmbientDisplayPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new IdlePowerCalculator(mPowerProfile));
-                mPowerCalculators.add(new CustomMeasuredPowerCalculator(mPowerProfile));
+                mPowerCalculators.add(new CustomEnergyConsumerPowerCalculator(mPowerProfile));
                 mPowerCalculators.add(new UserPowerCalculator());
 
                 // It is important that SystemServicePowerCalculator be applied last,
diff --git a/services/core/java/com/android/server/power/stats/BluetoothPowerCalculator.java b/services/core/java/com/android/server/power/stats/BluetoothPowerCalculator.java
index 0d4d4f2..2d96fcc 100644
--- a/services/core/java/com/android/server/power/stats/BluetoothPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/BluetoothPowerCalculator.java
@@ -93,11 +93,11 @@
             calculateApp(app, powerAndDuration, query);
         }
 
-        final long measuredChargeUC = batteryStats.getBluetoothMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(measuredChargeUC, query);
+        final long consumedEnergyUC = batteryStats.getBluetoothEnergyConsumptionUC();
+        final int powerModel = getPowerModel(consumedEnergyUC, query);
         final ControllerActivityCounter activityCounter =
                 batteryStats.getBluetoothControllerActivity();
-        calculatePowerAndDuration(null, powerModel, measuredChargeUC,
+        calculatePowerAndDuration(null, powerModel, consumedEnergyUC,
                 activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
 
         // Subtract what the apps used, but clamp to 0.
@@ -127,12 +127,12 @@
 
     private void calculateApp(UidBatteryConsumer.Builder app, PowerAndDuration powerAndDuration,
             BatteryUsageStatsQuery query) {
-        final long measuredChargeUC =
-                app.getBatteryStatsUid().getBluetoothMeasuredBatteryConsumptionUC();
-        final int powerModel = getPowerModel(measuredChargeUC, query);
+        final long consumedEnergyUC =
+                app.getBatteryStatsUid().getBluetoothEnergyConsumptionUC();
+        final int powerModel = getPowerModel(consumedEnergyUC, query);
         final ControllerActivityCounter activityCounter =
                 app.getBatteryStatsUid().getBluetoothControllerActivity();
-        calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, measuredChargeUC,
+        calculatePowerAndDuration(app.getBatteryStatsUid(), powerModel, consumedEnergyUC,
                 activityCounter, query.shouldForceUsePowerProfileModel(), powerAndDuration);
 
         app.setUsageDurationMillis(
@@ -163,7 +163,7 @@
     /** Returns bluetooth power usage based on the best data available. */
     private void calculatePowerAndDuration(@Nullable BatteryStats.Uid uid,
             @BatteryConsumer.PowerModel int powerModel,
-            long measuredChargeUC, ControllerActivityCounter counter, boolean ignoreReportedPower,
+            long consumedEnergyUC, ControllerActivityCounter counter, boolean ignoreReportedPower,
             PowerAndDuration powerAndDuration) {
         if (counter == null) {
             powerAndDuration.durationMs = 0;
@@ -183,8 +183,8 @@
 
         powerAndDuration.durationMs = idleTimeMs + rxTimeMs + txTimeMs;
 
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            powerAndDuration.powerMah = uCtoMah(measuredChargeUC);
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            powerAndDuration.powerMah = uCtoMah(consumedEnergyUC);
             if (uid != null && powerAndDuration.keys != null) {
                 for (int i = 0; i < powerAndDuration.keys.length; i++) {
                     BatteryConsumer.Key key = powerAndDuration.keys[i];
@@ -195,7 +195,7 @@
                     }
 
                     powerAndDuration.powerPerKeyMah[i] =
-                            uCtoMah(uid.getBluetoothMeasuredBatteryConsumptionUC(processState));
+                            uCtoMah(uid.getBluetoothEnergyConsumptionUC(processState));
                 }
             }
         } else {
diff --git a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
index 8b38a35..5074838 100644
--- a/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CpuPowerCalculator.java
@@ -124,7 +124,7 @@
             }
         }
 
-        final long consumptionUC = batteryStats.getCpuMeasuredBatteryConsumptionUC();
+        final long consumptionUC = batteryStats.getCpuEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
 
         builder.getAggregateBatteryConsumerBuilder(
@@ -133,13 +133,13 @@
         builder.getAggregateBatteryConsumerBuilder(
                 BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE)
                 .setConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU,
-                        powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY
+                        powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION
                                 ? uCtoMah(consumptionUC) : totalPowerMah, powerModel);
     }
 
     private void calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             BatteryUsageStatsQuery query, Result result, BatteryConsumer.Key[] keys) {
-        final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC();
+        final long consumptionUC = u.getCpuEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
         calculatePowerAndDuration(u, powerModel, consumptionUC, BatteryStats.STATS_SINCE_CHARGED,
                 result);
@@ -150,8 +150,8 @@
 
         if (query.isProcessStateDataNeeded() && keys != null) {
             switch (powerModel) {
-                case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
-                    calculateMeasuredPowerPerProcessState(app, u, keys);
+                case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
+                    calculateEnergyConsumptionPerProcessState(app, u, keys);
                     break;
                 case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
                     calculateModeledPowerPerProcessState(app, u, keys, result);
@@ -160,7 +160,7 @@
         }
     }
 
-    private void calculateMeasuredPowerPerProcessState(UidBatteryConsumer.Builder app,
+    private void calculateEnergyConsumptionPerProcessState(UidBatteryConsumer.Builder app,
             BatteryStats.Uid u, BatteryConsumer.Key[] keys) {
         for (BatteryConsumer.Key key : keys) {
             // The key for PROCESS_STATE_UNSPECIFIED aka PROCESS_STATE_ANY has already been
@@ -171,10 +171,10 @@
                 continue;
             }
 
-            final long consumptionUC = u.getCpuMeasuredBatteryConsumptionUC(key.processState);
+            final long consumptionUC = u.getCpuEnergyConsumptionUC(key.processState);
             if (consumptionUC != 0) {
                 app.setConsumedPower(key, uCtoMah(consumptionUC),
-                        BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                        BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
             }
         }
     }
@@ -226,7 +226,7 @@
 
         final double powerMah;
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
                 powerMah = uCtoMah(consumptionUC);
                 break;
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
diff --git a/services/core/java/com/android/server/power/stats/CustomMeasuredPowerCalculator.java b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
similarity index 69%
rename from services/core/java/com/android/server/power/stats/CustomMeasuredPowerCalculator.java
rename to services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
index a0f9d61..5b7467e 100644
--- a/services/core/java/com/android/server/power/stats/CustomMeasuredPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/CustomEnergyConsumerPowerCalculator.java
@@ -32,10 +32,10 @@
  * Calculates the amount of power consumed by custom energy consumers (i.e. consumers of type
  * {@link android.hardware.power.stats.EnergyConsumerType#OTHER}).
  */
-public class CustomMeasuredPowerCalculator extends PowerCalculator {
-    private static final String TAG = "CustomMeasuredPowerCalc";
+public class CustomEnergyConsumerPowerCalculator extends PowerCalculator {
+    private static final String TAG = "CustomEnergyCsmrPowerCalc";
 
-    public CustomMeasuredPowerCalculator(PowerProfile powerProfile) {
+    public CustomEnergyConsumerPowerCalculator(PowerProfile powerProfile) {
     }
 
     @Override
@@ -55,16 +55,16 @@
             totalAppPowerMah = calculateApp(app, app.getBatteryStatsUid(), totalAppPowerMah);
         }
 
-        final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
-                batteryStats.getCustomConsumerMeasuredBatteryConsumptionUC());
-        if (customMeasuredPowerMah != null) {
+        final double[] customEnergyConsumerPowerMah = uCtoMah(
+                batteryStats.getCustomEnergyConsumerBatteryConsumptionUC());
+        if (customEnergyConsumerPowerMah != null) {
             final AggregateBatteryConsumer.Builder deviceBatteryConsumerBuilder =
                     builder.getAggregateBatteryConsumerBuilder(
                             BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
-            for (int i = 0; i < customMeasuredPowerMah.length; i++) {
+            for (int i = 0; i < customEnergyConsumerPowerMah.length; i++) {
                 deviceBatteryConsumerBuilder.setConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
-                        customMeasuredPowerMah[i]);
+                        customEnergyConsumerPowerMah[i]);
             }
         }
         if (totalAppPowerMah != null) {
@@ -82,38 +82,39 @@
     private double[] calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             double[] totalPowerMah) {
         double[] newTotalPowerMah = null;
-        final double[] customMeasuredPowerMah = calculateMeasuredEnergiesMah(
-                u.getCustomConsumerMeasuredBatteryConsumptionUC());
-        if (customMeasuredPowerMah != null) {
+        final double[] customEnergyConsumerPowerMah =
+                uCtoMah(u.getCustomEnergyConsumerBatteryConsumptionUC());
+        if (customEnergyConsumerPowerMah != null) {
             if (totalPowerMah == null) {
-                newTotalPowerMah = new double[customMeasuredPowerMah.length];
-            } else if (totalPowerMah.length != customMeasuredPowerMah.length) {
+                newTotalPowerMah = new double[customEnergyConsumerPowerMah.length];
+            } else if (totalPowerMah.length != customEnergyConsumerPowerMah.length) {
                 Slog.wtf(TAG, "Number of custom energy components is not the same for all apps: "
-                        + totalPowerMah.length + ", " + customMeasuredPowerMah.length);
-                newTotalPowerMah = Arrays.copyOf(totalPowerMah, customMeasuredPowerMah.length);
+                        + totalPowerMah.length + ", " + customEnergyConsumerPowerMah.length);
+                newTotalPowerMah = Arrays.copyOf(totalPowerMah,
+                        customEnergyConsumerPowerMah.length);
             } else {
                 newTotalPowerMah = totalPowerMah;
             }
-            for (int i = 0; i < customMeasuredPowerMah.length; i++) {
+            for (int i = 0; i < customEnergyConsumerPowerMah.length; i++) {
                 app.setConsumedPowerForCustomComponent(
                         BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID + i,
-                        customMeasuredPowerMah[i]);
+                        customEnergyConsumerPowerMah[i]);
                 if (!app.isVirtualUid()) {
-                    newTotalPowerMah[i] += customMeasuredPowerMah[i];
+                    newTotalPowerMah[i] += customEnergyConsumerPowerMah[i];
                 }
             }
         }
         return newTotalPowerMah;
     }
 
-    private double[] calculateMeasuredEnergiesMah(long[] measuredChargeUC) {
-        if (measuredChargeUC == null) {
+    private double[] uCtoMah(long[] chargeUC) {
+        if (chargeUC == null) {
             return null;
         }
-        final double[] measuredEnergiesMah = new double[measuredChargeUC.length];
-        for (int i = 0; i < measuredChargeUC.length; i++) {
-            measuredEnergiesMah[i] = uCtoMah(measuredChargeUC[i]);
+        final double[] mah = new double[chargeUC.length];
+        for (int i = 0; i < chargeUC.length; i++) {
+            mah[i] = uCtoMah(chargeUC[i]);
         }
-        return measuredEnergiesMah;
+        return mah;
     }
 }
diff --git a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
similarity index 89%
rename from services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java
rename to services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
index c8b4e36..18595ca 100644
--- a/services/core/java/com/android/server/power/stats/MeasuredEnergySnapshot.java
+++ b/services/core/java/com/android/server/power/stats/EnergyConsumerSnapshot.java
@@ -23,7 +23,7 @@
 import android.hardware.power.stats.EnergyConsumerAttribution;
 import android.hardware.power.stats.EnergyConsumerResult;
 import android.hardware.power.stats.EnergyConsumerType;
-import android.os.BatteryStats.MeasuredEnergyDetails;
+import android.os.BatteryStats.EnergyConsumerDetails;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
@@ -34,8 +34,8 @@
 /**
  * Keeps snapshots of data from previously pulled EnergyConsumerResults.
  */
-public class MeasuredEnergySnapshot {
-    private static final String TAG = "MeasuredEnergySnapshot";
+public class EnergyConsumerSnapshot {
+    private static final String TAG = "EnergyConsumerSnapshot";
 
     private static final int MILLIVOLTS_PER_VOLT = 1000;
 
@@ -63,13 +63,13 @@
      *
      * If an id is not present yet, it is treated as uninitialized (energy {@link #UNAVAILABLE}).
      */
-    private final SparseLongArray mMeasuredEnergySnapshots;
+    private final SparseLongArray mEnergyConsumerSnapshots;
 
     /**
      * Voltage snapshots, mapping {@link EnergyConsumer#id} to voltage (mV) from the last time
      * each {@link EnergyConsumer} was updated.
      *
-     * see {@link mMeasuredEnergySnapshots}.
+     * see {@link #mEnergyConsumerSnapshots}.
      */
     private final SparseIntArray mVoltageSnapshots;
 
@@ -85,15 +85,15 @@
      */
     private final SparseArray<SparseLongArray> mAttributionSnapshots;
 
-    private MeasuredEnergyDetails mMeasuredEnergyDetails;
+    private EnergyConsumerDetails mEnergyConsumerDetails;
 
     /**
      * Constructor that initializes to the given id->EnergyConsumer map, indicating which consumers
      * exist and what their details are.
      */
-    MeasuredEnergySnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
+    EnergyConsumerSnapshot(@NonNull SparseArray<EnergyConsumer> idToConsumerMap) {
         mEnergyConsumers = idToConsumerMap;
-        mMeasuredEnergySnapshots = new SparseLongArray(mEnergyConsumers.size());
+        mEnergyConsumerSnapshots = new SparseLongArray(mEnergyConsumers.size());
         mVoltageSnapshots = new SparseIntArray(mEnergyConsumers.size());
 
         mNumCpuClusterOrdinals = calculateNumOrdinals(EnergyConsumerType.CPU_CLUSTER,
@@ -103,8 +103,8 @@
         mAttributionSnapshots = new SparseArray<>(mNumOtherOrdinals);
     }
 
-    /** Class for returning the relevant data calculated from the measured energy delta */
-    static class MeasuredEnergyDeltaData {
+    /** Class for returning the relevant data calculated from the energy consumer delta */
+    static class EnergyConsumerDeltaData {
         /** The chargeUC for {@link EnergyConsumerType#BLUETOOTH}. */
         public long bluetoothChargeUC = UNAVAILABLE;
 
@@ -153,14 +153,14 @@
     }
 
     /**
-     * Update with the some freshly measured energies and return the difference (delta)
+     * Update with the freshly retrieved energy consumers and return the difference (delta)
      * between the previously stored values and the passed-in values.
      *
      * @param ecrs EnergyConsumerResults for some (possibly not all) {@link EnergyConsumer}s.
      *             Consumers that are not present are ignored (they are *not* treated as 0).
      * @param voltageMV current voltage.
      *
-     * @return a MeasuredEnergyDeltaData, containing maps from the updated consumers to
+     * @return an EnergyConsumerDeltaData, containing maps from the updated consumers to
      *         their corresponding charge deltas.
      *         Fields with no interesting data (consumers not present in ecrs or with no energy
      *         difference) will generally be left as their default values.
@@ -168,19 +168,19 @@
      *         length {@link #getOtherOrdinalNames().length}.
      *         Returns null, if ecrs is null or empty.
      */
-    public @Nullable MeasuredEnergyDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs,
-            int voltageMV) {
+    @Nullable
+    public EnergyConsumerDeltaData updateAndGetDelta(EnergyConsumerResult[] ecrs, int voltageMV) {
         if (ecrs == null || ecrs.length == 0) {
             return null;
         }
         if (voltageMV <= 0) {
             Slog.wtf(TAG, "Unexpected battery voltage (" + voltageMV
-                    + " mV) when taking measured energy snapshot");
+                    + " mV) when taking energy consumer snapshot");
             // TODO (b/181685156): consider adding the nominal voltage to power profile and
             //  falling back to it if measured voltage is unavailable.
             return null;
         }
-        final MeasuredEnergyDeltaData output = new MeasuredEnergyDeltaData();
+        final EnergyConsumerDeltaData output = new EnergyConsumerDeltaData();
 
         for (final EnergyConsumerResult ecr : ecrs) {
             // Extract the new energy data for the current consumer.
@@ -198,9 +198,9 @@
             final int ordinal = consumer.ordinal;
 
             // Look up, and update, the old energy and voltage information about this consumer.
-            final long oldEnergyUJ = mMeasuredEnergySnapshots.get(consumerId, UNAVAILABLE);
+            final long oldEnergyUJ = mEnergyConsumerSnapshots.get(consumerId, UNAVAILABLE);
             final int oldVoltageMV = mVoltageSnapshots.get(consumerId);
-            mMeasuredEnergySnapshots.put(consumerId, newEnergyUJ);
+            mEnergyConsumerSnapshots.put(consumerId, newEnergyUJ);
             mVoltageSnapshots.put(consumerId, voltageMV);
 
             final int avgVoltageMV = (oldVoltageMV + voltageMV + 1) / 2;
@@ -275,8 +275,8 @@
 
     /**
      * For a consumer of type {@link EnergyConsumerType#OTHER}, updates
-     * {@link #mAttributionSnapshots} with freshly measured energies (per uid) and returns the
-     * charge consumed (in microcoulombs) between the previously stored values and the passed-in
+     * {@link #mAttributionSnapshots} with freshly retrieved energy consumers (per uid) and returns
+     * the charge consumed (in microcoulombs) between the previously stored values and the passed-in
      * values.
      *
      * @param consumerInfo a consumer of type {@link EnergyConsumerType#OTHER}.
@@ -284,7 +284,7 @@
      *                        Any uid not present is treated as having energy 0.
      *                        If null or empty, all uids are treated as having energy 0.
      * @param avgVoltageMV The average voltage since the last snapshot.
-     * @return A map (in the sense of {@link MeasuredEnergyDeltaData#otherUidChargesUC} for this
+     * @return A map (in the sense of {@link EnergyConsumerDeltaData#otherUidChargesUC} for this
      *         consumer) of uid -> chargeDelta, with all uids that have a non-zero chargeDelta.
      *         Returns null if no delta available to calculate.
      */
@@ -342,7 +342,7 @@
 
     /** Dump debug data. */
     public void dump(PrintWriter pw) {
-        pw.println("Measured energy snapshot");
+        pw.println("Energy consumer snapshot");
         pw.println("List of EnergyConsumers:");
         for (int i = 0; i < mEnergyConsumers.size(); i++) {
             final int id = mEnergyConsumers.keyAt(i);
@@ -351,9 +351,9 @@
                     consumer.id, consumer.ordinal, consumer.type, consumer.name));
         }
         pw.println("Map of consumerIds to energy (in microjoules):");
-        for (int i = 0; i < mMeasuredEnergySnapshots.size(); i++) {
-            final int id = mMeasuredEnergySnapshots.keyAt(i);
-            final long energyUJ = mMeasuredEnergySnapshots.valueAt(i);
+        for (int i = 0; i < mEnergyConsumerSnapshots.size(); i++) {
+            final int id = mEnergyConsumerSnapshots.keyAt(i);
+            final long energyUJ = mEnergyConsumerSnapshots.valueAt(i);
             final long voltageMV = mVoltageSnapshots.valueAt(i);
             pw.println(String.format("    Consumer %d has energy %d uJ at %d mV", id, energyUJ,
                     voltageMV));
@@ -418,19 +418,19 @@
     }
 
     /**
-     * Converts the MeasuredEnergyDeltaData object to MeasuredEnergyDetails, which can
+     * Converts the EnergyConsumerDeltaData object to EnergyConsumerDetails, which can
      * be saved in battery history.
      */
-    MeasuredEnergyDetails getMeasuredEnergyDetails(
-            MeasuredEnergySnapshot.MeasuredEnergyDeltaData delta) {
-        if (mMeasuredEnergyDetails == null) {
-            mMeasuredEnergyDetails = createMeasuredEnergyDetails();
+    EnergyConsumerDetails getEnergyConsumerDetails(
+            EnergyConsumerDeltaData delta) {
+        if (mEnergyConsumerDetails == null) {
+            mEnergyConsumerDetails = createEnergyConsumerDetails();
         }
 
-        final long[] chargeUC = mMeasuredEnergyDetails.chargeUC;
-        for (int i = 0; i < mMeasuredEnergyDetails.consumers.length; i++) {
-            MeasuredEnergyDetails.EnergyConsumer energyConsumer =
-                    mMeasuredEnergyDetails.consumers[i];
+        final long[] chargeUC = mEnergyConsumerDetails.chargeUC;
+        for (int i = 0; i < mEnergyConsumerDetails.consumers.length; i++) {
+            EnergyConsumerDetails.EnergyConsumer energyConsumer =
+                    mEnergyConsumerDetails.consumers[i];
             switch (energyConsumer.type) {
                 case EnergyConsumerType.BLUETOOTH:
                     chargeUC[i] = delta.bluetoothChargeUC;
@@ -470,17 +470,17 @@
                     break;
             }
         }
-        return mMeasuredEnergyDetails;
+        return mEnergyConsumerDetails;
     }
 
-    private MeasuredEnergyDetails createMeasuredEnergyDetails() {
-        MeasuredEnergyDetails details = new MeasuredEnergyDetails();
+    private EnergyConsumerDetails createEnergyConsumerDetails() {
+        EnergyConsumerDetails details = new EnergyConsumerDetails();
         details.consumers =
-                new MeasuredEnergyDetails.EnergyConsumer[mEnergyConsumers.size()];
+                new EnergyConsumerDetails.EnergyConsumer[mEnergyConsumers.size()];
         for (int i = 0; i < mEnergyConsumers.size(); i++) {
             EnergyConsumer energyConsumer = mEnergyConsumers.valueAt(i);
-            MeasuredEnergyDetails.EnergyConsumer consumer =
-                    new MeasuredEnergyDetails.EnergyConsumer();
+            EnergyConsumerDetails.EnergyConsumer consumer =
+                    new EnergyConsumerDetails.EnergyConsumer();
             consumer.type = energyConsumer.type;
             consumer.ordinal = energyConsumer.ordinal;
             switch (consumer.type) {
diff --git a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
index f9356ab..ab22e3e 100644
--- a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java
@@ -58,7 +58,7 @@
         for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
             final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
             final long consumptionUC =
-                    app.getBatteryStatsUid().getGnssMeasuredBatteryConsumptionUC();
+                    app.getBatteryStatsUid().getGnssEnergyConsumptionUC();
             final int powerModel = getPowerModel(consumptionUC, query);
             final double powerMah = calculateApp(app, app.getBatteryStatsUid(), powerModel,
                     rawRealtimeUs, averageGnssPowerMa, consumptionUC);
@@ -67,10 +67,10 @@
             }
         }
 
-        final long consumptionUC = batteryStats.getGnssMeasuredBatteryConsumptionUC();
+        final long consumptionUC = batteryStats.getGnssEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
         double powerMah;
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
             powerMah = uCtoMah(consumptionUC);
         } else {
             powerMah = appsPowerMah;
@@ -85,12 +85,12 @@
 
     private double calculateApp(UidBatteryConsumer.Builder app, BatteryStats.Uid u,
             @BatteryConsumer.PowerModel int powerModel, long rawRealtimeUs,
-            double averageGnssPowerMa, long measuredChargeUC) {
+            double averageGnssPowerMa, long consumedEnergyUC) {
         final long durationMs = computeDuration(u, rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
         final double powerMah;
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
-                powerMah = uCtoMah(measuredChargeUC);
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
+                powerMah = uCtoMah(consumedEnergyUC);
                 break;
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
             default:
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 2c7aea9..4608e9a 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerCalculator.java
@@ -128,20 +128,20 @@
 
         PowerAndDuration total = new PowerAndDuration();
 
-        final long totalConsumptionUC = batteryStats.getMobileRadioMeasuredBatteryConsumptionUC();
+        final long totalConsumptionUC = batteryStats.getMobileRadioEnergyConsumptionUC();
         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.
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            // EnergyConsumer is available, don't bother calculating power.
             totalActivePowerMah = Double.NaN;
             apps = null;
             appDurationsMs = null;
         } else {
             totalActivePowerMah = calculateActiveModemPowerMah(batteryStats, rawRealtimeUs);
-            apps = new ArrayList();
+            apps = new ArrayList<>();
             appDurationsMs = new LongArrayQueue();
         }
 
@@ -169,9 +169,9 @@
             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 (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+                // EnergyConsumer is available, populate the consumed power now.
+                final long appConsumptionUC = uid.getMobileRadioEnergyConsumptionUC();
                 if (appConsumptionUC != BatteryStats.POWER_DATA_UNAVAILABLE) {
                     final double appConsumptionMah = uCtoMah(appConsumptionUC);
                     if (!app.isVirtualUid()) {
@@ -188,7 +188,7 @@
                                 continue;
                             }
                             final long consumptionInStateUc =
-                                    uid.getMobileRadioMeasuredBatteryConsumptionUC(processState);
+                                    uid.getMobileRadioEnergyConsumptionUC(processState);
                             final double powerInStateMah = uCtoMah(consumptionInStateUc);
                             app.setConsumedPower(key, powerInStateMah, powerModel);
                         }
@@ -207,7 +207,7 @@
             totalActiveDurationMs = total.totalAppDurationMs;
         }
 
-        if (powerModel != BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+        if (powerModel != BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
             // Need to smear the calculated total active power across the apps based on app
             // active durations.
             final int appSize = apps.size();
@@ -259,7 +259,7 @@
         total.remainingDurationMs = totalActiveDurationMs - total.totalAppDurationMs;
 
         // Calculate remaining power consumption.
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
             total.remainingPowerMah = uCtoMah(totalConsumptionUC) - total.totalAppPowerMah;
             if (total.remainingPowerMah < 0) total.remainingPowerMah = 0;
         } else {
diff --git a/services/core/java/com/android/server/power/stats/PowerCalculator.java b/services/core/java/com/android/server/power/stats/PowerCalculator.java
index 1652f73..fe68d8f 100644
--- a/services/core/java/com/android/server/power/stats/PowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/PowerCalculator.java
@@ -79,17 +79,17 @@
     }
 
     protected static @BatteryConsumer.PowerModel int getPowerModel(
-            long measuredEnergyUC, @NonNull BatteryUsageStatsQuery query) {
-        if (measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE
+            long consumedEnergyUC, @NonNull BatteryUsageStatsQuery query) {
+        if (consumedEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE
                 && !query.shouldForceUsePowerProfileModel()) {
-            return BatteryConsumer.POWER_MODEL_MEASURED_ENERGY;
+            return BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION;
         }
         return BatteryConsumer.POWER_MODEL_POWER_PROFILE;
     }
 
-    protected static @BatteryConsumer.PowerModel int getPowerModel(long measuredEnergyUC) {
-        return measuredEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE
-                ? BatteryConsumer.POWER_MODEL_MEASURED_ENERGY
+    protected static @BatteryConsumer.PowerModel int getPowerModel(long consumedEnergyUC) {
+        return consumedEnergyUC != BatteryStats.POWER_DATA_UNAVAILABLE
+                ? BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION
                 : BatteryConsumer.POWER_MODEL_POWER_PROFILE;
     }
 
diff --git a/services/core/java/com/android/server/power/stats/ScreenPowerCalculator.java b/services/core/java/com/android/server/power/stats/ScreenPowerCalculator.java
index ddcbb04..25dc9b1 100644
--- a/services/core/java/com/android/server/power/stats/ScreenPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/ScreenPowerCalculator.java
@@ -73,7 +73,7 @@
             long rawRealtimeUs, long rawUptimeUs, BatteryUsageStatsQuery query) {
         final PowerAndDuration totalPowerAndDuration = new PowerAndDuration();
 
-        final long consumptionUC = batteryStats.getScreenOnMeasuredBatteryConsumptionUC();
+        final long consumptionUC = batteryStats.getScreenOnEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
         calculateTotalDurationAndPower(totalPowerAndDuration, powerModel, batteryStats,
                 rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED, consumptionUC);
@@ -87,12 +87,12 @@
         final SparseArray<UidBatteryConsumer.Builder> uidBatteryConsumerBuilders =
                 builder.getUidBatteryConsumerBuilders();
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
                 final PowerAndDuration appPowerAndDuration = new PowerAndDuration();
                 for (int i = uidBatteryConsumerBuilders.size() - 1; i >= 0; i--) {
                     final UidBatteryConsumer.Builder app = uidBatteryConsumerBuilders.valueAt(i);
-                    calculateAppUsingMeasuredEnergy(appPowerAndDuration, app.getBatteryStatsUid(),
-                            rawRealtimeUs);
+                    calculateAppUsingEnergyConsumption(appPowerAndDuration,
+                            app.getBatteryStatsUid(), rawRealtimeUs);
                     app.setUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN,
                                     appPowerAndDuration.durationMs)
                             .setConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN,
@@ -134,7 +134,7 @@
                 statsType);
 
         switch (powerModel) {
-            case BatteryConsumer.POWER_MODEL_MEASURED_ENERGY:
+            case BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION:
                 totalPowerAndDuration.powerMah = uCtoMah(consumptionUC);
                 break;
             case BatteryConsumer.POWER_MODEL_POWER_PROFILE:
@@ -144,11 +144,11 @@
         }
     }
 
-    private void calculateAppUsingMeasuredEnergy(PowerAndDuration appPowerAndDuration,
+    private void calculateAppUsingEnergyConsumption(PowerAndDuration appPowerAndDuration,
             BatteryStats.Uid u, long rawRealtimeUs) {
         appPowerAndDuration.durationMs = getProcessForegroundTimeMs(u, rawRealtimeUs);
 
-        final long chargeUC = u.getScreenOnMeasuredBatteryConsumptionUC();
+        final long chargeUC = u.getScreenOnEnergyConsumptionUC();
         if (chargeUC < 0) {
             Slog.wtf(TAG, "Screen energy not supported, so calculateApp shouldn't de called");
             appPowerAndDuration.powerMah = 0;
diff --git a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
index cd4b586..aa17d4fb 100644
--- a/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/SystemServicePowerCalculator.java
@@ -73,12 +73,12 @@
             return;
         }
 
-        final long consumptionUC = systemUid.getCpuMeasuredBatteryConsumptionUC();
+        final long consumptionUC = systemUid.getCpuEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
 
         double systemServicePowerMah;
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
-            systemServicePowerMah = calculatePowerUsingMeasuredConsumption(batteryStats,
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
+            systemServicePowerMah = calculatePowerUsingEnergyConsumption(batteryStats,
                     systemUid, consumptionUC);
         } else {
             systemServicePowerMah = calculatePowerUsingPowerProfile(batteryStats);
@@ -120,11 +120,11 @@
                         systemServicePowerMah);
     }
 
-    private double calculatePowerUsingMeasuredConsumption(BatteryStats batteryStats,
+    private double calculatePowerUsingEnergyConsumption(BatteryStats batteryStats,
             BatteryStats.Uid systemUid, long consumptionUC) {
         // Use the PowerProfile based model to estimate the ratio between the power consumed
         // while handling incoming binder calls and the entire System UID power consumption.
-        // Apply that ratio to the _measured_ system UID power consumption to get a more
+        // Apply that ratio to the _EnergyConsumer_ system UID power consumption to get a more
         // accurate estimate of the power consumed by incoming binder calls.
         final double systemServiceModeledPowerMah = calculatePowerUsingPowerProfile(batteryStats);
         final double systemUidModeledPowerMah = mCpuPowerCalculator.calculateUidModeledPowerMah(
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerCalculator.java b/services/core/java/com/android/server/power/stats/WifiPowerCalculator.java
index 4f5c8a5..4d055b4 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerCalculator.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerCalculator.java
@@ -107,7 +107,7 @@
             }
 
             final long consumptionUC =
-                    app.getBatteryStatsUid().getWifiMeasuredBatteryConsumptionUC();
+                    app.getBatteryStatsUid().getWifiEnergyConsumptionUC();
             final int powerModel = getPowerModel(consumptionUC, query);
 
             calculateApp(powerDurationAndTraffic, app.getBatteryStatsUid(), powerModel,
@@ -138,7 +138,7 @@
             }
         }
 
-        final long consumptionUC = batteryStats.getWifiMeasuredBatteryConsumptionUC();
+        final long consumptionUC = batteryStats.getWifiEnergyConsumptionUC();
         final int powerModel = getPowerModel(consumptionUC, query);
         calculateRemaining(powerDurationAndTraffic, powerModel, batteryStats, rawRealtimeUs,
                 BatteryStats.STATS_SINCE_CHARGED, batteryStats.hasWifiActivityReporting(),
@@ -214,7 +214,7 @@
                                             idleTimeCounter.getCountForProcessState(processState));
                         } else {
                             powerDurationAndTraffic.powerPerKeyMah[i] =
-                                    uCtoMah(u.getWifiMeasuredBatteryConsumptionUC(processState));
+                                    uCtoMah(u.getWifiEnergyConsumptionUC(processState));
                         }
                     }
                 }
@@ -264,7 +264,7 @@
         long totalDurationMs;
         double totalPowerMah = 0;
 
-        if (powerModel == BatteryConsumer.POWER_MODEL_MEASURED_ENERGY) {
+        if (powerModel == BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION) {
             totalPowerMah = uCtoMah(consumptionUC);
         }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index b95d372..863ee75 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -44,6 +44,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.PlaybackParams;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.AitInfo;
@@ -2520,7 +2521,30 @@
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
-            };
+            }
+        }
+
+        @Override
+        public void notifyAdBuffer(
+                IBinder sessionToken, AdBuffer buffer, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
+                    userId, "notifyAdBuffer");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyAdBuffer(buffer);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in notifyAdBuffer", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
         }
 
         @Override
@@ -3624,7 +3648,7 @@
         }
 
         @Override
-        public void onBroadcastInfoResponse (BroadcastInfoResponse response) {
+        public void onBroadcastInfoResponse(BroadcastInfoResponse response) {
             synchronized (mLock) {
                 if (DEBUG) {
                     Slog.d(TAG, "onBroadcastInfoResponse()");
@@ -3641,7 +3665,7 @@
         }
 
         @Override
-        public void onAdResponse (AdResponse response) {
+        public void onAdResponse(AdResponse response) {
             synchronized (mLock) {
                 if (DEBUG) {
                     Slog.d(TAG, "onAdResponse()");
@@ -3656,6 +3680,24 @@
                 }
             }
         }
+
+        @Override
+        public void onAdBufferConsumed(AdBuffer buffer) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slog.d(TAG, "onAdBufferConsumed()");
+                }
+                if (mSessionState.session == null || mSessionState.client == null) {
+                    return;
+                }
+                try {
+                    mSessionState.client.onAdBufferConsumed(
+                            buffer, mSessionState.seq);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "error in onAdBufferConsumed", e);
+                }
+            }
+        }
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 2c8fd96..f173f7a 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -29,6 +29,7 @@
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Rect;
+import android.media.tv.AdBuffer;
 import android.media.tv.AdRequest;
 import android.media.tv.AdResponse;
 import android.media.tv.BroadcastInfoRequest;
@@ -1513,6 +1514,29 @@
         }
 
         @Override
+        public void notifyAdBufferConsumed(
+                IBinder sessionToken, AdBuffer buffer, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+            final int resolvedUserId = resolveCallingUserId(callingPid, callingUid, userId,
+                    "notifyAdBufferConsumed");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        SessionState sessionState = getSessionStateLocked(sessionToken, callingUid,
+                                resolvedUserId);
+                        getSessionLocked(sessionState).notifyAdBufferConsumed(buffer);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slogf.e(TAG, "error in notifyAdBufferConsumed", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void registerCallback(final ITvInteractiveAppManagerCallback callback, int userId) {
             int callingPid = Binder.getCallingPid();
             int callingUid = Binder.getCallingUid();
@@ -2378,6 +2402,23 @@
             }
         }
 
+        @Override
+        public void onAdBuffer(AdBuffer buffer) {
+            synchronized (mLock) {
+                if (DEBUG) {
+                    Slogf.d(TAG, "onAdBuffer(buffer=" + buffer + ")");
+                }
+                if (mSessionState.mSession == null || mSessionState.mClient == null) {
+                    return;
+                }
+                try {
+                    mSessionState.mClient.onAdBuffer(buffer, mSessionState.mSeq);
+                } catch (RemoteException e) {
+                    Slogf.e(TAG, "error in onAdBuffer", e);
+                }
+            }
+        }
+
         @GuardedBy("mLock")
         private boolean addSessionTokenToClientStateLocked(ITvInteractiveAppSession session) {
             try {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 4fef2a8..e2ab216 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -141,7 +141,7 @@
     private final SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
             new SparseArray<>();
     private SparseArray<IBinder> mFocusedWindow = new SparseArray<>();
-    private int mFocusedDisplay = -1;
+    private int mFocusedDisplay = Display.INVALID_DISPLAY;
     private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
     // Set to true if initializing window population complete.
     private boolean mAllObserversInitialized = true;
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index f683d0a..a16e659 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -100,7 +100,6 @@
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.protolog.ProtoLogGroup;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
 import com.android.server.pm.KnownPackages;
@@ -191,6 +190,15 @@
     }
 
     @Override
+    public void activityRefreshed(IBinder token) {
+        final long origId = Binder.clearCallingIdentity();
+        synchronized (mGlobalLock) {
+            ActivityRecord.activityRefreshedLocked(token);
+        }
+        Binder.restoreCallingIdentity(origId);
+    }
+
+    @Override
     public void activityTopResumedStateLost() {
         final long origId = Binder.clearCallingIdentity();
         synchronized (mGlobalLock) {
@@ -482,46 +490,13 @@
                         finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
                 if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                         || (finishWithRootActivity && r == rootR)) {
-                    ActivityRecord topActivity =
-                            r.getTask().getTopNonFinishingActivity();
-                    boolean passesAsmChecks = topActivity != null
-                            && topActivity.getUid() == r.getUid();
-                    if (!passesAsmChecks) {
-                        Slog.i(TAG, "Finishing task from background. r: " + r);
-                        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
-                                /* caller_uid */
-                                r.getUid(),
-                                /* caller_activity_class_name */
-                                r.info.name,
-                                /* target_task_top_activity_uid */
-                                topActivity == null ? -1 : topActivity.getUid(),
-                                /* target_task_top_activity_class_name */
-                                topActivity == null ? null : topActivity.info.name,
-                                /* target_task_is_different */
-                                false,
-                                /* target_activity_uid */
-                                -1,
-                                /* target_activity_class_name */
-                                null,
-                                /* target_intent_action */
-                                null,
-                                /* target_intent_flags */
-                                0,
-                                /* action */
-                                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
-                                /* version */
-                                1,
-                                /* multi_window */
-                                false
-                        );
-                    }
                     // If requested, remove the task that is associated to this activity only if it
                     // was the root activity in the task. The result code and data is ignored
                     // because we don't support returning them across task boundaries. Also, to
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
                     mTaskSupervisor.removeTask(tr, false /*killProcess*/,
-                            finishWithRootActivity, "finish-activity");
+                            finishWithRootActivity, "finish-activity", r.getUid(), r.info.name);
                     res = true;
                     // Explicitly dismissing the activity so reset its relaunch flag.
                     r.mRelaunchReason = RELAUNCH_REASON_NONE;
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
index 48e6f97..ff1d442 100644
--- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.ActivityOptions;
 import android.app.TaskInfo;
 import android.content.Intent;
@@ -31,119 +32,423 @@
 /**
  * Callback to intercept activity starts and possibly block/redirect them. The callback methods will
  * be called with the WindowManagerGlobalLock held.
+ * @hide
  */
-public abstract class ActivityInterceptorCallback {
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public interface ActivityInterceptorCallback {
     /**
-     * Intercept the launch intent based on various signals. If an interception happened, returns
-     * a new/existing non-null {@link ActivityInterceptResult} which may redirect to another
-     * activity or with new {@link ActivityOptions}.
+     * Called to allow intercepting activity launching based on the provided launch parameters and
+     * intent resolution.
      *
-     * @return null if no interception occurred, or a non-null result which replaces the existing
-     * intent and activity options.
+     * <p>If the interceptor decides to change the {@link Intent} or return different {@link
+     * ActivityOptions}, it should return a non-{@code null} {@link ActivityInterceptResult} which
+     * may redirect to another activity or use new {@link ActivityOptions}. Otherwise, the
+     * interceptor should return {@code null} to indicate that passed {@link Intent} should not be
+     * changed.
+     *
+     * @param info the information about the {@link Intent} that is being intercepted to launch an
+     *             {@link android.app.Activity}.
+     * @return {@code null} if the interceptor decides not to change the existing intent, or a non-
+     * {@code null} result which replaces the existing intent and activity options.
      */
-    public abstract @Nullable ActivityInterceptResult intercept(ActivityInterceptorInfo info);
+    @Nullable
+    ActivityInterceptResult onInterceptActivityLaunch(@NonNull ActivityInterceptorInfo info);
 
     /**
-     * Called when an activity is successfully launched. The intent included in the
-     * ActivityInterceptorInfo may have changed from the one sent in
-     * {@link #intercept(ActivityInterceptorInfo)}, due to the return from
-     * {@link #intercept(ActivityInterceptorInfo)}.
+     * Called when an activity is successfully launched.
+     *
+     * <p>The intent included in the ActivityInterceptorInfo may have changed from the one sent in
+     * {@link #onInterceptActivityLaunch(ActivityInterceptorInfo)}, due to the changes might applied
+     * during internception.
+     *
+     * <p>There is no callback in case that the {@link android.app.Activity} is failed to launch,
+     * and this is not necessary to be added for the known use-cases.
+     *
+     * @param taskInfo the information about the @{@link Task} holds the launched
+     *                 {@link android.app.Activity}.
+     * @param activityInfo the information about the launched {@link android.app.Activity}.
+     * @param info the information about the {@link Intent} after calling {@link
+     *             #onInterceptActivityLaunch(ActivityInterceptorInfo)}.
      */
-    public void onActivityLaunched(TaskInfo taskInfo, ActivityInfo activityInfo,
-            ActivityInterceptorInfo info) {
-    }
+    default void onActivityLaunched(@NonNull TaskInfo taskInfo, @NonNull ActivityInfo activityInfo,
+            @NonNull ActivityInterceptorInfo info) {}
 
     /**
-     * The unique id of each interceptor which determines the order it will execute in.
+     * The unique id of each interceptor registered by a system service which determines the order
+     * it will execute in.
+     * @hide
      */
     @IntDef(suffix = { "_ORDERED_ID" }, value = {
-            FIRST_ORDERED_ID,
+            // Order Ids for system services
+            SYSTEM_FIRST_ORDERED_ID,
             PERMISSION_POLICY_ORDERED_ID,
             VIRTUAL_DEVICE_SERVICE_ORDERED_ID,
             DREAM_MANAGER_ORDERED_ID,
-            LAST_ORDERED_ID // Update this when adding new ids
+            SYSTEM_LAST_ORDERED_ID, // Update this when adding new ids
+            // Order Ids for mainline module services
+            MAINLINE_FIRST_ORDERED_ID,
+            MAINLINE_SDK_SANDBOX_ORDER_ID,
+            MAINLINE_LAST_ORDERED_ID  // Update this when adding new mainline module ids
     })
     @Retention(RetentionPolicy.SOURCE)
-    public @interface OrderedId {}
+    @interface OrderedId {}
 
     /**
      * The first id, used by the framework to determine the valid range of ids.
+     * @hide
      */
-    static final int FIRST_ORDERED_ID = 0;
+    int SYSTEM_FIRST_ORDERED_ID = 0;
 
     /**
      * The identifier for {@link com.android.server.policy.PermissionPolicyService} interceptor
+     * @hide
      */
-    public static final int PERMISSION_POLICY_ORDERED_ID = 1;
+    int PERMISSION_POLICY_ORDERED_ID = 1;
 
     /**
      * The identifier for {@link com.android.server.companion.virtual.VirtualDeviceManagerService}
      * interceptor.
+     * @hide
      */
-    public static final int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3;
+    int VIRTUAL_DEVICE_SERVICE_ORDERED_ID = 3;
 
     /**
      * The identifier for {@link com.android.server.dreams.DreamManagerService} interceptor.
+     * @hide
      */
-    public static final int DREAM_MANAGER_ORDERED_ID = 4;
+    int DREAM_MANAGER_ORDERED_ID = 4;
 
     /**
      * The final id, used by the framework to determine the valid range of ids. Update this when
      * adding new ids.
+     * @hide
      */
-    static final int LAST_ORDERED_ID = DREAM_MANAGER_ORDERED_ID;
+    int SYSTEM_LAST_ORDERED_ID = DREAM_MANAGER_ORDERED_ID;
+
+    /**
+     * The first mainline module id, used by the framework to determine the valid range of ids
+     * could be used by mainline modules.
+     * @hide
+     */
+    int MAINLINE_FIRST_ORDERED_ID = 1000;
+
+    /**
+     * The identifier for {@link com.android.server.sdksandbox.SdkSandboxManagerService.Lifecycle}
+     * interceptor.
+     */
+    int MAINLINE_SDK_SANDBOX_ORDER_ID = 1001;
+
+    /**
+     * The final mainline module id, used by the framework to determine the valid range of ids
+     * could be used by mainline modules. Update this when adding new ids for mainline modules.
+     * @hide
+     */
+    int MAINLINE_LAST_ORDERED_ID = MAINLINE_SDK_SANDBOX_ORDER_ID;
+
+    /**
+     * Returns {@code true} if the id is in the range of valid system services including mainline
+     * module services.
+     * @hide
+     */
+    static boolean isValidOrderId(int id) {
+        return isValidMainlineOrderId(id)
+                || (id >= SYSTEM_FIRST_ORDERED_ID && id <= SYSTEM_LAST_ORDERED_ID);
+    }
+
+    /**
+     * Returns {@code true} if the id is in the range of valid mainline module services.
+     * @hide
+     */
+    static boolean isValidMainlineOrderId(int id) {
+        return id >= MAINLINE_FIRST_ORDERED_ID && id <= MAINLINE_LAST_ORDERED_ID;
+    }
 
     /**
      * Data class for storing the various arguments needed for activity interception.
+     * @hide
      */
-    public static final class ActivityInterceptorInfo {
-        public final int realCallingUid;
-        public final int realCallingPid;
-        public final int userId;
-        public final String callingPackage;
-        public final String callingFeatureId;
-        public final Intent intent;
-        public final ResolveInfo rInfo;
-        public final ActivityInfo aInfo;
-        public final String resolvedType;
-        public final int callingPid;
-        public final int callingUid;
-        public final ActivityOptions checkedOptions;
-        public final @Nullable Runnable clearOptionsAnimation;
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    final class ActivityInterceptorInfo {
+        private final int mCallingUid;
+        private final int mCallingPid;
+        private final int mRealCallingUid;
+        private final int mRealCallingPid;
+        private final int mUserId;
+        private final Intent mIntent;
+        @NonNull
+        private final ResolveInfo mResolveInfo;
+        @NonNull
+        private final ActivityInfo mActivityInfo;
+        @Nullable
+        private final String mResolvedType;
+        @Nullable
+        private final String mCallingPackage;
+        @Nullable
+        private final String mCallingFeatureId;
+        @Nullable
+        private final ActivityOptions mCheckedOptions;
+        @Nullable
+        private final Runnable mClearOptionsAnimation;
 
-        public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId,
-                String callingPackage, String callingFeatureId, Intent intent,
-                ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid,
-                int callingUid, ActivityOptions checkedOptions,
-                @Nullable Runnable clearOptionsAnimation) {
-            this.realCallingUid = realCallingUid;
-            this.realCallingPid = realCallingPid;
-            this.userId = userId;
-            this.callingPackage = callingPackage;
-            this.callingFeatureId = callingFeatureId;
-            this.intent = intent;
-            this.rInfo = rInfo;
-            this.aInfo = aInfo;
-            this.resolvedType = resolvedType;
-            this.callingPid = callingPid;
-            this.callingUid = callingUid;
-            this.checkedOptions = checkedOptions;
-            this.clearOptionsAnimation = clearOptionsAnimation;
+        /**
+         * @hide
+         */
+        public ActivityInterceptorInfo(Builder builder) {
+            this.mCallingUid = builder.mCallingUid;
+            this.mCallingPid = builder.mCallingPid;
+            this.mRealCallingUid = builder.mRealCallingUid;
+            this.mRealCallingPid = builder.mRealCallingPid;
+            this.mUserId = builder.mUserId;
+            this.mIntent = builder.mIntent;
+            this.mResolveInfo = builder.mResolveInfo;
+            this.mActivityInfo = builder.mActivityInfo;
+            this.mResolvedType = builder.mResolvedType;
+            this.mCallingPackage = builder.mCallingPackage;
+            this.mCallingFeatureId = builder.mCallingFeatureId;
+            this.mCheckedOptions = builder.mCheckedOptions;
+            this.mClearOptionsAnimation = builder.mClearOptionsAnimation;
+        }
+
+        /**
+         * Builder class to build instances of {@link ActivityInterceptorInfo}.
+         */
+        public static final class Builder {
+            private final int mCallingUid;
+            private final int mCallingPid;
+            private final int mRealCallingUid;
+            private final int mRealCallingPid;
+            private final int mUserId;
+            private final Intent mIntent;
+            @NonNull
+            private final ResolveInfo mResolveInfo;
+            @NonNull
+            private final ActivityInfo mActivityInfo;
+            @Nullable
+            private String mResolvedType;
+            @Nullable
+            private String mCallingPackage = null;
+            @Nullable
+            private String mCallingFeatureId = null;
+            @Nullable
+            private ActivityOptions mCheckedOptions = null;
+            @Nullable
+            private Runnable mClearOptionsAnimation = null;
+
+            /**
+             * Constructor of {@link ActivityInterceptorInfo.Builder}.
+             */
+            public Builder(int callingUid, int callingPid, int realCallingUid,
+                    int realCallingPid, int userId, @NonNull Intent intent,
+                    @NonNull ResolveInfo rInfo, @NonNull ActivityInfo aInfo) {
+                this.mCallingUid = callingUid;
+                this.mCallingPid = callingPid;
+                this.mRealCallingUid = realCallingUid;
+                this.mRealCallingPid = realCallingPid;
+                this.mUserId = userId;
+                this.mIntent = intent;
+                this.mResolveInfo = rInfo;
+                this.mActivityInfo = aInfo;
+            }
+
+            /**
+             * Returns a new instance of {@link ActivityInterceptorInfo} based on the {@link
+             * Builder} fields.
+             *
+             * @return a new instance of {@link ActivityInterceptorInfo}.
+             */
+            @NonNull
+            public ActivityInterceptorInfo build() {
+                return new ActivityInterceptorInfo(this);
+            }
+
+            /**
+             * Sets the value for the resolved type.
+             * @param resolvedType the resolved type.
+             */
+            @NonNull
+            public Builder setResolvedType(@NonNull String resolvedType) {
+                mResolvedType = resolvedType;
+                return this;
+            }
+
+            /**
+             * Sets the value for the calling package.
+             * @param callingPackage the calling package.
+             */
+            @NonNull
+            public Builder setCallingPackage(@NonNull String callingPackage) {
+                mCallingPackage = callingPackage;
+                return this;
+            }
+
+            /**
+             * Sets the value for the calling feature id.
+             * @param callingFeatureId the calling feature id.
+             */
+            @NonNull
+            public Builder setCallingFeatureId(@NonNull String callingFeatureId) {
+                mCallingFeatureId = callingFeatureId;
+                return this;
+            }
+
+            /**
+             * Sets the value for the {@link ActivityOptions}.
+             * @param checkedOptions the {@link ActivityOptions}.
+             */
+            @NonNull
+            public Builder setCheckedOptions(@NonNull ActivityOptions checkedOptions) {
+                mCheckedOptions = checkedOptions;
+                return this;
+            }
+
+            /**
+             * Sets the value for the {@link Runnable} object to clear options Animation.
+             * @param clearOptionsAnimationRunnable the calling package.
+             */
+            @NonNull
+            public Builder setClearOptionsAnimationRunnable(@NonNull
+                    Runnable clearOptionsAnimationRunnable) {
+                mClearOptionsAnimation = clearOptionsAnimationRunnable;
+                return this;
+            }
+        }
+
+        /** Returns the calling uid. */
+        public int getCallingUid() {
+            return mCallingUid;
+        }
+
+        /** Returns the calling pid. */
+        public int getCallingPid() {
+            return mCallingPid;
+        }
+
+        /** Returns the real calling uid. */
+        public int getRealCallingUid() {
+            return mRealCallingUid;
+        }
+
+        /** Returns the real calling pid. */
+        public int getRealCallingPid() {
+            return mRealCallingPid;
+        }
+
+        /** Returns the user id. */
+        public int getUserId() {
+            return mUserId;
+        }
+
+        /** Returns the {@link Intent}. */
+        @SuppressWarnings("IntentBuilderName")
+        @NonNull
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        /** Returns the {@link ResolveInfo}. */
+        @NonNull
+        public ResolveInfo getResolveInfo() {
+            return mResolveInfo;
+        }
+
+        /** Returns the {@link ActivityInfo}. */
+        @NonNull
+        public ActivityInfo getActivityInfo() {
+            return mActivityInfo;
+        }
+
+        /** Returns the real resolved type. */
+        @Nullable
+        public String getResolvedType() {
+            return mResolvedType;
+        }
+
+        /** Returns the calling package. */
+        @Nullable
+        public String getCallingPackage() {
+            return mCallingPackage;
+        }
+
+        /** Returns the calling feature id. */
+        @Nullable
+        public String getCallingFeatureId() {
+            return mCallingFeatureId;
+        }
+
+        /** Returns the {@link ActivityOptions}. */
+        @Nullable
+        public ActivityOptions getCheckedOptions() {
+            return mCheckedOptions;
+        }
+
+        /** Returns the {@link Runnable} object to clear options Animation. */
+        @Nullable
+        public Runnable getClearOptionsAnimationRunnable() {
+            return mClearOptionsAnimation;
         }
     }
 
     /**
      * Data class for storing the intercept result.
+     * @hide
      */
-    public static final class ActivityInterceptResult {
-        @NonNull public final Intent intent;
-        @NonNull public final ActivityOptions activityOptions;
+    @SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+    final class ActivityInterceptResult {
+        @NonNull
+        private final Intent mIntent;
 
-        public ActivityInterceptResult(
-                @NonNull Intent intent,
+        @NonNull
+        private final ActivityOptions mActivityOptions;
+
+        private final boolean mActivityResolved;
+
+        /**
+         * This constructor should only be used if both {@link ActivityInfo} and {@link ResolveInfo}
+         * did not get resolved while interception.
+         * @hide
+         */
+        public ActivityInterceptResult(@NonNull Intent intent,
                 @NonNull ActivityOptions activityOptions) {
-            this.intent = intent;
-            this.activityOptions = activityOptions;
+            this(intent, activityOptions, false /* activityResolved */);
+        }
+
+        /**
+         * Generates the result of intercepting launching the {@link android.app.Activity}
+         *
+         * <p>Interceptor should return non-{@code null} result when {@link
+         * #onInterceptActivityLaunch(ActivityInterceptorInfo)} gets called as an indicator that
+         * interception has happened.
+         *
+         * @param intent is the modified {@link Intent} after interception.
+         * @param activityOptions holds the {@link ActivityOptions} after interception.
+         * @param activityResolved should be {@code true} only if {@link ActivityInfo} or {@link
+         *                         ResolveInfo} gets resolved, otherwise should be {@code false}.
+         */
+        public ActivityInterceptResult(@NonNull Intent intent,
+                @NonNull ActivityOptions activityOptions, boolean activityResolved) {
+            this.mIntent = intent;
+            this.mActivityOptions = activityOptions;
+            this.mActivityResolved = activityResolved;
+        }
+
+        /** Returns the intercepted {@link Intent} */
+        @SuppressWarnings("IntentBuilderName")
+        @NonNull
+        public Intent getIntent() {
+            return mIntent;
+        }
+
+        /** Returns the intercepted {@link ActivityOptions} */
+        @NonNull
+        public ActivityOptions getActivityOptions() {
+            return mActivityOptions;
+        }
+
+        /**
+         * Returns if the {@link ActivityInfo} or {@link ResolveInfo} gets resolved.
+         */
+        public boolean isActivityResolved() {
+            return mActivityResolved;
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java
new file mode 100644
index 0000000..bb9462f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java
@@ -0,0 +1,122 @@
+/*
+ * 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.wm;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Binder;
+import android.os.Process;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+
+/**
+ * This class should be used by system services which are part of mainline modules to register
+ * {@link ActivityInterceptorCallback}. For other system services, this function should be used
+ * instead {@link ActivityTaskManagerInternal#registerActivityStartInterceptor(
+ * int, ActivityInterceptorCallback)}.
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
+public class ActivityInterceptorCallbackRegistry {
+
+    private static final ActivityInterceptorCallbackRegistry sInstance =
+            new ActivityInterceptorCallbackRegistry();
+
+    private ActivityInterceptorCallbackRegistry() {}
+
+    /** Returns an already initialised singleton instance of this class. */
+    @NonNull
+    public static ActivityInterceptorCallbackRegistry getInstance() {
+        return sInstance;
+    }
+
+    /**
+     * Registers a callback which can intercept activity launching flow.
+     *
+     * <p>Only system services which are part of mainline modules should call this function.
+     *
+     * <p>To avoid Activity launch delays, the callbacks must execute quickly and avoid acquiring
+     * other system process locks.
+     *
+     * @param mainlineOrderId has to be one of the following [{@link
+     *                        ActivityInterceptorCallback#MAINLINE_FIRST_ORDERED_ID}].
+     * @param callback the {@link ActivityInterceptorCallback} to register.
+     * @throws IllegalArgumentException if duplicate ids are provided, the provided id is not the
+     * mainline module range or the provided {@code callback} is null.
+     */
+    // ExecutorRegistration is suppressed as the callback is called synchronously in the system
+    // server.
+    @SuppressWarnings("ExecutorRegistration")
+    public void registerActivityInterceptorCallback(
+            @ActivityInterceptorCallback.OrderedId int mainlineOrderId,
+            @NonNull ActivityInterceptorCallback callback) {
+        if (getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only system server can register "
+                    + "ActivityInterceptorCallback");
+        }
+        if (!ActivityInterceptorCallback.isValidMainlineOrderId(mainlineOrderId)) {
+            throw new IllegalArgumentException("id is not in the mainline modules range, please use"
+                    + "ActivityTaskManagerInternal.registerActivityStartInterceptor(OrderedId, "
+                    + "ActivityInterceptorCallback) instead.");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("The passed ActivityInterceptorCallback can not be "
+                    + "null");
+        }
+        ActivityTaskManagerInternal activityTaskManagerInternal =
+                LocalServices.getService(ActivityTaskManagerInternal.class);
+        activityTaskManagerInternal.registerActivityStartInterceptor(mainlineOrderId, callback);
+    }
+
+    /**
+     * Unregisters an already registered {@link ActivityInterceptorCallback}.
+     *
+     * @param mainlineOrderId the order id of the {@link ActivityInterceptorCallback} should be
+     *                        unregistered, this callback should be registered before by calling
+     *                        {@link #registerActivityInterceptorCallback(int,
+     *                        ActivityInterceptorCallback)} using the same order id.
+     * @throws IllegalArgumentException if the provided id is not the mainline module range or is
+     * not registered
+     */
+    public void unregisterActivityInterceptorCallback(
+            @ActivityInterceptorCallback.OrderedId int mainlineOrderId) {
+        if (getCallingUid() != Process.SYSTEM_UID) {
+            throw new SecurityException("Only system server can register "
+                    + "ActivityInterceptorCallback");
+        }
+        if (!ActivityInterceptorCallback.isValidMainlineOrderId(mainlineOrderId)) {
+            throw new IllegalArgumentException("id is not in the mainline modules range, please use"
+                    + "ActivityTaskManagerInternal.unregisterActivityStartInterceptor(OrderedId) "
+                    + "instead.");
+        }
+        ActivityTaskManagerInternal activityTaskManagerInternal =
+                LocalServices.getService(ActivityTaskManagerInternal.class);
+        activityTaskManagerInternal.unregisterActivityStartInterceptor(mainlineOrderId);
+    }
+
+    /**
+     * This hidden function is for unit tests as a way to behave like as if they are called from
+     * system server process uid by mocking it and returning {@link Process#SYSTEM_UID}.
+     * Do not make this {@code public} to apps as apps should not have a way to change the uid.
+     * @hide
+     */
+    @VisibleForTesting
+    int getCallingUid() {
+        return Binder.getCallingUid();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 064f4ee..bf4e25c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -736,7 +736,6 @@
      */
     private boolean mWillCloseOrEnterPip;
 
-    @VisibleForTesting
     final LetterboxUiController mLetterboxUiController;
 
     /**
@@ -2003,7 +2002,7 @@
 
             mOverrideTaskTransition = options.getOverrideTaskTransition();
             mDismissKeyguard = options.getDismissKeyguard();
-            mShareIdentity = options.getShareIdentity();
+            mShareIdentity = options.isShareIdentityEnabled();
         }
 
         ColorDisplayService.ColorDisplayServiceInternal cds = LocalServices.getService(
@@ -6076,6 +6075,19 @@
         r.mDisplayContent.mUnknownAppVisibilityController.notifyAppResumedFinished(r);
     }
 
+    static void activityRefreshedLocked(IBinder token) {
+        final ActivityRecord r = ActivityRecord.forTokenLocked(token);
+        ProtoLog.i(WM_DEBUG_STATES, "Refreshed activity: %s", r);
+        if (r == null) {
+            // In case the record on server side has been removed (e.g. destroy timeout)
+            // and the token could be null.
+            return;
+        }
+        if (r.mDisplayContent.mDisplayRotationCompatPolicy != null) {
+            r.mDisplayContent.mDisplayRotationCompatPolicy.onActivityRefreshed(r);
+        }
+    }
+
     static void splashScreenAttachedLocked(IBinder token) {
         final ActivityRecord r = ActivityRecord.forTokenLocked(token);
         if (r == null) {
@@ -8149,7 +8161,7 @@
     }
 
     /**
-     * Adjusts position of resolved bounds if they doesn't fill the parent using gravity
+     * Adjusts position of resolved bounds if they don't fill the parent using gravity
      * requested in the config or via an ADB command. For more context see {@link
      * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
      * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
@@ -8168,9 +8180,8 @@
         int offsetX = 0;
         if (parentBounds.width() != screenResolvedBounds.width()) {
             if (screenResolvedBounds.width() <= parentAppBounds.width()) {
-                float positionMultiplier =
-                        mLetterboxUiController.getHorizontalPositionMultiplier(
-                                newParentConfiguration);
+                float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier(
+                        newParentConfiguration);
                 offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
                         * positionMultiplier);
             }
@@ -8180,9 +8191,8 @@
         int offsetY = 0;
         if (parentBounds.height() != screenResolvedBounds.height()) {
             if (screenResolvedBounds.height() <= parentAppBounds.height()) {
-                float positionMultiplier =
-                        mLetterboxUiController.getVerticalPositionMultiplier(
-                                newParentConfiguration);
+                float positionMultiplier = mLetterboxUiController.getVerticalPositionMultiplier(
+                        newParentConfiguration);
                 offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
                         * positionMultiplier);
             }
@@ -9153,6 +9163,8 @@
             } else {
                 scheduleConfigurationChanged(newMergedOverrideConfig);
             }
+            notifyDisplayCompatPolicyAboutConfigurationChange(
+                    mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
             return true;
         }
 
@@ -9221,11 +9233,24 @@
         } else {
             scheduleConfigurationChanged(newMergedOverrideConfig);
         }
+        notifyDisplayCompatPolicyAboutConfigurationChange(
+                mLastReportedConfiguration.getMergedConfiguration(), mTmpConfig);
+
         stopFreezingScreenLocked(false);
 
         return true;
     }
 
+    private void notifyDisplayCompatPolicyAboutConfigurationChange(
+            Configuration newConfig, Configuration lastReportedConfig) {
+        if (mDisplayContent.mDisplayRotationCompatPolicy == null
+                || !shouldBeResumed(/* activeActivity */ null)) {
+            return;
+        }
+        mDisplayContent.mDisplayRotationCompatPolicy.onActivityConfigurationChanging(
+                this, newConfig, lastReportedConfig);
+    }
+
     /** Get process configuration, or global config if the process is not set. */
     private Configuration getProcessGlobalConfiguration() {
         return app != null ? app.getConfiguration() : mAtmService.getGlobalConfiguration();
@@ -9537,7 +9562,7 @@
 
         final ActivityTaskManagerService service = taskSupervisor.mService;
         final ActivityInfo aInfo = taskSupervisor.resolveActivity(intent, resolvedType, 0, null,
-                userId, Binder.getCallingUid());
+                userId, Binder.getCallingUid(), 0);
         if (aInfo == null) {
             throw new XmlPullParserException("restoreActivity resolver error. Intent=" + intent +
                     " resolvedType=" + resolvedType);
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index 05ec3b5..43d3111 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -417,7 +417,8 @@
 
                 // Collect information about the target of the Intent.
                 ActivityInfo aInfo = mSupervisor.resolveActivity(intent, resolvedTypes[i],
-                        0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid);
+                        0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid,
+                        callingPid);
                 aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId);
 
                 if (aInfo != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 719f72c..b40aa3c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -23,6 +23,7 @@
 import static android.app.PendingIntent.FLAG_ONE_SHOT;
 import static android.app.admin.DevicePolicyManager.EXTRA_RESTRICTION;
 import static android.app.admin.DevicePolicyManager.POLICY_SUSPEND_PACKAGES;
+import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
 import static android.content.Context.KEYGUARD_SERVICE;
 import static android.content.Intent.EXTRA_INTENT;
 import static android.content.Intent.EXTRA_PACKAGE_NAME;
@@ -33,7 +34,9 @@
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.KeyguardManager;
@@ -226,16 +229,26 @@
                 getInterceptorInfo(null /* clearOptionsAnimation */);
 
         for (int i = 0; i < callbacks.size(); i++) {
+            final int orderId = callbacks.keyAt(i);
+            if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) {
+                continue;
+            }
+
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
-            final ActivityInterceptResult interceptResult = callback.intercept(interceptorInfo);
+            final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch(
+                    interceptorInfo);
             if (interceptResult == null) {
                 continue;
             }
-            mIntent = interceptResult.intent;
-            mActivityOptions = interceptResult.activityOptions;
+            mIntent = interceptResult.getIntent();
+            mActivityOptions = interceptResult.getActivityOptions();
             mCallingPid = mRealCallingPid;
             mCallingUid = mRealCallingUid;
-            mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid);
+            if (interceptResult.isActivityResolved()) {
+                return true;
+            }
+            mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0,
+                    mRealCallingUid, mRealCallingPid);
             mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags,
                     null /*profilerInfo*/);
             return true;
@@ -277,7 +290,8 @@
         mResolvedType = null;
 
         final UserInfo parent = mUserManager.getProfileParent(mUserId);
-        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0, mRealCallingUid);
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0,
+                mRealCallingUid, mRealCallingPid);
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
@@ -298,10 +312,10 @@
         final UserInfo parent = mUserManager.getProfileParent(mUserId);
         if (parent != null) {
             mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0,
-                    mRealCallingUid);
+                    mRealCallingUid, mRealCallingPid);
         } else {
             mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0,
-                    mRealCallingUid);
+                    mRealCallingUid, mRealCallingPid);
         }
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
@@ -334,7 +348,8 @@
         mCallingPid = mRealCallingPid;
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
-        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0,
+                mRealCallingUid, mRealCallingPid);
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
@@ -353,7 +368,8 @@
         mCallingPid = mRealCallingPid;
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
-        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0,
+                mRealCallingUid, mRealCallingPid);
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
@@ -388,7 +404,8 @@
         }
 
         final UserInfo parent = mUserManager.getProfileParent(mUserId);
-        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0, mRealCallingUid);
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id, 0,
+                mRealCallingUid, mRealCallingPid);
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
@@ -445,7 +462,8 @@
         mCallingUid = mRealCallingUid;
         mResolvedType = null;
 
-        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+        mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0,
+                mRealCallingUid, mRealCallingPid);
         mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
         return true;
     }
@@ -459,6 +477,11 @@
         ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
                 r::clearOptionsAnimationForSiblings);
         for (int i = 0; i < callbacks.size(); i++) {
+            final int orderId = callbacks.keyAt(i);
+            if (!shouldNotifyOnActivityLaunch(orderId, info)) {
+                continue;
+            }
+
             final ActivityInterceptorCallback callback = callbacks.valueAt(i);
             callback.onActivityLaunched(taskInfo, r.info, info);
         }
@@ -466,9 +489,33 @@
 
     private ActivityInterceptorCallback.ActivityInterceptorInfo getInterceptorInfo(
             @Nullable Runnable clearOptionsAnimation) {
-        return new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid,
-                mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent,
-                mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid,
-                mActivityOptions, clearOptionsAnimation);
+        return new ActivityInterceptorCallback.ActivityInterceptorInfo.Builder(mCallingUid,
+                mCallingPid, mRealCallingUid, mRealCallingPid, mUserId, mIntent, mRInfo, mAInfo)
+                .setResolvedType(mResolvedType)
+                .setCallingPackage(mCallingPackage)
+                .setCallingFeatureId(mCallingFeatureId)
+                .setCheckedOptions(mActivityOptions)
+                .setClearOptionsAnimationRunnable(clearOptionsAnimation)
+                .build();
+    }
+
+    private boolean shouldInterceptActivityLaunch(
+            @ActivityInterceptorCallback.OrderedId int orderId,
+            @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
+        if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
+            return info.getIntent() != null && info.getIntent().getAction() != null
+                    && info.getIntent().getAction().equals(ACTION_START_SANDBOXED_ACTIVITY);
+        }
+        return true;
+    }
+
+    private boolean shouldNotifyOnActivityLaunch(
+            @ActivityInterceptorCallback.OrderedId int orderId,
+            @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
+        if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
+            return info.getIntent() != null && info.getIntent().getAction() != null
+                    && info.getIntent().getAction().equals(ACTION_START_SANDBOXED_ACTIVITY);
+        }
+        return true;
     }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index fb1b591..ff50fd4 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -528,7 +528,8 @@
 
             resolveInfo = supervisor.resolveIntent(intent, resolvedType, userId,
                     0 /* matchFlags */,
-                    computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid));
+                    computeResolveFilterUid(callingUid, realCallingUid, filterCallingUid),
+                    realCallingPid);
             if (resolveInfo == null) {
                 final UserInfo userInfo = supervisor.getUserInfo(userId);
                 if (userInfo != null && userInfo.isManagedProfile()) {
@@ -551,7 +552,7 @@
                                 PackageManager.MATCH_DIRECT_BOOT_AWARE
                                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
                                 computeResolveFilterUid(callingUid, realCallingUid,
-                                        filterCallingUid));
+                                        filterCallingUid), realCallingPid);
                     }
                 }
             }
@@ -806,7 +807,7 @@
         mRequest.resolveInfo = mSupervisor.resolveIntent(mRequest.intent, null /* resolvedType */,
                 mRequest.userId, 0 /* matchFlags */,
                 computeResolveFilterUid(mRequest.callingUid, mRequest.realCallingUid,
-                        mRequest.filterCallingUid));
+                        mRequest.filterCallingUid), mRequest.realCallingPid);
         mRequest.activityInfo =
                 mRequest.resolveInfo != null ? mRequest.resolveInfo.activityInfo : null;
         if (mRequest.activityInfo != null) {
@@ -1143,7 +1144,8 @@
 
                 rInfo = mSupervisor.resolveIntent(intent, resolvedType, userId, 0,
                         computeResolveFilterUid(
-                                callingUid, realCallingUid, request.filterCallingUid));
+                                callingUid, realCallingUid, request.filterCallingUid),
+                        realCallingPid);
                 aInfo = mSupervisor.resolveActivity(intent, rInfo, startFlags,
                         null /*profilerInfo*/);
 
@@ -1661,6 +1663,12 @@
             pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
                     UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
                     resultToUid /*visible*/, true /*direct*/);
+        } else if (mStartActivity.mShareIdentity) {
+            final PackageManagerInternal pmInternal =
+                    mService.getPackageManagerInternalLocked();
+            pmInternal.grantImplicitAccess(mStartActivity.mUserId, mIntent,
+                    UserHandle.getAppId(mStartActivity.info.applicationInfo.uid) /*recipient*/,
+                    r.launchedFromUid /*visible*/, true /*direct*/);
         }
         final Task startedTask = mStartActivity.getTask();
         if (newTask) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ec48643..ef126a9 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -29,6 +29,7 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.LocaleList;
@@ -622,10 +623,19 @@
         @Nullable
         public final LocaleList mLocales;
 
+        /**
+         * Gender for the application, null if app-specific grammatical gender is not set.
+         */
+        @Nullable
+        public final @Configuration.GrammaticalGender
+        Integer mGrammaticalGender;
+
         @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-        public PackageConfig(Integer nightMode, LocaleList locales) {
+        public PackageConfig(Integer nightMode, LocaleList locales,
+                @Configuration.GrammaticalGender Integer grammaticalGender) {
             mNightMode = nightMode;
             mLocales = locales;
+            mGrammaticalGender = grammaticalGender;
         }
 
         /**
@@ -660,6 +670,13 @@
         PackageConfigurationUpdater setLocales(LocaleList locales);
 
         /**
+         * Sets the gender for the current application. This setting is persisted and will
+         * override the system configuration for this application.
+         */
+        PackageConfigurationUpdater setGrammaticalGender(
+                @Configuration.GrammaticalGender int gender);
+
+        /**
          * Commit changes.
          * @return true if the configuration changes were persisted,
          * false if there were no changes, or if erroneous inputs were provided, such as:
@@ -680,12 +697,23 @@
 
     /**
      * Registers a callback which can intercept activity starts.
-     * @throws IllegalArgumentException if duplicate ids are provided
+     * @throws IllegalArgumentException if duplicate ids are provided or the provided {@code
+     * callback} is null
+     * @see ActivityInterceptorCallbackRegistry
+     * #registerActivityInterceptorCallback(int, ActivityInterceptorCallback)
      */
     public abstract void registerActivityStartInterceptor(
             @ActivityInterceptorCallback.OrderedId int id,
             ActivityInterceptorCallback callback);
 
+    /**
+     * Unregisters an {@link ActivityInterceptorCallback}.
+     * @throws IllegalArgumentException if id is not registered
+     * @see ActivityInterceptorCallbackRegistry#unregisterActivityInterceptorCallback(int)
+     */
+    public abstract void unregisterActivityStartInterceptor(
+            @ActivityInterceptorCallback.OrderedId int id);
+
     /** Get the most recent task excluding the first running task (the one on the front most). */
     public abstract ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground();
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5dd77ea..f4d76c2 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -65,6 +65,7 @@
 import static android.provider.Settings.System.FONT_SCALE;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
 
@@ -94,8 +95,10 @@
 import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE;
 import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
 import static com.android.server.am.EventLogTags.writeConfigurationChanged;
-import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
-import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
@@ -1876,7 +1879,7 @@
         try {
             // Collect information about the target of the Intent.
             final ActivityInfo aInfo = resolveActivityInfoForIntent(intent, resolvedType, userId,
-                    callingUid);
+                    callingUid, callingPid);
 
             synchronized (mGlobalLock) {
                 return mTaskSupervisor.canPlaceEntityOnDisplay(displayId, callingPid, callingUid,
@@ -1888,11 +1891,11 @@
     }
 
     ActivityInfo resolveActivityInfoForIntent(Intent intent, String resolvedType,
-            int userId, int callingUid) {
+            int userId, int callingUid, int callingPid) {
         ActivityInfo aInfo = mTaskSupervisor.resolveActivity(intent, resolvedType,
                 0 /* startFlags */, null /* profilerInfo */, userId,
                 ActivityStarter.computeResolveFilterUid(callingUid, callingUid,
-                        UserHandle.USER_NULL));
+                        UserHandle.USER_NULL), callingPid);
         return mAmInternal.getActivityInfoForUser(aInfo, userId);
     }
 
@@ -2841,7 +2844,7 @@
     }
 
     @Override
-    public boolean resizeTask(int taskId, Rect bounds, int resizeMode) {
+    public void resizeTask(int taskId, Rect bounds, int resizeMode) {
         enforceTaskPermission("resizeTask()");
         final long ident = Binder.clearCallingIdentity();
         try {
@@ -2850,19 +2853,48 @@
                         MATCH_ATTACHED_TASK_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "resizeTask: taskId=" + taskId + " not found");
-                    return false;
+                    return;
                 }
                 if (!task.getWindowConfiguration().canResizeTask()) {
                     Slog.w(TAG, "resizeTask not allowed on task=" + task);
-                    return false;
+                    return;
                 }
 
                 // Reparent the task to the right root task if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
 
-                // After reparenting (which only resizes the task to the root task bounds),
-                // resize the task to the actual bounds provided
-                return task.resize(bounds, resizeMode, preserveWindow);
+                if (!getTransitionController().isShellTransitionsEnabled()) {
+                    // After reparenting (which only resizes the task to the root task bounds),
+                    // resize the task to the actual bounds provided
+                    task.resize(bounds, resizeMode, preserveWindow);
+                    return;
+                }
+
+                final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+                        getTransitionController(), mWindowManager.mSyncEngine);
+                if (mWindowManager.mSyncEngine.hasActiveSync()) {
+                    mWindowManager.mSyncEngine.queueSyncSet(
+                            () -> getTransitionController().moveToCollecting(transition),
+                            () -> {
+                                if (!task.getWindowConfiguration().canResizeTask()) {
+                                    Slog.w(TAG, "resizeTask not allowed on task=" + task);
+                                    transition.abort();
+                                    return;
+                                }
+                                getTransitionController().requestStartTransition(transition, task,
+                                        null /* remoteTransition */, null /* displayChange */);
+                                getTransitionController().collect(task);
+                                task.resize(bounds, resizeMode, preserveWindow);
+                                transition.setReady(task, true);
+                            });
+                } else {
+                    getTransitionController().moveToCollecting(transition);
+                    getTransitionController().requestStartTransition(transition, task,
+                            null /* remoteTransition */, null /* displayChange */);
+                    getTransitionController().collect(task);
+                    task.resize(bounds, resizeMode, preserveWindow);
+                    transition.setReady(task, true);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -3628,19 +3660,6 @@
     }
 
     @Override
-    public void setSplitScreenResizing(boolean resizing) {
-        enforceTaskPermission("setSplitScreenResizing()");
-        final long ident = Binder.clearCallingIdentity();
-        try {
-            synchronized (mGlobalLock) {
-                mTaskSupervisor.setSplitScreenResizing(resizing);
-            }
-        } finally {
-            Binder.restoreCallingIdentity(ident);
-        }
-    }
-
-    @Override
     public IWindowOrganizerController getWindowOrganizerController() {
         return mWindowOrganizerController;
     }
@@ -6812,16 +6831,35 @@
                 if (mActivityInterceptorCallbacks.contains(id)) {
                     throw new IllegalArgumentException("Duplicate id provided: " + id);
                 }
-                if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) {
+                if (callback == null) {
+                    throw new IllegalArgumentException("The passed ActivityInterceptorCallback "
+                            + "can not be null");
+                }
+                if (!ActivityInterceptorCallback.isValidOrderId(id)) {
                     throw new IllegalArgumentException(
-                            "Provided id " + id + " is not in range of valid ids ["
-                                    + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]");
+                            "Provided id " + id + " is not in range of valid ids for system "
+                                    + "services [" + SYSTEM_FIRST_ORDERED_ID + ","
+                                    + SYSTEM_LAST_ORDERED_ID + "] nor in range of valid ids for "
+                                    + "mainline module services [" + MAINLINE_FIRST_ORDERED_ID + ","
+                                    + MAINLINE_LAST_ORDERED_ID + "]");
                 }
                 mActivityInterceptorCallbacks.put(id, callback);
             }
         }
 
         @Override
+        public void unregisterActivityStartInterceptor(
+                @ActivityInterceptorCallback.OrderedId int id) {
+            synchronized (mGlobalLock) {
+                if (!mActivityInterceptorCallbacks.contains(id)) {
+                    throw new IllegalArgumentException(
+                            "ActivityInterceptorCallback with id (" + id + ") is not registered");
+                }
+                mActivityInterceptorCallbacks.remove(id);
+            }
+        }
+
+        @Override
         public ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground() {
             List<ActivityManager.RunningTaskInfo> runningTaskInfoList = getTasks(1);
             ActivityManager.RunningTaskInfo runningTaskInfo;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 33c90a0..3f885f3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -46,6 +46,7 @@
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
@@ -98,6 +99,7 @@
 import android.app.servertransaction.LaunchActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -140,6 +142,7 @@
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
 import com.android.server.am.ActivityManagerService;
@@ -207,9 +210,6 @@
     // Used to indicate that a task is removed it should also be removed from recents.
     static final boolean REMOVE_FROM_RECENTS = true;
 
-    /** True if the docked root task is currently being resized. */
-    private boolean mDockedRootTaskResizing;
-
     // Activity actions an app cannot start if it uses a permission which is not granted.
     private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
             new ArrayMap<>();
@@ -255,6 +255,7 @@
     private WindowManagerService mWindowManager;
 
     private AppOpsManager mAppOpsManager;
+    private VirtualDeviceManager mVirtualDeviceManager;
 
     /** Common synchronization logic used to save things to disks. */
     PersisterQueue mPersisterQueue;
@@ -718,7 +719,7 @@
     }
 
     ResolveInfo resolveIntent(Intent intent, String resolvedType, int userId, int flags,
-            int filterCallingUid) {
+            int filterCallingUid, int callingPid) {
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "resolveIntent");
             int modifiedFlags = flags
@@ -745,7 +746,7 @@
             try {
                 return mService.getPackageManagerInternalLocked().resolveIntentExported(
                         intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
-                        filterCallingUid);
+                        filterCallingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -755,8 +756,9 @@
     }
 
     ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
-            ProfilerInfo profilerInfo, int userId, int filterCallingUid) {
-        final ResolveInfo rInfo = resolveIntent(intent, resolvedType, userId, 0, filterCallingUid);
+            ProfilerInfo profilerInfo, int userId, int filterCallingUid, int callingPid) {
+        final ResolveInfo rInfo = resolveIntent(intent, resolvedType, userId, 0,
+                filterCallingUid, callingPid);
         return resolveActivity(intent, rInfo, startFlags, profilerInfo);
     }
 
@@ -897,12 +899,14 @@
 
                 final boolean isTransitionForward = r.isTransitionForward();
                 final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
+
+                final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
                 clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
                         mergedConfiguration.getGlobalConfiguration(),
-                        mergedConfiguration.getOverrideConfiguration(),
+                        mergedConfiguration.getOverrideConfiguration(), deviceId,
                         r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,
                         proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),
                         results, newIntents, r.takeOptions(), isTransitionForward,
@@ -1218,6 +1222,17 @@
         }
     }
 
+    int getDeviceIdForDisplayId(int displayId) {
+        if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY)  {
+            return VirtualDeviceManager.DEVICE_ID_DEFAULT;
+        }
+        if (mVirtualDeviceManager == null) {
+            mVirtualDeviceManager =
+                    mService.mContext.getSystemService(VirtualDeviceManager.class);
+        }
+        return mVirtualDeviceManager.getDeviceIdForDisplayId(displayId);
+    }
+
     private AppOpsManager getAppOpsManager() {
         if (mAppOpsManager == null) {
             mAppOpsManager = mService.mContext.getSystemService(AppOpsManager.class);
@@ -1526,15 +1541,6 @@
         return mLaunchParamsController;
     }
 
-    void setSplitScreenResizing(boolean resizing) {
-        if (resizing == mDockedRootTaskResizing) {
-            return;
-        }
-
-        mDockedRootTaskResizing = resizing;
-        mWindowManager.setDockedRootTaskResizing(resizing);
-    }
-
     private void removePinnedRootTaskInSurfaceTransaction(Task rootTask) {
         /**
          * Workaround: Force-stop all the activities in the root pinned task before we reparent them
@@ -1601,11 +1607,11 @@
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskById(int taskId, boolean killProcess, boolean removeFromRecents,
-            String reason) {
+            String reason, int callingUid) {
         final Task task =
                 mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS);
         if (task != null) {
-            removeTask(task, killProcess, removeFromRecents, reason);
+            removeTask(task, killProcess, removeFromRecents, reason, callingUid, null);
             return true;
         }
         Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
@@ -1613,10 +1619,52 @@
     }
 
     void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason) {
+        removeTask(task, killProcess, removeFromRecents, reason, SYSTEM_UID, null);
+    }
+
+    void removeTask(Task task, boolean killProcess, boolean removeFromRecents, String reason,
+            int callingUid, String callerActivityClassName) {
         if (task.mInRemoveTask) {
             // Prevent recursion.
             return;
         }
+        // We may have already checked that the callingUid has additional clearTask privileges, and
+        // cleared the calling identify. If so, we infer we do not need further restrictions here.
+        // TODO(b/263368846) Move to live with the rest of the ASM logic.
+        if (callingUid != SYSTEM_UID) {
+            ActivityRecord topActivity = task.getTopNonFinishingActivity();
+            boolean passesAsmChecks = topActivity != null
+                    && topActivity.getUid() == callingUid;
+            if (!passesAsmChecks) {
+                Slog.i(TAG, "Finishing task from background. t: " + task);
+                FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                        /* caller_uid */
+                        callingUid,
+                        /* caller_activity_class_name */
+                        callerActivityClassName,
+                        /* target_task_top_activity_uid */
+                        topActivity == null ? -1 : topActivity.getUid(),
+                        /* target_task_top_activity_class_name */
+                        topActivity == null ? null : topActivity.info.name,
+                        /* target_task_is_different */
+                        false,
+                        /* target_activity_uid */
+                        -1,
+                        /* target_activity_class_name */
+                        null,
+                        /* target_intent_action */
+                        null,
+                        /* target_intent_flags */
+                        0,
+                        /* action */
+                        FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+                        /* version */
+                        1,
+                        /* multi_window */
+                        false
+                );
+            }
+        }
         task.mTransitionController.requestCloseTransitionIfNeeded(task);
         task.mInRemoveTask = true;
         try {
@@ -1739,7 +1787,7 @@
             // Task was trimmed from the recent tasks list -- remove the active task record as well
             // since the user won't really be able to go back to it
             removeTaskById(task.mTaskId, killProcess, false /* removeFromRecents */,
-                    "recent-task-trimmed");
+                    "recent-task-trimmed", SYSTEM_UID);
         }
         task.removedFromRecents();
     }
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index b160af6a..7bd8c53 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -72,15 +72,16 @@
         checkCallerOrSystemOrRoot();
 
         synchronized (mService.mGlobalLock) {
-            final long origId = Binder.clearCallingIdentity();
+            int origCallingUid = Binder.getCallingUid();
+            final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 // We remove the task from recents to preserve backwards
                 if (!mService.mTaskSupervisor.removeTaskById(mTaskId, false,
-                        REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
+                        REMOVE_FROM_RECENTS, "finish-and-remove-task", origCallingUid)) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
             } finally {
-                Binder.restoreCallingIdentity(origId);
+                Binder.restoreCallingIdentity(callingIdentity);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 0c6cea8..58d4e82 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -536,16 +536,19 @@
      * Applies app-specific nightMode and {@link LocaleList} on requested configuration.
      * @return true if any of the requested configuration has been updated.
      */
-    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales) {
+    public boolean applyAppSpecificConfig(Integer nightMode, LocaleList locales,
+            @Configuration.GrammaticalGender Integer gender) {
         mRequestsTmpConfig.setTo(getRequestedOverrideConfiguration());
         boolean newNightModeSet = (nightMode != null) && setOverrideNightMode(mRequestsTmpConfig,
                 nightMode);
         boolean newLocalesSet = (locales != null) && setOverrideLocales(mRequestsTmpConfig,
                 locales);
-        if (newNightModeSet || newLocalesSet) {
+        boolean newGenderSet = (gender != null) && setOverrideGender(mRequestsTmpConfig,
+                gender);
+        if (newNightModeSet || newLocalesSet || newGenderSet) {
             onRequestedOverrideConfigurationChanged(mRequestsTmpConfig);
         }
-        return newNightModeSet || newLocalesSet;
+        return newNightModeSet || newLocalesSet || newGenderSet;
     }
 
     /**
@@ -578,6 +581,21 @@
         return true;
     }
 
+    /**
+     * Overrides the gender to this ConfigurationContainer.
+     *
+     * @return true if the grammatical gender has been changed.
+     */
+    private boolean setOverrideGender(Configuration requestsTmpConfig,
+            @Configuration.GrammaticalGender int gender) {
+        if (mRequestedOverrideConfiguration.getGrammaticalGender() == gender) {
+            return false;
+        } else {
+            requestsTmpConfig.setGrammaticalGender(gender);
+            return true;
+        }
+    }
+
     public boolean isActivityTypeDream() {
         return getActivityType() == ACTIVITY_TYPE_DREAM;
     }
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 8d5d0d5..af135b7 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -211,6 +211,7 @@
      * Stops recording on this DisplayContent, and updates the session details.
      */
     void stopRecording() {
+        unregisterListener();
         if (mRecordedSurface != null) {
             // Do not wait for the mirrored surface to be garbage collected, but clean up
             // immediately.
@@ -227,7 +228,7 @@
      * Ensure recording does not fall back to the display stack; ensure the recording is stopped
      * and the client notified by tearing down the virtual display.
      */
-    void stopMediaProjection() {
+    private void stopMediaProjection() {
         ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                 "Stop MediaProjection on virtual display %d", mDisplayContent.getDisplayId());
         if (mMediaProjectionManager != null) {
@@ -247,6 +248,16 @@
                 null, mDisplayContent.mWmService);
     }
 
+    private void unregisterListener() {
+        Task recordedTask = mRecordedWindowContainer != null
+                ? mRecordedWindowContainer.asTask() : null;
+        if (recordedTask == null || !isRecordingContentTask()) {
+            return;
+        }
+        recordedTask.unregisterWindowContainerListener(this);
+        mRecordedWindowContainer = null;
+    }
+
     /**
      * Start recording to this DisplayContent if it does not have its own content. Captures the
      * content of a WindowContainer indicated by a WindowToken. If unable to start recording, falls
@@ -301,6 +312,13 @@
         // Retrieve the size of the DisplayArea to mirror.
         updateMirroredSurface(transaction, mRecordedWindowContainer.getBounds(), surfaceSize);
 
+        // Notify the client about the visibility of the mirrored region, now that we have begun
+        // capture.
+        if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+            mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                    mRecordedWindowContainer.asTask().isVisibleRequested());
+        }
+
         // No need to clean up. In SurfaceFlinger, parents hold references to their children. The
         // mirrored SurfaceControl is alive since the parent DisplayContent SurfaceControl is
         // holding a reference to it. Therefore, the mirrored SurfaceControl will be cleaned up
@@ -389,6 +407,7 @@
      */
     private void handleStartRecordingFailed() {
         final boolean shouldExitTaskRecording = isRecordingContentTask();
+        unregisterListener();
         clearContentRecordingSession();
         if (shouldExitTaskRecording) {
             // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,12 +497,7 @@
                 "Recorded task is removed, so stop recording on display %d",
                 mDisplayContent.getDisplayId());
 
-        Task recordedTask = mRecordedWindowContainer != null
-                ? mRecordedWindowContainer.asTask() : null;
-        if (recordedTask == null || !isRecordingContentTask()) {
-            return;
-        }
-        recordedTask.unregisterWindowContainerListener(this);
+        unregisterListener();
         // Stop mirroring and teardown.
         clearContentRecordingSession();
         // Clean up the cached session first to ensure recording doesn't re-start, since
@@ -501,9 +515,20 @@
         mLastOrientation = mergedOverrideConfiguration.orientation;
     }
 
+    // WindowContainerListener
+    @Override
+    public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+        // Check still recording just to be safe.
+        if (isCurrentlyRecording() && mLastRecordedBounds != null) {
+            mMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                    isVisibleRequested);
+        }
+    }
+
     @VisibleForTesting interface MediaProjectionManagerWrapper {
         void stopActiveProjection();
         void notifyActiveProjectionCapturedContentResized(int width, int height);
+        void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible);
     }
 
     private static final class RemoteMediaProjectionManagerWrapper implements
@@ -543,6 +568,23 @@
             }
         }
 
+        @Override
+        public void notifyActiveProjectionCapturedContentVisibilityChanged(boolean isVisible) {
+            fetchMediaProjectionManager();
+            if (mIMediaProjectionManager == null) {
+                return;
+            }
+            try {
+                mIMediaProjectionManager.notifyActiveProjectionCapturedContentVisibilityChanged(
+                        isVisible);
+            } catch (RemoteException e) {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Unable to tell MediaProjectionManagerService about visibility change on "
+                                + "the active projection: %s",
+                        e);
+            }
+        }
+
         private void fetchMediaProjectionManager() {
             if (mIMediaProjectionManager != null) {
                 return;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4f81ee4..82237bb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -449,6 +449,7 @@
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
     private final DisplayPolicy mDisplayPolicy;
     private final DisplayRotation mDisplayRotation;
+    @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
     DisplayFrames mDisplayFrames;
 
     private boolean mInTouchMode;
@@ -530,7 +531,6 @@
     /** Remove this display when animation on it has completed. */
     private boolean mDeferredRemoval;
 
-    final DockedTaskDividerController mDividerControllerLocked;
     final PinnedTaskController mPinnedTaskController;
 
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
@@ -791,6 +791,12 @@
         }
     };
 
+    /**
+     * A lambda function to find the focused window of the given window.
+     *
+     * <p>The lambda returns true if a focused window was found, false otherwise. If a focused
+     * window is found it will be stored in <code>mTmpWindow</code>.
+     */
     private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
         final ActivityRecord focusedApp = mFocusedApp;
         ProtoLog.v(WM_DEBUG_FOCUS, "Looking for focus: %s, flags=%d, canReceive=%b, reason=%s",
@@ -1141,7 +1147,7 @@
         }
 
         mDisplayPolicy = new DisplayPolicy(mWmService, this);
-        mDisplayRotation = new DisplayRotation(mWmService, this);
+        mDisplayRotation = new DisplayRotation(mWmService, this, mDisplayInfo.address);
 
         mDeviceStateController = new DeviceStateController(mWmService.mContext, mWmService.mH,
                 newFoldState -> {
@@ -1163,7 +1169,6 @@
             mDisplayPolicy.systemReady();
         }
         mWindowCornerRadius = mDisplayPolicy.getWindowCornerRadius();
-        mDividerControllerLocked = new DockedTaskDividerController(this);
         mPinnedTaskController = new PinnedTaskController(mWmService, this);
 
         final Transaction pendingTransaction = getPendingTransaction();
@@ -1174,6 +1179,13 @@
         onDisplayChanged(this);
         updateDisplayAreaOrganizers();
 
+        mDisplayRotationCompatPolicy =
+                // Not checking DeviceConfig value here to allow enabling via DeviceConfig
+                // without the need to restart the device.
+                mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                            /* checkDeviceConfig */ false)
+                        ? new DisplayRotationCompatPolicy(this) : null;
+
         mInputMonitor = new InputMonitor(mWmService, this);
         mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
         mMinSizeOfResizeableTaskDp = getMinimalTaskSizeDp();
@@ -2596,10 +2608,6 @@
         }
     }
 
-    DockedTaskDividerController getDockedDividerController() {
-        return mDividerControllerLocked;
-    }
-
     PinnedTaskController getPinnedTaskController() {
         return mPinnedTaskController;
     }
@@ -2756,6 +2764,14 @@
             }
         }
 
+        if (mDisplayRotationCompatPolicy != null) {
+            int compatOrientation = mDisplayRotationCompatPolicy.getOrientation();
+            if (compatOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
+                mLastOrientationSource = null;
+                return compatOrientation;
+            }
+        }
+
         final int orientation = super.getOrientation();
 
         if (!handlesOrientationChangeFromDescendant(orientation)) {
@@ -3318,6 +3334,10 @@
         // on the next traversal if it's removed from RootWindowContainer child list.
         getPendingTransaction().apply();
         mWmService.mWindowPlacerLocked.requestTraversal();
+
+        if (mDisplayRotationCompatPolicy != null) {
+            mDisplayRotationCompatPolicy.dispose();
+        }
     }
 
     /** Returns true if a removal action is still being deferred. */
@@ -3725,6 +3745,14 @@
     }
 
     /**
+     * @see Display#FLAG_STEAL_TOP_FOCUS_DISABLED
+     * @return True if this display can become the top focused display, false otherwise.
+     */
+    boolean canStealTopFocus() {
+        return (mDisplayInfo.flags & Display.FLAG_STEAL_TOP_FOCUS_DISABLED) == 0;
+    }
+
+    /**
      * Looking for the focused window on this display if the top focused display hasn't been
      * found yet (topFocusedDisplayId is INVALID_DISPLAY) or per-display focused was allowed.
      *
@@ -3736,9 +3764,15 @@
                     ? findFocusedWindow() : null;
     }
 
+    /**
+     * Find the focused window of this DisplayContent. The search takes the state of the display
+     * content into account
+     * @return The focused window, null if none was found.
+     */
     WindowState findFocusedWindow() {
         mTmpWindow = null;
 
+        // mFindFocusedWindow will populate mTmpWindow with the new focused window when found.
         forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
 
         if (mTmpWindow == null) {
@@ -6491,15 +6525,6 @@
     }
 
     /**
-     * The MediaProjection instance is torn down.
-     */
-    @VisibleForTesting void stopMediaProjection() {
-        if (mContentRecorder != null) {
-            mContentRecorder.stopMediaProjection();
-        }
-    }
-
-    /**
      * Sets the incoming recording session. Should only be used when starting to record on
      * this display; stopping recording is handled separately when the display is destroyed.
      *
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 300deca..a68d7af 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -59,11 +59,6 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_LEFT;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_RIGHT;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_TOP;
-import static android.view.WindowManagerPolicyConstants.ALT_BAR_UNKNOWN;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
@@ -139,7 +134,6 @@
 import com.android.internal.widget.PointerLocationView;
 import com.android.server.LocalServices;
 import com.android.server.UiThread;
-import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
@@ -246,27 +240,6 @@
     @NavigationBarPosition
     private int mNavigationBarPosition = NAV_BAR_BOTTOM;
 
-    // Alternative status bar for when flexible insets mapping is used to place the status bar on
-    // another side of the screen.
-    private WindowState mStatusBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mStatusBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative navigation bar for when flexible insets mapping is used to place the navigation
-    // bar elsewhere on the screen.
-    private WindowState mNavigationBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mNavigationBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative climate bar for when flexible insets mapping is used to place a climate bar on
-    // the screen.
-    private WindowState mClimateBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mClimateBarAltPosition = ALT_BAR_UNKNOWN;
-    // Alternative extra nav bar for when flexible insets mapping is used to place an extra nav bar
-    // on the screen.
-    private WindowState mExtraNavBarAlt = null;
-    @WindowManagerPolicy.AltBarPosition
-    private int mExtraNavBarAltPosition = ALT_BAR_UNKNOWN;
-
     private final ArraySet<WindowState> mInsetsSourceWindowsExceptIme = new ArraySet<>();
 
     /** Apps which are controlling the appearance of system bars */
@@ -345,6 +318,15 @@
     private boolean mForceConsumeSystemBars;
     private boolean mForceShowSystemBars;
 
+    /**
+     * Windows that provides gesture insets. If multiple windows provide gesture insets at the same
+     * side, the window with the highest z-order wins.
+     */
+    private WindowState mLeftGestureHost;
+    private WindowState mTopGestureHost;
+    private WindowState mRightGestureHost;
+    private WindowState mBottomGestureHost;
+
     private boolean mShowingDream;
     private boolean mLastShowingDream;
     private boolean mDreamingLockscreen;
@@ -362,13 +344,9 @@
     private boolean mShouldAttachNavBarToAppDuringTransition;
 
     // -------- PolicyHandler --------
-    private static final int MSG_REQUEST_TRANSIENT_BARS = 2;
     private static final int MSG_ENABLE_POINTER_LOCATION = 4;
     private static final int MSG_DISABLE_POINTER_LOCATION = 5;
 
-    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
-    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
-
     private final GestureNavigationSettingsObserver mGestureNavigationSettingsObserver;
 
     private final WindowManagerInternal.AppTransitionListener mAppTransitionListener;
@@ -385,15 +363,6 @@
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_REQUEST_TRANSIENT_BARS:
-                    synchronized (mLock) {
-                        WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
-                                ? getStatusBar() : getNavigationBar();
-                        if (targetBar != null) {
-                            requestTransientBars(targetBar, true /* isGestureOnSystemBar */);
-                        }
-                    }
-                    break;
                 case MSG_ENABLE_POINTER_LOCATION:
                     enablePointerLocation();
                     break;
@@ -438,35 +407,57 @@
         mSystemGestures = new SystemGesturesPointerEventListener(mUiContext, mHandler,
                 new SystemGesturesPointerEventListener.Callbacks() {
 
+                    private static final long MOUSE_GESTURE_DELAY_MS = 500;
+
+                    private Runnable mOnSwipeFromLeft = this::onSwipeFromLeft;
+                    private Runnable mOnSwipeFromTop = this::onSwipeFromTop;
+                    private Runnable mOnSwipeFromRight = this::onSwipeFromRight;
+                    private Runnable mOnSwipeFromBottom = this::onSwipeFromBottom;
+
+                    private Insets getControllableInsets(WindowState win) {
+                        if (win == null) {
+                            return Insets.NONE;
+                        }
+                        final InsetsSourceProvider provider = win.getControllableInsetProvider();
+                        if (provider == null) {
+                            return  Insets.NONE;
+                        }
+                        return provider.getSource().calculateInsets(win.getBounds(),
+                                true /* ignoreVisibility */);
+                    }
+
                     @Override
                     public void onSwipeFromTop() {
                         synchronized (mLock) {
-                            final WindowState bar = mStatusBar != null
-                                    ? mStatusBar
-                                    : findAltBarMatchingPosition(ALT_BAR_TOP);
-                            requestTransientBars(bar, true /* isGestureOnSystemBar */);
+                            requestTransientBars(mTopGestureHost,
+                                    getControllableInsets(mTopGestureHost).top > 0);
                         }
                     }
 
                     @Override
                     public void onSwipeFromBottom() {
                         synchronized (mLock) {
-                            final WindowState bar = mNavigationBar != null
-                                        && mNavigationBarPosition == NAV_BAR_BOTTOM
-                                    ? mNavigationBar
-                                    : findAltBarMatchingPosition(ALT_BAR_BOTTOM);
-                            requestTransientBars(bar, true /* isGestureOnSystemBar */);
+                            requestTransientBars(mBottomGestureHost,
+                                    getControllableInsets(mBottomGestureHost).bottom > 0);
                         }
                     }
 
+                    private boolean allowsSideSwipe(Region excludedRegion) {
+                        return mNavigationBarAlwaysShowOnSideGesture
+                                && !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
+                    }
+
                     @Override
                     public void onSwipeFromRight() {
                         final Region excludedRegion = Region.obtain();
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_RIGHT,
-                                    ALT_BAR_RIGHT);
+                            final boolean hasWindow =
+                                    getControllableInsets(mRightGestureHost).right > 0;
+                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                                requestTransientBars(mRightGestureHost, hasWindow);
+                            }
                         }
                         excludedRegion.recycle();
                     }
@@ -477,33 +468,15 @@
                         synchronized (mLock) {
                             mDisplayContent.calculateSystemGestureExclusion(
                                     excludedRegion, null /* outUnrestricted */);
-                            requestTransientBarsForSideSwipe(excludedRegion, NAV_BAR_LEFT,
-                                    ALT_BAR_LEFT);
+                            final boolean hasWindow =
+                                    getControllableInsets(mLeftGestureHost).left > 0;
+                            if (hasWindow || allowsSideSwipe(excludedRegion)) {
+                                requestTransientBars(mLeftGestureHost, hasWindow);
+                            }
                         }
                         excludedRegion.recycle();
                     }
 
-                    private void requestTransientBarsForSideSwipe(Region excludedRegion,
-                            int navBarSide, int altBarSide) {
-                        final WindowState barMatchingSide = mNavigationBar != null
-                                        && mNavigationBarPosition == navBarSide
-                                ? mNavigationBar
-                                : findAltBarMatchingPosition(altBarSide);
-                        final boolean allowSideSwipe = mNavigationBarAlwaysShowOnSideGesture &&
-                                !mSystemGestures.currentGestureStartedInRegion(excludedRegion);
-                        if (barMatchingSide == null && !allowSideSwipe) {
-                            return;
-                        }
-
-                        // Request transient bars on the matching bar, or any bar if we always allow
-                        // side swipes to show the bars
-                        final boolean isGestureOnSystemBar = barMatchingSide != null;
-                        final WindowState bar = barMatchingSide != null
-                                ? barMatchingSide
-                                : findTransientNavOrAltBar();
-                        requestTransientBars(bar, isGestureOnSystemBar);
-                    }
-
                     @Override
                     public void onFling(int duration) {
                         if (mService.mPowerManagerInternal != null) {
@@ -539,24 +512,47 @@
                     }
 
                     @Override
+                    public void onMouseHoverAtLeft() {
+                        mHandler.removeCallbacks(mOnSwipeFromLeft);
+                        mHandler.postDelayed(mOnSwipeFromLeft, MOUSE_GESTURE_DELAY_MS);
+                    }
+
+                    @Override
                     public void onMouseHoverAtTop() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
-                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
-                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS;
-                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                        mHandler.removeCallbacks(mOnSwipeFromTop);
+                        mHandler.postDelayed(mOnSwipeFromTop, MOUSE_GESTURE_DELAY_MS);
+                    }
+
+                    @Override
+                    public void onMouseHoverAtRight() {
+                        mHandler.removeCallbacks(mOnSwipeFromRight);
+                        mHandler.postDelayed(mOnSwipeFromRight, MOUSE_GESTURE_DELAY_MS);
                     }
 
                     @Override
                     public void onMouseHoverAtBottom() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
-                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
-                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION;
-                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                        mHandler.removeCallbacks(mOnSwipeFromBottom);
+                        mHandler.postDelayed(mOnSwipeFromBottom, MOUSE_GESTURE_DELAY_MS);
                     }
 
                     @Override
-                    public void onMouseLeaveFromEdge() {
-                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
+                    public void onMouseLeaveFromLeft() {
+                        mHandler.removeCallbacks(mOnSwipeFromLeft);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromTop() {
+                        mHandler.removeCallbacks(mOnSwipeFromTop);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromRight() {
+                        mHandler.removeCallbacks(mOnSwipeFromRight);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromBottom() {
+                        mHandler.removeCallbacks(mOnSwipeFromBottom);
                     }
                 });
         displayContent.registerPointerEventListener(mSystemGestures);
@@ -666,41 +662,6 @@
         }
     }
 
-    /**
-     * Returns the first non-null alt bar window matching the given position.
-     */
-    private WindowState findAltBarMatchingPosition(@WindowManagerPolicy.AltBarPosition int pos) {
-        if (mStatusBarAlt != null && mStatusBarAltPosition == pos) {
-            return mStatusBarAlt;
-        }
-        if (mNavigationBarAlt != null && mNavigationBarAltPosition == pos) {
-            return mNavigationBarAlt;
-        }
-        if (mClimateBarAlt != null && mClimateBarAltPosition == pos) {
-            return mClimateBarAlt;
-        }
-        if (mExtraNavBarAlt != null && mExtraNavBarAltPosition == pos) {
-            return mExtraNavBarAlt;
-        }
-        return null;
-    }
-
-    /**
-     * Finds the first non-null nav bar to request transient for.
-     */
-    private WindowState findTransientNavOrAltBar() {
-        if (mNavigationBar != null) {
-            return mNavigationBar;
-        }
-        if (mNavigationBarAlt != null) {
-            return mNavigationBarAlt;
-        }
-        if (mExtraNavBarAlt != null) {
-            return mExtraNavBarAlt;
-        }
-        return null;
-    }
-
     void systemReady() {
         mSystemGestures.systemReady();
         if (mService.mPointerLocationEnabled) {
@@ -970,20 +931,6 @@
             attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
         }
 
-        // Check if alternate bars positions were updated.
-        if (mStatusBarAlt == win) {
-            mStatusBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mNavigationBarAlt == win) {
-            mNavigationBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mClimateBarAlt == win) {
-            mClimateBarAltPosition = getAltBarPosition(attrs);
-        }
-        if (mExtraNavBarAlt == win) {
-            mExtraNavBarAltPosition = getAltBarPosition(attrs);
-        }
-
         final InsetsSourceProvider provider = win.getControllableInsetProvider();
         if (provider != null && provider.getSource().getInsetsRoundedCornerFrame()
                 != attrs.insetsRoundedCornerFrame) {
@@ -1035,8 +982,7 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if ((mStatusBar != null && mStatusBar.isAlive())
-                        || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
+                if (mStatusBar != null && mStatusBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
@@ -1054,8 +1000,7 @@
                 mContext.enforcePermission(
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
-                if ((mNavigationBar != null && mNavigationBar.isAlive())
-                        || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
+                if (mNavigationBar != null && mNavigationBar.isAlive()) {
                     return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
                 }
                 break;
@@ -1086,34 +1031,6 @@
                         "DisplayPolicy");
             }
             enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providedInsets);
-
-            for (InsetsFrameProvider provider : attrs.providedInsets) {
-                @InternalInsetsType int insetsType = provider.type;
-                switch (insetsType) {
-                    case ITYPE_STATUS_BAR:
-                        if ((mStatusBar != null && mStatusBar.isAlive())
-                                || (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_NAVIGATION_BAR:
-                        if ((mNavigationBar != null && mNavigationBar.isAlive())
-                                || (mNavigationBarAlt != null && mNavigationBarAlt.isAlive())) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_CLIMATE_BAR:
-                        if (mClimateBarAlt != null && mClimateBarAlt.isAlive()) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                    case ITYPE_EXTRA_NAVIGATION_BAR:
-                        if (mExtraNavBarAlt != null && mExtraNavBarAlt.isAlive()) {
-                            return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
-                        }
-                        break;
-                }
-            }
         }
         return ADD_OKAY;
     }
@@ -1139,28 +1056,6 @@
         if (attrs.providedInsets != null) {
             for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
                 final InsetsFrameProvider provider = attrs.providedInsets[i];
-                switch (provider.type) {
-                    case ITYPE_STATUS_BAR:
-                        if (attrs.type != TYPE_STATUS_BAR) {
-                            mStatusBarAlt = win;
-                            mStatusBarAltPosition = getAltBarPosition(attrs);
-                        }
-                        break;
-                    case ITYPE_NAVIGATION_BAR:
-                        if (attrs.type != TYPE_NAVIGATION_BAR) {
-                            mNavigationBarAlt = win;
-                            mNavigationBarAltPosition = getAltBarPosition(attrs);
-                        }
-                        break;
-                    case ITYPE_CLIMATE_BAR:
-                        mClimateBarAlt = win;
-                        mClimateBarAltPosition = getAltBarPosition(attrs);
-                        break;
-                    case ITYPE_EXTRA_NAVIGATION_BAR:
-                        mExtraNavBarAlt = win;
-                        mExtraNavBarAltPosition = getAltBarPosition(attrs);
-                        break;
-                }
                 // The index of the provider and corresponding insets types cannot change at
                 // runtime as ensured in WMS. Make use of the index in the provider directly
                 // to access the latest provided size at runtime.
@@ -1217,22 +1112,6 @@
         };
     }
 
-    @WindowManagerPolicy.AltBarPosition
-    private int getAltBarPosition(WindowManager.LayoutParams params) {
-        switch (params.gravity) {
-            case Gravity.LEFT:
-                return ALT_BAR_LEFT;
-            case Gravity.RIGHT:
-                return ALT_BAR_RIGHT;
-            case Gravity.BOTTOM:
-                return ALT_BAR_BOTTOM;
-            case Gravity.TOP:
-                return ALT_BAR_TOP;
-            default:
-                return ALT_BAR_UNKNOWN;
-        }
-    }
-
     TriConsumer<DisplayFrames, WindowContainer, Rect> getImeSourceFrameProvider() {
         return (displayFrames, windowContainer, inOutFrame) -> {
             WindowState windowState = windowContainer.asWindowState();
@@ -1280,32 +1159,27 @@
      * @param win The window being removed.
      */
     void removeWindowLw(WindowState win) {
-        if (mStatusBar == win || mStatusBarAlt == win) {
+        if (mStatusBar == win) {
             mStatusBar = null;
-            mStatusBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_STATUS_BAR, null, null);
-        } else if (mNavigationBar == win || mNavigationBarAlt == win) {
+        } else if (mNavigationBar == win) {
             mNavigationBar = null;
-            mNavigationBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, null, null);
         } else if (mNotificationShade == win) {
             mNotificationShade = null;
-        } else if (mClimateBarAlt == win) {
-            mClimateBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_CLIMATE_BAR, null, null);
-        } else if (mExtraNavBarAlt == win) {
-            mExtraNavBarAlt = null;
-            mDisplayContent.setInsetProvider(ITYPE_EXTRA_NAVIGATION_BAR, null, null);
         }
         if (mLastFocusedWindow == win) {
             mLastFocusedWindow = null;
         }
 
+        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
+        for (int index = sources.size() - 1; index >= 0; index--) {
+            final @InternalInsetsType int type = sources.keyAt(index);
+            mDisplayContent.setInsetProvider(type, null /* win */, null /* frameProvider */);
+        }
         mInsetsSourceWindowsExceptIme.remove(win);
     }
 
     WindowState getStatusBar() {
-        return mStatusBar != null ? mStatusBar : mStatusBarAlt;
+        return mStatusBar;
     }
 
     WindowState getNotificationShade() {
@@ -1313,7 +1187,7 @@
     }
 
     WindowState getNavigationBar() {
-        return mNavigationBar != null ? mNavigationBar : mNavigationBarAlt;
+        return mNavigationBar;
     }
 
     /**
@@ -1439,6 +1313,10 @@
      * Called following layout of all windows before each window has policy applied.
      */
     public void beginPostLayoutPolicyLw() {
+        mLeftGestureHost = null;
+        mTopGestureHost = null;
+        mRightGestureHost = null;
+        mBottomGestureHost = null;
         mTopFullscreenOpaqueWindowState = null;
         mNavBarColorWindowCandidate = null;
         mNavBarBackgroundWindow = null;
@@ -1480,6 +1358,33 @@
             mIsFreeformWindowOverlappingWithNavBar = true;
         }
 
+        final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
+        final Rect bounds = win.getBounds();
+        for (int index = sources.size() - 1; index >= 0; index--) {
+            final InsetsSource source = sources.valueAt(index);
+            if ((source.getType()
+                    & (Type.systemGestures() | Type.mandatorySystemGestures())) == 0) {
+                continue;
+            }
+            if (mLeftGestureHost != null && mTopGestureHost != null
+                    && mRightGestureHost != null && mBottomGestureHost != null) {
+                continue;
+            }
+            final Insets insets = source.calculateInsets(bounds, false /* ignoreVisibility */);
+            if (mLeftGestureHost == null && insets.left > 0) {
+                mLeftGestureHost = win;
+            }
+            if (mTopGestureHost == null && insets.top > 0) {
+                mTopGestureHost = win;
+            }
+            if (mRightGestureHost == null && insets.right > 0) {
+                mRightGestureHost = win;
+            }
+            if (mBottomGestureHost == null && insets.bottom > 0) {
+                mBottomGestureHost = win;
+            }
+        }
+
         if (!affectsSystemUi) {
             return;
         }
@@ -2588,11 +2493,6 @@
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar="); pw.println(mStatusBar);
         }
-        if (mStatusBarAlt != null) {
-            pw.print(prefix); pw.print("mStatusBarAlt="); pw.println(mStatusBarAlt);
-            pw.print(prefix); pw.print("mStatusBarAltPosition=");
-            pw.println(mStatusBarAltPosition);
-        }
         if (mNotificationShade != null) {
             pw.print(prefix); pw.print("mExpandedPanel="); pw.println(mNotificationShade);
         }
@@ -2604,20 +2504,17 @@
             pw.print(prefix); pw.print("mNavigationBarPosition=");
             pw.println(mNavigationBarPosition);
         }
-        if (mNavigationBarAlt != null) {
-            pw.print(prefix); pw.print("mNavigationBarAlt="); pw.println(mNavigationBarAlt);
-            pw.print(prefix); pw.print("mNavigationBarAltPosition=");
-            pw.println(mNavigationBarAltPosition);
+        if (mLeftGestureHost != null) {
+            pw.print(prefix); pw.print("mLeftGestureHost="); pw.println(mLeftGestureHost);
         }
-        if (mClimateBarAlt != null) {
-            pw.print(prefix); pw.print("mClimateBarAlt="); pw.println(mClimateBarAlt);
-            pw.print(prefix); pw.print("mClimateBarAltPosition=");
-            pw.println(mClimateBarAltPosition);
+        if (mTopGestureHost != null) {
+            pw.print(prefix); pw.print("mTopGestureHost="); pw.println(mTopGestureHost);
         }
-        if (mExtraNavBarAlt != null) {
-            pw.print(prefix); pw.print("mExtraNavBarAlt="); pw.println(mExtraNavBarAlt);
-            pw.print(prefix); pw.print("mExtraNavBarAltPosition=");
-            pw.println(mExtraNavBarAltPosition);
+        if (mRightGestureHost != null) {
+            pw.print(prefix); pw.print("mRightGestureHost="); pw.println(mRightGestureHost);
+        }
+        if (mBottomGestureHost != null) {
+            pw.print(prefix); pw.print("mBottomGestureHost="); pw.println(mBottomGestureHost);
         }
         if (mFocusedWindow != null) {
             pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 185e06e..cf3a688 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.util.RotationUtils.deltaRotation;
@@ -55,9 +56,11 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
+import android.view.DisplayAddress;
 import android.view.IWindowManager;
 import android.view.Surface;
 import android.window.TransitionRequestInfo;
@@ -75,6 +78,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayDeque;
+import java.util.Set;
 
 /**
  * Defines the mapping between orientation and rotation of a display.
@@ -211,15 +215,16 @@
     private boolean mDemoHdmiRotationLock;
     private boolean mDemoRotationLock;
 
-    DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
-        this(service, displayContent, displayContent.getDisplayPolicy(),
+    DisplayRotation(WindowManagerService service, DisplayContent displayContent,
+            DisplayAddress displayAddress) {
+        this(service, displayContent, displayAddress, displayContent.getDisplayPolicy(),
                 service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
     }
 
     @VisibleForTesting
     DisplayRotation(WindowManagerService service, DisplayContent displayContent,
-            DisplayPolicy displayPolicy, DisplayWindowSettings displayWindowSettings,
-            Context context, Object lock) {
+            DisplayAddress displayAddress, DisplayPolicy displayPolicy,
+            DisplayWindowSettings displayWindowSettings, Context context, Object lock) {
         mService = service;
         mDisplayContent = displayContent;
         mDisplayPolicy = displayPolicy;
@@ -235,6 +240,8 @@
         mDeskDockRotation = readRotation(R.integer.config_deskDockRotation);
         mUndockedHdmiRotation = readRotation(R.integer.config_undockedHdmiRotation);
 
+        mRotation = readDefaultDisplayRotation(displayAddress);
+
         if (isDefaultDisplay) {
             final Handler uiHandler = UiThread.getHandler();
             mOrientationListener = new OrientationListener(mContext, uiHandler);
@@ -248,6 +255,33 @@
         }
     }
 
+    // Change the default value to the value specified in the sysprop
+    // ro.bootanim.set_orientation_<display_id>. Four values are supported: ORIENTATION_0,
+    // ORIENTATION_90, ORIENTATION_180 and ORIENTATION_270.
+    // If the value isn't specified or is ORIENTATION_0, nothing will be changed.
+    // This is needed to support having default orientation different from the natural
+    // device orientation. For example, on tablets that may want to keep natural orientation
+    // portrait for applications compatibility but have landscape orientation as a default choice
+    // from the UX perspective.
+    @Surface.Rotation
+    private int readDefaultDisplayRotation(DisplayAddress displayAddress) {
+        if (!(displayAddress instanceof DisplayAddress.Physical)) {
+            return Surface.ROTATION_0;
+        }
+        final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) displayAddress;
+        String syspropValue = SystemProperties.get(
+                "ro.bootanim.set_orientation_" + physicalAddress.getPhysicalDisplayId(),
+                "ORIENTATION_0");
+        if (syspropValue.equals("ORIENTATION_90")) {
+            return Surface.ROTATION_90;
+        } else if (syspropValue.equals("ORIENTATION_180")) {
+            return Surface.ROTATION_180;
+        } else if (syspropValue.equals("ORIENTATION_270")) {
+            return Surface.ROTATION_270;
+        }
+        return Surface.ROTATION_0;
+    }
+
     private int readRotation(int resID) {
         try {
             final int rotation = mContext.getResources().getInteger(resID);
@@ -1521,6 +1555,15 @@
         proto.end(token);
     }
 
+    boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+        if (mFoldController == null) return false;
+        return mFoldController.isDeviceInPosture(state, isTabletop);
+    }
+
+    boolean isDisplaySeparatingHinge() {
+        return mFoldController != null && mFoldController.isSeparatingHinge();
+    }
+
     /**
      * Called by the DeviceStateManager callback when the device state changes.
      */
@@ -1538,6 +1581,63 @@
         private DeviceStateController.FoldState mFoldState =
                 DeviceStateController.FoldState.UNKNOWN;
         private boolean mInHalfFoldTransition = false;
+        private final boolean mIsDisplayAlwaysSeparatingHinge;
+        private final Set<Integer> mTabletopRotations;
+
+        FoldController() {
+            mTabletopRotations = new ArraySet<>();
+            int[] tabletop_rotations = mContext.getResources().getIntArray(
+                    R.array.config_deviceTabletopRotations);
+            if (tabletop_rotations != null) {
+                for (int angle : tabletop_rotations) {
+                    switch (angle) {
+                        case 0:
+                            mTabletopRotations.add(Surface.ROTATION_0);
+                            break;
+                        case 90:
+                            mTabletopRotations.add(Surface.ROTATION_90);
+                            break;
+                        case 180:
+                            mTabletopRotations.add(Surface.ROTATION_180);
+                            break;
+                        case 270:
+                            mTabletopRotations.add(Surface.ROTATION_270);
+                            break;
+                        default:
+                            ProtoLog.e(WM_DEBUG_ORIENTATION,
+                                    "Invalid surface rotation angle in "
+                                            + "config_deviceTabletopRotations: %d",
+                                    angle);
+                    }
+                }
+            } else {
+                ProtoLog.w(WM_DEBUG_ORIENTATION,
+                        "config_deviceTabletopRotations is not defined. Half-fold "
+                                + "letterboxing will work inconsistently.");
+            }
+            mIsDisplayAlwaysSeparatingHinge = mContext.getResources().getBoolean(
+                    R.bool.config_isDisplayHingeAlwaysSeparating);
+        }
+
+        boolean isDeviceInPosture(DeviceStateController.FoldState state, boolean isTabletop) {
+            if (state != mFoldState) {
+                return false;
+            }
+            if (mFoldState == DeviceStateController.FoldState.HALF_FOLDED) {
+                return !(isTabletop ^ mTabletopRotations.contains(mRotation));
+            }
+            return true;
+        }
+
+        DeviceStateController.FoldState getFoldState() {
+            return mFoldState;
+        }
+
+        boolean isSeparatingHinge() {
+            return mFoldState == DeviceStateController.FoldState.HALF_FOLDED
+                    || (mFoldState == DeviceStateController.FoldState.OPEN
+                        && mIsDisplayAlwaysSeparatingHinge);
+        }
 
         boolean overrideFrozenRotation() {
             return mFoldState == DeviceStateController.FoldState.HALF_FOLDED;
@@ -1586,6 +1686,15 @@
                 mService.updateRotation(false /* alwaysSendConfiguration */,
                         false /* forceRelayout */);
             }
+            // Alert the activity of possible new bounds.
+            final Task topFullscreenTask =
+                    mDisplayContent.getTask(t -> t.getWindowingMode() == WINDOWING_MODE_FULLSCREEN);
+            if (topFullscreenTask != null) {
+                final ActivityRecord top = topFullscreenTask.topRunningActivity();
+                if (top != null) {
+                    top.recomputeConfiguration();
+                }
+            }
         }
     }
 
@@ -1696,6 +1805,7 @@
             final int mHalfFoldSavedRotation;
             final boolean mInHalfFoldTransition;
             final DeviceStateController.FoldState mFoldState;
+            @Nullable final String mDisplayRotationCompatPolicySummary;
 
             Record(DisplayRotation dr, int fromRotation, int toRotation) {
                 mFromRotation = fromRotation;
@@ -1730,6 +1840,10 @@
                     mInHalfFoldTransition = false;
                     mFoldState = DeviceStateController.FoldState.UNKNOWN;
                 }
+                mDisplayRotationCompatPolicySummary = dc.mDisplayRotationCompatPolicy == null
+                        ? null
+                        : dc.mDisplayRotationCompatPolicy
+                                .getSummaryForDisplayRotationHistoryRecord();
             }
 
             void dump(String prefix, PrintWriter pw) {
@@ -1752,6 +1866,9 @@
                             + " mInHalfFoldTransition=" + mInHalfFoldTransition
                             + " mFoldState=" + mFoldState);
                 }
+                if (mDisplayRotationCompatPolicySummary != null) {
+                    pw.println(prefix + mDisplayRotationCompatPolicySummary);
+                }
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
new file mode 100644
index 0000000..18c5c3b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -0,0 +1,433 @@
+/*
+ * 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.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.screenOrientationToString;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
+import static android.view.Display.TYPE_INTERNAL;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Controls camera compatibility treatment that handles orientation mismatch between camera
+ * buffers and an app window for a particular display that can lead to camera issues like sideways
+ * or stretched viewfinder.
+ *
+ * <p>This includes force rotation of fixed orientation activities connected to the camera.
+ *
+ * <p>The treatment is enabled for internal displays that have {@code ignoreOrientationRequest}
+ * display setting enabled and when {@code
+ * R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+ */
+ // TODO(b/261444714): Consider moving Camera-specific logic outside of the WM Core path
+final class DisplayRotationCompatPolicy {
+
+    // Delay for updating display rotation after Camera connection is closed. Needed to avoid
+    // rotation flickering when an app is flipping between front and rear cameras or when size
+    // compat mode is restarted.
+    // TODO(b/263114289): Consider associating this delay with a specific activity so that if
+    // the new non-camera activity started on top of the camer one we can rotate faster.
+    private static final int CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS = 2000;
+    // Delay for updating display rotation after Camera connection is opened. This delay is
+    // selected to be long enough to avoid conflicts with transitions on the app's side.
+    // Using a delay < CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS to avoid flickering when an app
+    // is flipping between front and rear cameras (in case requested orientation changes at
+    // runtime at the same time) or when size compat mode is restarted.
+    private static final int CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS =
+            CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS / 2;
+    // Delay for ensuring that onActivityRefreshed is always called after an activity refresh. The
+    // client process may not always report the event back to the server, such as process is
+    // crashed or got killed.
+    private static final int REFRESH_CALLBACK_TIMEOUT_MS = 2000;
+
+    private final DisplayContent mDisplayContent;
+    private final WindowManagerService mWmService;
+    private final CameraManager mCameraManager;
+    private final Handler mHandler;
+
+    // Bi-directional map between package names and active camera IDs since we need to 1) get a
+    // camera id by a package name when determining rotation; 2) get a package name by a camera id
+    // when camera connection is closed and we need to clean up our records.
+    @GuardedBy("this")
+    private final CameraIdPackageNameBiMap mCameraIdPackageBiMap = new CameraIdPackageNameBiMap();
+    @GuardedBy("this")
+    private final Set<String> mScheduledToBeRemovedCameraIdSet = new ArraySet<>();
+    @GuardedBy("this")
+    private final Set<String> mScheduledOrientationUpdateCameraIdSet = new ArraySet<>();
+
+    private final CameraManager.AvailabilityCallback mAvailabilityCallback =
+            new  CameraManager.AvailabilityCallback() {
+                @Override
+                public void onCameraOpened(@NonNull String cameraId, @NonNull String packageId) {
+                    notifyCameraOpened(cameraId, packageId);
+                }
+
+                @Override
+                public void onCameraClosed(@NonNull String cameraId) {
+                    notifyCameraClosed(cameraId);
+                }
+            };
+
+    @ScreenOrientation
+    private int mLastReportedOrientation = SCREEN_ORIENTATION_UNSET;
+
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent) {
+        this(displayContent, displayContent.mWmService.mH);
+    }
+
+    @VisibleForTesting
+    DisplayRotationCompatPolicy(@NonNull DisplayContent displayContent, Handler handler) {
+        // This constructor is called from DisplayContent constructor. Don't use any fields in
+        // DisplayContent here since they aren't guaranteed to be set.
+        mHandler = handler;
+        mDisplayContent = displayContent;
+        mWmService = displayContent.mWmService;
+        mCameraManager = mWmService.mContext.getSystemService(CameraManager.class);
+        mCameraManager.registerAvailabilityCallback(
+                mWmService.mContext.getMainExecutor(), mAvailabilityCallback);
+    }
+
+    void dispose() {
+        mCameraManager.unregisterAvailabilityCallback(mAvailabilityCallback);
+    }
+
+    /**
+     * Determines orientation for Camera compatibility.
+     *
+     * <p>The goal of this function is to compute a orientation which would align orientations of
+     * portrait app window and natural orientation of the device and set opposite to natural
+     * orientation for a landscape app window. This is one of the strongest assumptions that apps
+     * make when they implement camera previews. Since app and natural display orientations aren't
+     * guaranteed to match, the rotation can cause letterboxing.
+     *
+     * <p>If treatment isn't applicable returns {@link SCREEN_ORIENTATION_UNSPECIFIED}. See {@link
+     * #shouldComputeCameraCompatOrientation} for conditions enabling the treatment.
+     */
+    @ScreenOrientation
+    int getOrientation() {
+        mLastReportedOrientation = getOrientationInternal();
+        return mLastReportedOrientation;
+    }
+
+    @ScreenOrientation
+    private synchronized int getOrientationInternal() {
+        if (!isTreatmentEnabledForDisplay()) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        if (!isTreatmentEnabledForActivity(topActivity)) {
+            return SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+        boolean isPortraitActivity =
+                topActivity.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+        boolean isNaturalDisplayOrientationPortrait =
+                mDisplayContent.getNaturalOrientation() == ORIENTATION_PORTRAIT;
+        // Rotate portrait-only activity in the natural orientation of the displays (and in the
+        // opposite to natural orientation for landscape-only) since many apps assume that those
+        // are aligned when they compute orientation of the preview.
+        // This means that even for a landscape-only activity and a device with landscape natural
+        // orientation this would return SCREEN_ORIENTATION_PORTRAIT because an assumption that
+        // natural orientation = portrait window = portait camera is the main wrong assumption
+        // that apps make when they implement camera previews so landscape windows need be
+        // rotated in the orientation oposite to the natural one even if it's portrait.
+        // TODO(b/261475895): Consider allowing more rotations for "sensor" and "user" versions
+        // of the portrait and landscape orientation requests.
+        int orientation = (isPortraitActivity && isNaturalDisplayOrientationPortrait)
+                || (!isPortraitActivity && !isNaturalDisplayOrientationPortrait)
+                        ? SCREEN_ORIENTATION_PORTRAIT
+                        : SCREEN_ORIENTATION_LANDSCAPE;
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is ignoring all orientation requests, camera is active "
+                        + "and the top activity is eligible for force rotation, return %s,"
+                        + "portrait activity: %b, is natural orientation portrait: %b.",
+                mDisplayContent.mDisplayId, screenOrientationToString(orientation),
+                isPortraitActivity, isNaturalDisplayOrientationPortrait);
+        return orientation;
+    }
+
+    /**
+     * "Refreshes" activity by going through "stopped -> resumed" or "paused -> resumed" cycle.
+     * This allows to clear cached values in apps (e.g. display or camera rotation) that influence
+     * camera preview and can lead to sideways or stretching issues persisting even after force
+     * rotation.
+     */
+    void onActivityConfigurationChanging(ActivityRecord activity, Configuration newConfig,
+            Configuration lastReportedConfig) {
+        if (!isTreatmentEnabledForDisplay()
+                || !mWmService.mLetterboxConfiguration.isCameraCompatRefreshEnabled()
+                || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) {
+            return;
+        }
+        boolean cycleThroughStop = mWmService.mLetterboxConfiguration
+                .isCameraCompatRefreshCycleThroughStopEnabled();
+        try {
+            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true);
+            ProtoLog.v(WM_DEBUG_STATES,
+                    "Refershing activity for camera compatibility treatment, "
+                            + "activityRecord=%s", activity);
+            final ClientTransaction transaction = ClientTransaction.obtain(
+                    activity.app.getThread(), activity.token);
+            transaction.addCallback(
+                    RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+            transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+            activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+            mHandler.postDelayed(
+                    () -> onActivityRefreshed(activity),
+                    REFRESH_CALLBACK_TIMEOUT_MS);
+        } catch (RemoteException e) {
+            activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+        }
+    }
+
+    void onActivityRefreshed(@NonNull ActivityRecord activity) {
+        activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(false);
+    }
+
+    String getSummaryForDisplayRotationHistoryRecord() {
+        String summaryIfEnabled = "";
+        if (isTreatmentEnabledForDisplay()) {
+            ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                    /* considerKeyguardState= */ true);
+            summaryIfEnabled =
+                    " mLastReportedOrientation="
+                            + screenOrientationToString(mLastReportedOrientation)
+                    + " topActivity="
+                            + (topActivity == null ? "null" : topActivity.shortComponentName)
+                    + " isTreatmentEnabledForActivity="
+                            + isTreatmentEnabledForActivity(topActivity)
+                    + " CameraIdPackageNameBiMap="
+                            + mCameraIdPackageBiMap.getSummaryForDisplayRotationHistoryRecord();
+        }
+        return "DisplayRotationCompatPolicy{"
+                + " isTreatmentEnabledForDisplay=" + isTreatmentEnabledForDisplay()
+                + summaryIfEnabled
+                + " }";
+    }
+
+    // Refreshing only when configuration changes after rotation.
+    private boolean shouldRefreshActivity(ActivityRecord activity, Configuration newConfig,
+            Configuration lastReportedConfig) {
+        return newConfig.windowConfiguration.getDisplayRotation()
+                        != lastReportedConfig.windowConfiguration.getDisplayRotation()
+                && isTreatmentEnabledForActivity(activity);
+    }
+
+    /**
+     * Whether camera compat treatment is enabled for the display.
+     *
+     * <p>Conditions that need to be met:
+     * <ul>
+     *     <li>{@code R.bool.config_isWindowManagerCameraCompatTreatmentEnabled} is {@code true}.
+     *     <li>Setting {@code ignoreOrientationRequest} is enabled for the display.
+     *     <li>Associated {@link DisplayContent} is for internal display. See b/225928882
+     *     that tracks supporting external displays in the future.
+     * </ul>
+     */
+    private boolean isTreatmentEnabledForDisplay() {
+        return mWmService.mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ true)
+                && mDisplayContent.getIgnoreOrientationRequest()
+                // TODO(b/225928882): Support camera compat rotation for external displays
+                && mDisplayContent.getDisplay().getType() == TYPE_INTERNAL;
+    }
+
+    /**
+     * Whether camera compat treatment is applicable for the given activity.
+     *
+     * <p>Conditions that need to be met:
+     * <ul>
+     *     <li>{@link #isCameraActiveForPackage} is {@code true} for the activity.
+     *     <li>The activity is in fullscreen
+     *     <li>The activity has fixed orientation but not "locked" or "nosensor" one.
+     * </ul>
+     */
+    private boolean isTreatmentEnabledForActivity(@Nullable ActivityRecord activity) {
+        return activity != null && !activity.inMultiWindowMode()
+                && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED
+                // "locked" and "nosensor" values are often used by camera apps that can't
+                // handle dynamic changes so we shouldn't force rotate them.
+                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR
+                && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED
+                && mCameraIdPackageBiMap.containsPackageName(activity.packageName);
+    }
+
+    private synchronized void notifyCameraOpened(
+            @NonNull String cameraId, @NonNull String packageName) {
+        // If an activity is restarting or camera is flipping, the camera connection can be
+        // quickly closed and reopened.
+        mScheduledToBeRemovedCameraIdSet.remove(cameraId);
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is open for package %s",
+                mDisplayContent.mDisplayId, cameraId, packageName);
+        // Some apps can’t handle configuration changes coming at the same time with Camera setup
+        // so delaying orientation update to accomadate for that.
+        mScheduledOrientationUpdateCameraIdSet.add(cameraId);
+        mHandler.postDelayed(
+                () ->  delayedUpdateOrientationWithWmLock(cameraId, packageName),
+                CAMERA_OPENED_ROTATION_UPDATE_DELAY_MS);
+    }
+
+    private void updateOrientationWithWmLock() {
+        synchronized (mWmService.mGlobalLock) {
+            mDisplayContent.updateOrientation();
+        }
+    }
+
+    private void delayedUpdateOrientationWithWmLock(
+            @NonNull String cameraId, @NonNull String packageName) {
+        synchronized (this) {
+            if (!mScheduledOrientationUpdateCameraIdSet.remove(cameraId)) {
+                // Orientation update has happened already or was cancelled because
+                // camera was closed.
+                return;
+            }
+            mCameraIdPackageBiMap.put(packageName, cameraId);
+        }
+        updateOrientationWithWmLock();
+    }
+
+    private synchronized void notifyCameraClosed(@NonNull String cameraId) {
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is closed, scheduling rotation update.",
+                mDisplayContent.mDisplayId, cameraId);
+        mScheduledToBeRemovedCameraIdSet.add(cameraId);
+        // No need to update orientation for this camera if it's already closed.
+        mScheduledOrientationUpdateCameraIdSet.remove(cameraId);
+        scheduleRemoveCameraId(cameraId);
+    }
+
+    // Delay is needed to avoid rotation flickering when an app is flipping between front and
+    // rear cameras, when size compat mode is restarted or activity is being refreshed.
+    private void scheduleRemoveCameraId(@NonNull String cameraId) {
+        mHandler.postDelayed(
+                () -> removeCameraId(cameraId),
+                CAMERA_CLOSED_ROTATION_UPDATE_DELAY_MS);
+    }
+
+    private void removeCameraId(String cameraId) {
+        synchronized (this) {
+            if (!mScheduledToBeRemovedCameraIdSet.remove(cameraId)) {
+                // Already reconnected to this camera, no need to clean up.
+                return;
+            }
+            if (isActivityForCameraIdRefreshing(cameraId)) {
+                ProtoLog.v(WM_DEBUG_ORIENTATION,
+                        "Display id=%d is notified that Camera %s is closed but activity is"
+                                + " still refreshing. Rescheduling an update.",
+                        mDisplayContent.mDisplayId, cameraId);
+                mScheduledToBeRemovedCameraIdSet.add(cameraId);
+                scheduleRemoveCameraId(cameraId);
+                return;
+            }
+            mCameraIdPackageBiMap.removeCameraId(cameraId);
+        }
+        ProtoLog.v(WM_DEBUG_ORIENTATION,
+                "Display id=%d is notified that Camera %s is closed, updating rotation.",
+                mDisplayContent.mDisplayId, cameraId);
+        updateOrientationWithWmLock();
+    }
+
+    private boolean isActivityForCameraIdRefreshing(String cameraId) {
+        ActivityRecord topActivity = mDisplayContent.topRunningActivity(
+                /* considerKeyguardState= */ true);
+        if (!isTreatmentEnabledForActivity(topActivity)) {
+            return false;
+        }
+        String activeCameraId = mCameraIdPackageBiMap.getCameraId(topActivity.packageName);
+        if (activeCameraId == null || activeCameraId != cameraId) {
+            return false;
+        }
+        return topActivity.mLetterboxUiController.isRefreshAfterRotationRequested();
+    }
+
+    private static class CameraIdPackageNameBiMap {
+
+        private final Map<String, String> mPackageToCameraIdMap = new ArrayMap<>();
+        private final Map<String, String> mCameraIdToPackageMap = new ArrayMap<>();
+
+        void put(String packageName, String cameraId) {
+            // Always using the last connected camera ID for the package even for the concurrent
+            // camera use case since we can't guess which camera is more important anyway.
+            removePackageName(packageName);
+            removeCameraId(cameraId);
+            mPackageToCameraIdMap.put(packageName, cameraId);
+            mCameraIdToPackageMap.put(cameraId, packageName);
+        }
+
+        boolean containsPackageName(String packageName) {
+            return mPackageToCameraIdMap.containsKey(packageName);
+        }
+
+        @Nullable
+        String getCameraId(String packageName) {
+            return mPackageToCameraIdMap.get(packageName);
+        }
+
+        void removeCameraId(String cameraId) {
+            String packageName = mCameraIdToPackageMap.get(cameraId);
+            if (packageName == null) {
+                return;
+            }
+            mPackageToCameraIdMap.remove(packageName, cameraId);
+            mCameraIdToPackageMap.remove(cameraId, packageName);
+        }
+
+        String getSummaryForDisplayRotationHistoryRecord() {
+            return "{ mPackageToCameraIdMap=" + mPackageToCameraIdMap + " }";
+        }
+
+        private void removePackageName(String packageName) {
+            String cameraId = mPackageToCameraIdMap.get(packageName);
+            if (cameraId == null) {
+                return;
+            }
+            mPackageToCameraIdMap.remove(packageName, cameraId);
+            mCameraIdToPackageMap.remove(cameraId, packageName);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index b735b30..6d47eeb 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -291,7 +291,7 @@
 
         boolean dontMoveToTop = settings.mDontMoveToTop != null
                 ? settings.mDontMoveToTop : false;
-        dc.mDontMoveToTop = dontMoveToTop;
+        dc.mDontMoveToTop = !dc.canStealTopFocus() || dontMoveToTop;
 
         if (includeRotationSettings) applyRotationSettingsToDisplayLocked(dc);
     }
diff --git a/services/core/java/com/android/server/wm/DockedTaskDividerController.java b/services/core/java/com/android/server/wm/DockedTaskDividerController.java
deleted file mode 100644
index 925a6d8..0000000
--- a/services/core/java/com/android/server/wm/DockedTaskDividerController.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.wm;
-
-import android.graphics.Rect;
-
-/**
- * Keeps information about the docked task divider.
- */
-public class DockedTaskDividerController {
-
-    private final DisplayContent mDisplayContent;
-    private boolean mResizing;
-
-    private final Rect mTouchRegion = new Rect();
-
-    DockedTaskDividerController(DisplayContent displayContent) {
-        mDisplayContent = displayContent;
-    }
-
-    boolean isResizing() {
-        return mResizing;
-    }
-
-    void setResizing(boolean resizing) {
-        if (mResizing != resizing) {
-            mResizing = resizing;
-            resetDragResizingChangeReported();
-        }
-    }
-
-    void setTouchRegion(Rect touchRegion) {
-        mTouchRegion.set(touchRegion);
-        // We need to report touchable region changes to accessibility.
-        if (mDisplayContent.mWmService.mAccessibilityController.hasCallbacks()) {
-            mDisplayContent.mWmService.mAccessibilityController.onSomeWindowResizedOrMoved(
-                    mDisplayContent.getDisplayId());
-        }
-    }
-
-    void getTouchRegion(Rect outRegion) {
-        outRegion.set(mTouchRegion);
-    }
-
-    private void resetDragResizingChangeReported() {
-        mDisplayContent.forAllWindows(WindowState::resetDragResizingChangeReported,
-                true /* traverseTopToBottom */);
-    }
-}
diff --git a/services/core/java/com/android/server/wm/DragResizeMode.java b/services/core/java/com/android/server/wm/DragResizeMode.java
deleted file mode 100644
index 684cf06..0000000
--- a/services/core/java/com/android/server/wm/DragResizeMode.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-
-/**
- * Describes the mode in which a window is drag resizing.
- */
-class DragResizeMode {
-
-    /**
-     * Freeform mode: Client surface is fullscreen, and client is responsible to draw window at
-     * the correct position.
-     */
-    static final int DRAG_RESIZE_MODE_FREEFORM = 0;
-
-    /**
-     * Mode for resizing the docked (and adjacent) root task: Client surface is fullscreen, but
-     * window is drawn at (0, 0), window manager is responsible for positioning the surface when
-     * dragging.
-     */
-    static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
-
-    static boolean isModeAllowedForRootTask(Task rootTask, int mode) {
-        switch (mode) {
-            case DRAG_RESIZE_MODE_FREEFORM:
-                return rootTask.getWindowingMode() == WINDOWING_MODE_FREEFORM;
-            default:
-                return false;
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 127a7bf..a7bf595f 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -16,12 +16,16 @@
 
 package com.android.server.wm;
 
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Color;
 import android.provider.DeviceConfig;
+import android.util.Slog;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,6 +37,8 @@
 /** Reads letterbox configs from resources and controls their overrides at runtime. */
 final class LetterboxConfiguration {
 
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxConfiguration" : TAG_ATM;
+
     /**
      * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
      * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -144,6 +150,14 @@
     // side of the screen and 1.0 to the bottom side.
     private float mLetterboxVerticalPositionMultiplier;
 
+    // Horizontal position of a center of the letterboxed app window when the device is half-folded.
+    // 0 corresponds to the left side of the screen and 1.0 to the right side.
+    private float mLetterboxBookModePositionMultiplier;
+
+    // Vertical position of a center of the letterboxed app window when the device is half-folded.
+    // 0 corresponds to the top side of the screen and 1.0 to the bottom side.
+    private float mLetterboxTabletopModePositionMultiplier;
+
     // Default horizontal position the letterboxed app window when horizontal reachability is
     // enabled and an app is fullscreen in landscape device orientation.
     // It is used as a starting point for mLetterboxPositionForHorizontalReachability.
@@ -177,10 +191,31 @@
     // Allows to enable letterboxing strategy for translucent activities ignoring flags.
     private boolean mTranslucentLetterboxingOverrideEnabled;
 
+    // Whether camera compatibility treatment is enabled.
+    // See DisplayRotationCompatPolicy for context.
+    private final boolean mIsCameraCompatTreatmentEnabled;
+
+    // Whether activity "refresh" in camera compatibility treatment is enabled.
+    // See RefreshCallbackItem for context.
+    private boolean mIsCameraCompatTreatmentRefreshEnabled = true;
+
+    // Whether activity "refresh" in camera compatibility treatment should happen using the
+    // "stopped -> resumed" cycle rather than "paused -> resumed" cycle. Using "stop -> resumed"
+    // cycle by default due to higher success rate confirmed with app compatibility testing.
+    // See RefreshCallbackItem for context.
+    private boolean mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+
     LetterboxConfiguration(Context systemUiContext) {
         this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext,
-                () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext),
-                () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext)));
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
+                        /* forBookMode */ false),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
+                        /* forTabletopMode */ false),
+                () -> readLetterboxHorizontalReachabilityPositionFromConfig(systemUiContext,
+                        /* forBookMode */ true),
+                () -> readLetterboxVerticalReachabilityPositionFromConfig(systemUiContext,
+                        /* forTabletopMode */ true)
+        ));
     }
 
     @VisibleForTesting
@@ -200,14 +235,18 @@
                 R.dimen.config_letterboxHorizontalPositionMultiplier);
         mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
                 R.dimen.config_letterboxVerticalPositionMultiplier);
+        mLetterboxBookModePositionMultiplier = mContext.getResources().getFloat(
+                R.dimen.config_letterboxBookModePositionMultiplier);
+        mLetterboxTabletopModePositionMultiplier = mContext.getResources().getFloat(
+                R.dimen.config_letterboxTabletopModePositionMultiplier);
         mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsHorizontalReachabilityEnabled);
         mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsVerticalReachabilityEnabled);
         mDefaultPositionForHorizontalReachability =
-                readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext, false);
         mDefaultPositionForVerticalReachability =
-                readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext, false);
         mIsEducationEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsEducationEnabled);
         setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
@@ -216,6 +255,8 @@
                 R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
         mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean(
                 R.bool.config_letterboxIsEnabledForTranslucentActivities);
+        mIsCameraCompatTreatmentEnabled = mContext.getResources().getBoolean(
+                R.bool.config_isWindowManagerCameraCompatTreatmentEnabled);
         mLetterboxConfigurationPersister = letterboxConfigurationPersister;
         mLetterboxConfigurationPersister.start();
     }
@@ -460,11 +501,30 @@
      * or via an ADB command. 0 corresponds to the left side of the screen and 1 to the
      * right side.
      */
-    float getLetterboxHorizontalPositionMultiplier() {
-        return (mLetterboxHorizontalPositionMultiplier < 0.0f
-                || mLetterboxHorizontalPositionMultiplier > 1.0f)
-                        // Default to central position if invalid value is provided.
-                        ? 0.5f : mLetterboxHorizontalPositionMultiplier;
+    float getLetterboxHorizontalPositionMultiplier(boolean isInBookMode) {
+        if (isInBookMode) {
+            if (mLetterboxBookModePositionMultiplier < 0.0f
+                    || mLetterboxBookModePositionMultiplier > 1.0f) {
+                Slog.w(TAG,
+                        "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=true): "
+                        + mLetterboxBookModePositionMultiplier);
+                // Default to left position if invalid value is provided.
+                return 0.0f;
+            } else {
+                return mLetterboxBookModePositionMultiplier;
+            }
+        } else {
+            if (mLetterboxHorizontalPositionMultiplier < 0.0f
+                    || mLetterboxHorizontalPositionMultiplier > 1.0f) {
+                Slog.w(TAG,
+                        "mLetterboxBookModePositionMultiplier out of bounds (isInBookMode=false):"
+                        + mLetterboxBookModePositionMultiplier);
+                // Default to central position if invalid value is provided.
+                return 0.5f;
+            } else {
+                return mLetterboxHorizontalPositionMultiplier;
+            }
+        }
     }
 
     /*
@@ -473,11 +533,18 @@
      * or via an ADB command. 0 corresponds to the top side of the screen and 1 to the
      * bottom side.
      */
-    float getLetterboxVerticalPositionMultiplier() {
-        return (mLetterboxVerticalPositionMultiplier < 0.0f
-                || mLetterboxVerticalPositionMultiplier > 1.0f)
-                        // Default to central position if invalid value is provided.
-                        ? 0.5f : mLetterboxVerticalPositionMultiplier;
+    float getLetterboxVerticalPositionMultiplier(boolean isInTabletopMode) {
+        if (isInTabletopMode) {
+            return (mLetterboxTabletopModePositionMultiplier < 0.0f
+                    || mLetterboxTabletopModePositionMultiplier > 1.0f)
+                    // Default to top position if invalid value is provided.
+                    ? 0.0f : mLetterboxTabletopModePositionMultiplier;
+        } else {
+            return (mLetterboxVerticalPositionMultiplier < 0.0f
+                    || mLetterboxVerticalPositionMultiplier > 1.0f)
+                    // Default to central position if invalid value is provided.
+                    ? 0.5f : mLetterboxVerticalPositionMultiplier;
+        }
     }
 
     /**
@@ -618,7 +685,8 @@
      */
     void resetDefaultPositionForHorizontalReachability() {
         mDefaultPositionForHorizontalReachability =
-                readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+                readLetterboxHorizontalReachabilityPositionFromConfig(mContext,
+                        false /* forBookMode */);
     }
 
     /**
@@ -627,27 +695,34 @@
      */
     void resetDefaultPositionForVerticalReachability() {
         mDefaultPositionForVerticalReachability =
-                readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+                readLetterboxVerticalReachabilityPositionFromConfig(mContext,
+                        false /* forTabletopMode */);
     }
 
     @LetterboxHorizontalReachabilityPosition
-    private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context) {
+    private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context,
+            boolean forBookMode) {
         int position = context.getResources().getInteger(
-                R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+                forBookMode
+                    ? R.integer.config_letterboxDefaultPositionForBookModeReachability
+                    : R.integer.config_letterboxDefaultPositionForHorizontalReachability);
         return position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT
-                    || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
-                    || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT
+                || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+                || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT
                     ? position : LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
     }
 
     @LetterboxVerticalReachabilityPosition
-    private static int readLetterboxVerticalReachabilityPositionFromConfig(Context context) {
+    private static int readLetterboxVerticalReachabilityPositionFromConfig(Context context,
+            boolean forTabletopMode) {
         int position = context.getResources().getInteger(
-                R.integer.config_letterboxDefaultPositionForVerticalReachability);
+                forTabletopMode
+                    ? R.integer.config_letterboxDefaultPositionForTabletopModeReachability
+                    : R.integer.config_letterboxDefaultPositionForVerticalReachability);
         return position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP
                 || position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
                 || position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM
-                ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+                    ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
     }
 
     /*
@@ -656,9 +731,10 @@
      *
      * <p>The position multiplier is changed after each double tap in the letterbox area.
      */
-    float getHorizontalMultiplierForReachability() {
+    float getHorizontalMultiplierForReachability(boolean isDeviceInBookMode) {
         final int letterboxPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        isDeviceInBookMode);
         switch (letterboxPositionForHorizontalReachability) {
             case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
                 return 0.0f;
@@ -679,9 +755,10 @@
      *
      * <p>The position multiplier is changed after each double tap in the letterbox area.
      */
-    float getVerticalMultiplierForReachability() {
+    float getVerticalMultiplierForReachability(boolean isDeviceInTabletopMode) {
         final int letterboxPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+                        isDeviceInTabletopMode);
         switch (letterboxPositionForVerticalReachability) {
             case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
                 return 0.0f;
@@ -701,8 +778,9 @@
      * enabled.
      */
     @LetterboxHorizontalReachabilityPosition
-    int getLetterboxPositionForHorizontalReachability() {
-        return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+    int getLetterboxPositionForHorizontalReachability(boolean isInFullScreenBookMode) {
+        return mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                isInFullScreenBookMode);
     }
 
     /*
@@ -710,8 +788,9 @@
      * enabled.
      */
     @LetterboxVerticalReachabilityPosition
-    int getLetterboxPositionForVerticalReachability() {
-        return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+    int getLetterboxPositionForVerticalReachability(boolean isInFullScreenTabletopMode) {
+        return mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+                isInFullScreenTabletopMode);
     }
 
     /** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
@@ -750,34 +829,41 @@
      * Changes letterbox position for horizontal reachability to the next available one on the
      * right side.
      */
-    void movePositionForHorizontalReachabilityToNextRightStop() {
-        updatePositionForHorizontalReachability(prev -> Math.min(
-                prev + 1, LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
+    void movePositionForHorizontalReachabilityToNextRightStop(boolean isDeviceInBookMode) {
+        updatePositionForHorizontalReachability(isDeviceInBookMode, prev -> Math.min(
+                prev + (isDeviceInBookMode ? 2 : 1), // Move 2 stops in book mode to avoid center.
+                LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT));
     }
 
     /**
      * Changes letterbox position for horizontal reachability to the next available one on the left
      * side.
      */
-    void movePositionForHorizontalReachabilityToNextLeftStop() {
-        updatePositionForHorizontalReachability(prev -> Math.max(prev - 1, 0));
+    void movePositionForHorizontalReachabilityToNextLeftStop(boolean isDeviceInBookMode) {
+        updatePositionForHorizontalReachability(isDeviceInBookMode, prev -> Math.max(
+                prev - (isDeviceInBookMode ? 2 : 1), 0)); // Move 2 stops in book mode to avoid
+                                                          // center.
     }
 
     /**
      * Changes letterbox position for vertical reachability to the next available one on the bottom
      * side.
      */
-    void movePositionForVerticalReachabilityToNextBottomStop() {
-        updatePositionForVerticalReachability(prev -> Math.min(
-                prev + 1, LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
+    void movePositionForVerticalReachabilityToNextBottomStop(boolean isDeviceInTabletopMode) {
+        updatePositionForVerticalReachability(isDeviceInTabletopMode, prev -> Math.min(
+                prev + (isDeviceInTabletopMode ? 2 : 1), // Move 2 stops in tabletop mode to avoid
+                                                         // center.
+                LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM));
     }
 
     /**
      * Changes letterbox position for vertical reachability to the next available one on the top
      * side.
      */
-    void movePositionForVerticalReachabilityToNextTopStop() {
-        updatePositionForVerticalReachability(prev -> Math.max(prev - 1, 0));
+    void movePositionForVerticalReachabilityToNextTopStop(boolean isDeviceInTabletopMode) {
+        updatePositionForVerticalReachability(isDeviceInTabletopMode, prev -> Math.max(
+                prev - (isDeviceInTabletopMode ? 2 : 1), 0)); // Move 2 stops in tabletop mode to
+                                                              // avoid center.
     }
 
     /**
@@ -854,30 +940,88 @@
     }
 
     /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */
-    private void updatePositionForHorizontalReachability(
+    private void updatePositionForHorizontalReachability(boolean isDeviceInBookMode,
             Function<Integer, Integer> newHorizonalPositionFun) {
         final int letterboxPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        isDeviceInBookMode);
         final int nextHorizontalPosition = newHorizonalPositionFun.apply(
                 letterboxPositionForHorizontalReachability);
         mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
-                nextHorizontalPosition);
+                isDeviceInBookMode, nextHorizontalPosition);
     }
 
     /** Calculates a new letterboxPositionForVerticalReachability value and updates the store */
-    private void updatePositionForVerticalReachability(
+    private void updatePositionForVerticalReachability(boolean isDeviceInTabletopMode,
             Function<Integer, Integer> newVerticalPositionFun) {
         final int letterboxPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(
+                        isDeviceInTabletopMode);
         final int nextVerticalPosition = newVerticalPositionFun.apply(
                 letterboxPositionForVerticalReachability);
         mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
-                nextVerticalPosition);
+                isDeviceInTabletopMode, nextVerticalPosition);
     }
 
-    // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener
+    // TODO(b/262378106): Cache a runtime flag and implement
+    // DeviceConfig.OnPropertiesChangedListener
     static boolean isTranslucentLetterboxingAllowed() {
         return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
                 "enable_translucent_activity_letterbox", false);
     }
+
+    /** Whether camera compatibility treatment is enabled. */
+    boolean isCameraCompatTreatmentEnabled(boolean checkDeviceConfig) {
+        return mIsCameraCompatTreatmentEnabled
+                && (!checkDeviceConfig || isCameraCompatTreatmentAllowed());
+    }
+
+    // TODO(b/262977416): Cache a runtime flag and implement
+    // DeviceConfig.OnPropertiesChangedListener
+    private static boolean isCameraCompatTreatmentAllowed() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+                "enable_camera_compat_treatment", false);
+    }
+
+    /** Whether camera compatibility refresh is enabled. */
+    boolean isCameraCompatRefreshEnabled() {
+        return mIsCameraCompatTreatmentRefreshEnabled;
+    }
+
+    /** Overrides whether camera compatibility treatment is enabled. */
+    void setCameraCompatRefreshEnabled(boolean enabled) {
+        mIsCameraCompatTreatmentRefreshEnabled = enabled;
+    }
+
+    /**
+     * Resets whether camera compatibility treatment is enabled to {@code true}.
+     */
+    void resetCameraCompatRefreshEnabled() {
+        mIsCameraCompatTreatmentRefreshEnabled = true;
+    }
+
+    /**
+     * Whether activity "refresh" in camera compatibility treatment should happen using the
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+     */
+    boolean isCameraCompatRefreshCycleThroughStopEnabled() {
+        return mIsCameraCompatRefreshCycleThroughStopEnabled;
+    }
+
+    /**
+     * Overrides whether activity "refresh" in camera compatibility treatment should happen using
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle.
+     */
+    void setCameraCompatRefreshCycleThroughStopEnabled(boolean enabled) {
+        mIsCameraCompatRefreshCycleThroughStopEnabled = enabled;
+    }
+
+    /**
+     * Resets  whether activity "refresh" in camera compatibility treatment should happen using
+     * "stopped -> resumed" cycle rather than "paused -> resumed" cycle to {@code true}.
+     */
+    void resetCameraCompatRefreshCycleThroughStopEnabled() {
+        mIsCameraCompatRefreshCycleThroughStopEnabled = true;
+    }
+
 }
diff --git a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
index 70639b1..4a99db5 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfigurationPersister.java
@@ -55,6 +55,8 @@
     private final Context mContext;
     private final Supplier<Integer> mDefaultHorizontalReachabilitySupplier;
     private final Supplier<Integer> mDefaultVerticalReachabilitySupplier;
+    private final Supplier<Integer> mDefaultBookModeReachabilitySupplier;
+    private final Supplier<Integer> mDefaultTabletopModeReachabilitySupplier;
 
     // Horizontal position of a center of the letterboxed app window which is global to prevent
     // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
@@ -64,6 +66,11 @@
     @LetterboxHorizontalReachabilityPosition
     private volatile int mLetterboxPositionForHorizontalReachability;
 
+    // The same as mLetterboxPositionForHorizontalReachability but used when the device is
+    // half-folded.
+    @LetterboxHorizontalReachabilityPosition
+    private volatile int mLetterboxPositionForBookModeReachability;
+
     // Vertical position of a center of the letterboxed app window which is global to prevent
     // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
     // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
@@ -72,6 +79,11 @@
     @LetterboxVerticalReachabilityPosition
     private volatile int mLetterboxPositionForVerticalReachability;
 
+    // The same as mLetterboxPositionForVerticalReachability but used when the device is
+    // half-folded.
+    @LetterboxVerticalReachabilityPosition
+    private volatile int mLetterboxPositionForTabletopModeReachability;
+
     @NonNull
     private final AtomicFile mConfigurationFile;
 
@@ -83,9 +95,13 @@
 
     LetterboxConfigurationPersister(Context systemUiContext,
             Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier) {
+            Supplier<Integer> defaultVerticalReachabilitySupplier,
+            Supplier<Integer> defaultBookModeReachabilitySupplier,
+            Supplier<Integer> defaultTabletopModeReachabilitySupplier) {
         this(systemUiContext, defaultHorizontalReachabilitySupplier,
                 defaultVerticalReachabilitySupplier,
+                defaultBookModeReachabilitySupplier,
+                defaultTabletopModeReachabilitySupplier,
                 Environment.getDataSystemDirectory(), new PersisterQueue(),
                 /* completionCallback */ null);
     }
@@ -93,11 +109,18 @@
     @VisibleForTesting
     LetterboxConfigurationPersister(Context systemUiContext,
             Supplier<Integer> defaultHorizontalReachabilitySupplier,
-            Supplier<Integer> defaultVerticalReachabilitySupplier, File configFolder,
+            Supplier<Integer> defaultVerticalReachabilitySupplier,
+            Supplier<Integer> defaultBookModeReachabilitySupplier,
+            Supplier<Integer> defaultTabletopModeReachabilitySupplier,
+            File configFolder,
             PersisterQueue persisterQueue, @Nullable Consumer<String> completionCallback) {
         mContext = systemUiContext.createDeviceProtectedStorageContext();
         mDefaultHorizontalReachabilitySupplier = defaultHorizontalReachabilitySupplier;
         mDefaultVerticalReachabilitySupplier = defaultVerticalReachabilitySupplier;
+        mDefaultBookModeReachabilitySupplier =
+                defaultBookModeReachabilitySupplier;
+        mDefaultTabletopModeReachabilitySupplier =
+                defaultTabletopModeReachabilitySupplier;
         mCompletionCallback = completionCallback;
         final File prefFiles = new File(configFolder, LETTERBOX_CONFIGURATION_FILENAME);
         mConfigurationFile = new AtomicFile(prefFiles);
@@ -117,8 +140,12 @@
      * enabled.
      */
     @LetterboxHorizontalReachabilityPosition
-    int getLetterboxPositionForHorizontalReachability() {
-        return mLetterboxPositionForHorizontalReachability;
+    int getLetterboxPositionForHorizontalReachability(boolean forBookMode) {
+        if (forBookMode) {
+            return mLetterboxPositionForBookModeReachability;
+        } else {
+            return mLetterboxPositionForHorizontalReachability;
+        }
     }
 
     /*
@@ -126,31 +153,55 @@
      * enabled.
      */
     @LetterboxVerticalReachabilityPosition
-    int getLetterboxPositionForVerticalReachability() {
-        return mLetterboxPositionForVerticalReachability;
-    }
-
-    /**
-     * Updates letterboxPositionForVerticalReachability if different from the current value
-     */
-    void setLetterboxPositionForHorizontalReachability(
-            int letterboxPositionForHorizontalReachability) {
-        if (mLetterboxPositionForHorizontalReachability
-                != letterboxPositionForHorizontalReachability) {
-            mLetterboxPositionForHorizontalReachability =
-                    letterboxPositionForHorizontalReachability;
-            updateConfiguration();
+    int getLetterboxPositionForVerticalReachability(boolean forTabletopMode) {
+        if (forTabletopMode) {
+            return mLetterboxPositionForTabletopModeReachability;
+        } else {
+            return mLetterboxPositionForVerticalReachability;
         }
     }
 
     /**
      * Updates letterboxPositionForVerticalReachability if different from the current value
      */
-    void setLetterboxPositionForVerticalReachability(
+    void setLetterboxPositionForHorizontalReachability(boolean forBookMode,
+            int letterboxPositionForHorizontalReachability) {
+        if (forBookMode) {
+            if (mLetterboxPositionForBookModeReachability
+                    != letterboxPositionForHorizontalReachability) {
+                mLetterboxPositionForBookModeReachability =
+                        letterboxPositionForHorizontalReachability;
+                updateConfiguration();
+            }
+        } else {
+            if (mLetterboxPositionForHorizontalReachability
+                    != letterboxPositionForHorizontalReachability) {
+                mLetterboxPositionForHorizontalReachability =
+                        letterboxPositionForHorizontalReachability;
+                updateConfiguration();
+            }
+        }
+    }
+
+    /**
+     * Updates letterboxPositionForVerticalReachability if different from the current value
+     */
+    void setLetterboxPositionForVerticalReachability(boolean forTabletopMode,
             int letterboxPositionForVerticalReachability) {
-        if (mLetterboxPositionForVerticalReachability != letterboxPositionForVerticalReachability) {
-            mLetterboxPositionForVerticalReachability = letterboxPositionForVerticalReachability;
-            updateConfiguration();
+        if (forTabletopMode) {
+            if (mLetterboxPositionForTabletopModeReachability
+                    != letterboxPositionForVerticalReachability) {
+                mLetterboxPositionForTabletopModeReachability =
+                        letterboxPositionForVerticalReachability;
+                updateConfiguration();
+            }
+        } else {
+            if (mLetterboxPositionForVerticalReachability
+                    != letterboxPositionForVerticalReachability) {
+                mLetterboxPositionForVerticalReachability =
+                        letterboxPositionForVerticalReachability;
+                updateConfiguration();
+            }
         }
     }
 
@@ -158,6 +209,10 @@
     void useDefaultValue() {
         mLetterboxPositionForHorizontalReachability = mDefaultHorizontalReachabilitySupplier.get();
         mLetterboxPositionForVerticalReachability = mDefaultVerticalReachabilitySupplier.get();
+        mLetterboxPositionForBookModeReachability =
+                mDefaultBookModeReachabilitySupplier.get();
+        mLetterboxPositionForTabletopModeReachability =
+                mDefaultTabletopModeReachabilitySupplier.get();
     }
 
     private void readCurrentConfiguration() {
@@ -171,6 +226,10 @@
                     letterboxData.letterboxPositionForHorizontalReachability;
             mLetterboxPositionForVerticalReachability =
                     letterboxData.letterboxPositionForVerticalReachability;
+            mLetterboxPositionForBookModeReachability =
+                    letterboxData.letterboxPositionForBookModeReachability;
+            mLetterboxPositionForTabletopModeReachability =
+                    letterboxData.letterboxPositionForTabletopModeReachability;
         } catch (IOException ioe) {
             Slog.e(TAG,
                     "Error reading from LetterboxConfigurationPersister. "
@@ -192,6 +251,8 @@
         mPersisterQueue.addItem(new UpdateValuesCommand(mConfigurationFile,
                 mLetterboxPositionForHorizontalReachability,
                 mLetterboxPositionForVerticalReachability,
+                mLetterboxPositionForBookModeReachability,
+                mLetterboxPositionForTabletopModeReachability,
                 mCompletionCallback), /* flush */ true);
     }
 
@@ -221,13 +282,18 @@
 
         private final int mHorizontalReachability;
         private final int mVerticalReachability;
+        private final int mBookModeReachability;
+        private final int mTabletopModeReachability;
 
         UpdateValuesCommand(@NonNull AtomicFile fileToUpdate,
                 int horizontalReachability, int verticalReachability,
+                int bookModeReachability, int tabletopModeReachability,
                 @Nullable Consumer<String> onComplete) {
             mFileToUpdate = fileToUpdate;
             mHorizontalReachability = horizontalReachability;
             mVerticalReachability = verticalReachability;
+            mBookModeReachability = bookModeReachability;
+            mTabletopModeReachability = tabletopModeReachability;
             mOnComplete = onComplete;
         }
 
@@ -237,6 +303,10 @@
                     new WindowManagerProtos.LetterboxProto();
             letterboxData.letterboxPositionForHorizontalReachability = mHorizontalReachability;
             letterboxData.letterboxPositionForVerticalReachability = mVerticalReachability;
+            letterboxData.letterboxPositionForBookModeReachability =
+                    mBookModeReachability;
+            letterboxData.letterboxPositionForTabletopModeReachability =
+                    mTabletopModeReachability;
             final byte[] bytes = WindowManagerProtos.LetterboxProto.toByteArray(letterboxData);
 
             FileOutputStream fos = null;
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index a53a5fc..fd7e082 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -80,6 +80,7 @@
 // SizeCompatTests and LetterboxTests but not all.
 // TODO(b/185264020): Consider making LetterboxUiController applicable to any level of the
 // hierarchy in addition to ActivityRecord (Task, DisplayArea, ...).
+// TODO(b/263021211): Consider renaming to more generic CompatUIController.
 final class LetterboxUiController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM;
@@ -125,6 +126,11 @@
     @Nullable
     private Letterbox mLetterbox;
 
+    // Whether activity "refresh" was requested but not finished in
+    // ActivityRecord#activityResumedLocked following the camera compat force rotation in
+    // DisplayRotationCompatPolicy.
+    private boolean mIsRefreshAfterRotationRequested;
+
     LetterboxUiController(WindowManagerService wmService, ActivityRecord activityRecord) {
         mLetterboxConfiguration = wmService.mLetterboxConfiguration;
         // Given activityRecord may not be fully constructed since LetterboxUiController
@@ -147,6 +153,18 @@
         }
     }
 
+    /**
+     * Whether activity "refresh" was requested but not finished in {@link #activityResumedLocked}
+     * following the camera compat force rotation in {@link DisplayRotationCompatPolicy}.
+     */
+    boolean isRefreshAfterRotationRequested() {
+        return mIsRefreshAfterRotationRequested;
+    }
+
+    void setIsRefreshAfterRotationRequested(boolean isRequested) {
+        mIsRefreshAfterRotationRequested = isRequested;
+    }
+
     boolean hasWallpaperBackgroundForLetterbox() {
         return mShowWallpaperForLetterboxBackground;
     }
@@ -275,30 +293,62 @@
                 && mActivityRecord.fillsParent();
     }
 
+    // Check if we are in the given pose and in fullscreen mode.
+    // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+    // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+    // actually fullscreen.
+    private boolean isDisplayFullScreenAndInPosture(DeviceStateController.FoldState state,
+            boolean isTabletop) {
+        Task task = mActivityRecord.getTask();
+        return mActivityRecord.mDisplayContent != null
+                && mActivityRecord.mDisplayContent.getDisplayRotation().isDeviceInPosture(state,
+                    isTabletop)
+                && task != null
+                && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
+    // Note that we check the task rather than the parent as with ActivityEmbedding the parent might
+    // be a TaskFragment, and its windowing mode is always MULTI_WINDOW, even if the task is
+    // actually fullscreen.
+    private boolean isDisplayFullScreenAndSeparatingHinge() {
+        Task task = mActivityRecord.getTask();
+        return mActivityRecord.mDisplayContent != null
+                && mActivityRecord.mDisplayContent.getDisplayRotation().isDisplaySeparatingHinge()
+                && task != null
+                && task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+
+
     float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
+        boolean bookMode = isDisplayFullScreenAndInPosture(
+                DeviceStateController.FoldState.HALF_FOLDED, false /* isTabletop */);
         return isHorizontalReachabilityEnabled(parentConfiguration)
                 // Using the last global dynamic position to avoid "jumps" when moving
                 // between apps or activities.
-                ? mLetterboxConfiguration.getHorizontalMultiplierForReachability()
-                : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
+                ? mLetterboxConfiguration.getHorizontalMultiplierForReachability(bookMode)
+                : mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(bookMode);
     }
 
     float getVerticalPositionMultiplier(Configuration parentConfiguration) {
         // Don't check resolved configuration because it may not be updated yet during
         // configuration change.
+        boolean tabletopMode = isDisplayFullScreenAndInPosture(
+                DeviceStateController.FoldState.HALF_FOLDED, true /* isTabletop */);
         return isVerticalReachabilityEnabled(parentConfiguration)
                 // Using the last global dynamic position to avoid "jumps" when moving
                 // between apps or activities.
-                ? mLetterboxConfiguration.getVerticalMultiplierForReachability()
-                : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier();
+                ? mLetterboxConfiguration.getVerticalMultiplierForReachability(tabletopMode)
+                : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(tabletopMode);
     }
 
     float getFixedOrientationLetterboxAspectRatio() {
-        return mActivityRecord.shouldCreateCompatDisplayInsets()
-                ? getDefaultMinAspectRatioForUnresizableApps()
-                : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+        return isDisplayFullScreenAndSeparatingHinge()
+                ? getSplitScreenAspectRatio()
+                : mActivityRecord.shouldCreateCompatDisplayInsets()
+                    ? getDefaultMinAspectRatioForUnresizableApps()
+                    : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
     }
 
     private float getDefaultMinAspectRatioForUnresizableApps() {
@@ -351,11 +401,13 @@
             return;
         }
 
+        boolean isInFullScreenBookMode = isDisplayFullScreenAndSeparatingHinge();
         int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
-                .getLetterboxPositionForHorizontalReachability();
+                .getLetterboxPositionForHorizontalReachability(isInFullScreenBookMode);
         if (mLetterbox.getInnerFrame().left > x) {
             // Moving to the next stop on the left side of the app window: right > center > left.
-            mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+            mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop(
+                    isInFullScreenBookMode);
             int changeToLog =
                     letterboxPositionForHorizontalReachability
                             == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
@@ -364,7 +416,8 @@
             logLetterboxPositionChange(changeToLog);
         } else if (mLetterbox.getInnerFrame().right < x) {
             // Moving to the next stop on the right side of the app window: left > center > right.
-            mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+            mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+                    isInFullScreenBookMode);
             int changeToLog =
                     letterboxPositionForHorizontalReachability
                             == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
@@ -388,11 +441,13 @@
             // Only react to clicks at the top and bottom of the letterboxed app window.
             return;
         }
+        boolean isInFullScreenTabletopMode = isDisplayFullScreenAndSeparatingHinge();
         int letterboxPositionForVerticalReachability = mLetterboxConfiguration
-                .getLetterboxPositionForVerticalReachability();
+                .getLetterboxPositionForVerticalReachability(isInFullScreenTabletopMode);
         if (mLetterbox.getInnerFrame().top > y) {
             // Moving to the next stop on the top side of the app window: bottom > center > top.
-            mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+            mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop(
+                    isInFullScreenTabletopMode);
             int changeToLog =
                     letterboxPositionForVerticalReachability
                             == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
@@ -401,7 +456,8 @@
             logLetterboxPositionChange(changeToLog);
         } else if (mLetterbox.getInnerFrame().bottom < y) {
             // Moving to the next stop on the bottom side of the app window: top > center > bottom.
-            mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+            mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+                    isInFullScreenTabletopMode);
             int changeToLog =
                     letterboxPositionForVerticalReachability
                             == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
@@ -712,10 +768,10 @@
                 + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
         pw.println(prefix + "  letterboxPositionForHorizontalReachability="
                 + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
-                    mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+                mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false)));
         pw.println(prefix + "  letterboxPositionForVerticalReachability="
                 + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
-                    mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
+                mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false)));
         pw.println(prefix + "  fixedOrientationLetterboxAspectRatio="
                 + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
         pw.println(prefix + "  defaultMinAspectRatioForUnresizableApps="
@@ -780,14 +836,20 @@
         int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
         if (isHorizontalReachabilityEnabled()) {
             int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
-                    .getLetterboxPositionForHorizontalReachability();
+                    .getLetterboxPositionForHorizontalReachability(
+                            isDisplayFullScreenAndInPosture(
+                                    DeviceStateController.FoldState.HALF_FOLDED,
+                                    false /* isTabletop */));
             positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
-                            letterboxPositionForHorizontalReachability);
+                    letterboxPositionForHorizontalReachability);
         } else if (isVerticalReachabilityEnabled()) {
             int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
-                    .getLetterboxPositionForVerticalReachability();
+                    .getLetterboxPositionForVerticalReachability(
+                            isDisplayFullScreenAndInPosture(
+                                    DeviceStateController.FoldState.HALF_FOLDED,
+                                    true /* isTabletop */));
             positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
-                            letterboxPositionForVerticalReachability);
+                    letterboxPositionForVerticalReachability);
         }
         return positionToLog;
     }
diff --git a/services/core/java/com/android/server/wm/PackageConfigPersister.java b/services/core/java/com/android/server/wm/PackageConfigPersister.java
index 18a7d2e..23127ac 100644
--- a/services/core/java/com/android/server/wm/PackageConfigPersister.java
+++ b/services/core/java/com/android/server/wm/PackageConfigPersister.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import android.annotation.NonNull;
 import android.content.res.Configuration;
 import android.os.Environment;
@@ -165,7 +167,8 @@
             if (modifiedRecord != null) {
                 container.applyAppSpecificConfig(modifiedRecord.mNightMode,
                         LocaleOverlayHelper.combineLocalesIfOverlayExists(
-                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()));
+                        modifiedRecord.mLocales, mAtm.getGlobalConfiguration().getLocales()),
+                        modifiedRecord.mGrammaticalGender);
             }
         }
     }
@@ -188,16 +191,19 @@
             }
             boolean isNightModeChanged = updateNightMode(impl.getNightMode(), record);
             boolean isLocalesChanged = updateLocales(impl.getLocales(), record);
+            boolean isGenderChanged = updateGender(impl.getGrammaticalGender(), record);
 
             if ((record.mNightMode == null || record.isResetNightMode())
-                    && (record.mLocales == null || record.mLocales.isEmpty())) {
+                    && (record.mLocales == null || record.mLocales.isEmpty())
+                    && (record.mGrammaticalGender == null
+                            || record.mGrammaticalGender == GRAMMATICAL_GENDER_NOT_SPECIFIED)) {
                 // if all values default to system settings, we can remove the package.
                 removePackage(packageName, userId);
                 // if there was a pre-existing record for the package that was deleted,
                 // we return true (since it was successfully deleted), else false (since there was
                 // no change to the previous state).
                 return isRecordPresent;
-            } else if (!isNightModeChanged && !isLocalesChanged) {
+            } else if (!isNightModeChanged && !isLocalesChanged && !isGenderChanged) {
                 return false;
             } else {
                 final PackageConfigRecord pendingRecord =
@@ -211,7 +217,8 @@
                 }
 
                 if (!updateNightMode(record.mNightMode, writeRecord)
-                        && !updateLocales(record.mLocales, writeRecord)) {
+                        && !updateLocales(record.mLocales, writeRecord)
+                        && !updateGender(record.mGrammaticalGender, writeRecord)) {
                     return false;
                 }
 
@@ -240,6 +247,15 @@
         return true;
     }
 
+    private boolean updateGender(@Configuration.GrammaticalGender Integer requestedGender,
+            PackageConfigRecord record) {
+        if (requestedGender == null || requestedGender.equals(record.mGrammaticalGender)) {
+            return false;
+        }
+        record.mGrammaticalGender = requestedGender;
+        return true;
+    }
+
     @GuardedBy("mLock")
     void removeUser(int userId) {
         synchronized (mLock) {
@@ -305,7 +321,9 @@
                 return null;
             }
             return new ActivityTaskManagerInternal.PackageConfig(
-                    packageConfigRecord.mNightMode, packageConfigRecord.mLocales);
+                    packageConfigRecord.mNightMode,
+                    packageConfigRecord.mLocales,
+                    packageConfigRecord.mGrammaticalGender);
         }
     }
 
@@ -336,6 +354,8 @@
         final int mUserId;
         Integer mNightMode;
         LocaleList mLocales;
+        @Configuration.GrammaticalGender
+        Integer mGrammaticalGender;
 
         PackageConfigRecord(String name, int userId) {
             mName = name;
diff --git a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
index f3be66c..2cf8a4a 100644
--- a/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
+++ b/services/core/java/com/android/server/wm/PackageConfigurationUpdaterImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.LocaleList;
 import android.util.ArraySet;
@@ -33,6 +34,8 @@
     private final Optional<Integer> mPid;
     private Integer mNightMode;
     private LocaleList mLocales;
+    private @Configuration.GrammaticalGender
+    int mGrammaticalGender;
     private String mPackageName;
     private int mUserId;
     private ActivityTaskManagerService mAtm;
@@ -68,6 +71,15 @@
     }
 
     @Override
+    public ActivityTaskManagerInternal.PackageConfigurationUpdater setGrammaticalGender(
+            @Configuration.GrammaticalGender int gender) {
+        synchronized (this) {
+            mGrammaticalGender = gender;
+        }
+        return this;
+    }
+
+    @Override
     public boolean commit() {
         synchronized (this) {
             synchronized (mAtm.mGlobalLock) {
@@ -112,12 +124,12 @@
         for (int i = processes.size() - 1; i >= 0; i--) {
             final WindowProcessController wpc = processes.valueAt(i);
             if (wpc.mInfo.packageName.equals(packageName)) {
-                wpc.applyAppSpecificConfig(mNightMode, localesOverride);
+                wpc.applyAppSpecificConfig(mNightMode, localesOverride, mGrammaticalGender);
             }
             // Always inform individual activities about the update, since activities from other
             // packages may be sharing this process
             wpc.updateAppSpecificSettingsForAllActivitiesInPackage(packageName, mNightMode,
-                    localesOverride);
+                    localesOverride, mGrammaticalGender);
         }
     }
 
@@ -128,4 +140,9 @@
     LocaleList getLocales() {
         return mLocales;
     }
+
+    @Configuration.GrammaticalGender
+    Integer getGrammaticalGender() {
+        return mGrammaticalGender;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ca9b9b3..fd5c5eb 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -442,10 +442,14 @@
         mDisplayOffTokenAcquirer = mService.new SleepTokenAcquirerImpl(DISPLAY_OFF_SLEEP_TOKEN_TAG);
     }
 
+    /**
+     * Updates the children's focused window and the top focused display if needed.
+     */
     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
         mTopFocusedAppByProcess.clear();
         boolean changed = false;
         int topFocusedDisplayId = INVALID_DISPLAY;
+        // Go through the children in z-order starting at the top-most
         for (int i = mChildren.size() - 1; i >= 0; --i) {
             final DisplayContent dc = mChildren.get(i);
             changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
@@ -1478,7 +1482,7 @@
                 final String resolvedType =
                         homeIntent.resolveTypeIfNeeded(mService.mContext.getContentResolver());
                 final ResolveInfo info = mTaskSupervisor.resolveIntent(homeIntent, resolvedType,
-                        userId, flags, Binder.getCallingUid());
+                        userId, flags, Binder.getCallingUid(), Binder.getCallingPid());
                 if (info != null) {
                     aInfo = info.activityInfo;
                 }
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index bf3edcf..17b463f 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -403,7 +403,7 @@
                             pi.getIntentSender().getTarget());
                     final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
                             launchIntent, null /* resolvedType */, user.getIdentifier(),
-                            callingUid);
+                            callingUid, callingPid);
                     item.setActivityInfo(info);
                 }
             } finally {
@@ -437,7 +437,7 @@
                 }
                 final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
                         shortcutIntents[0], null /* resolvedType */, user.getIdentifier(),
-                        callingUid);
+                        callingUid, callingPid);
                 item.setActivityInfo(info);
             }
         } else if (hasTask) {
@@ -461,7 +461,8 @@
                 } else {
                     // Resolve the activity info manually if the task was restored after reboot
                     final ActivityInfo info = mService.mAtmService.resolveActivityInfoForIntent(
-                            task.intent, null /* resolvedType */, task.mUserId, callingUid);
+                            task.intent, null /* resolvedType */, task.mUserId, callingUid,
+                            callingPid);
                     item.setActivityInfo(info);
                 }
             }
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
index 658f4ef..878b33f 100644
--- a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -78,7 +78,10 @@
     private int mDownPointers;
     private boolean mSwipeFireable;
     private boolean mDebugFireable;
-    private boolean mMouseHoveringAtEdge;
+    private boolean mMouseHoveringAtLeft;
+    private boolean mMouseHoveringAtTop;
+    private boolean mMouseHoveringAtRight;
+    private boolean mMouseHoveringAtBottom;
     private long mLastFlingTime;
 
     SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) {
@@ -174,9 +177,21 @@
                 mDebugFireable = true;
                 mDownPointers = 0;
                 captureDown(event, 0);
-                if (mMouseHoveringAtEdge) {
-                    mMouseHoveringAtEdge = false;
-                    mCallbacks.onMouseLeaveFromEdge();
+                if (mMouseHoveringAtLeft) {
+                    mMouseHoveringAtLeft = false;
+                    mCallbacks.onMouseLeaveFromLeft();
+                }
+                if (mMouseHoveringAtTop) {
+                    mMouseHoveringAtTop = false;
+                    mCallbacks.onMouseLeaveFromTop();
+                }
+                if (mMouseHoveringAtRight) {
+                    mMouseHoveringAtRight = false;
+                    mCallbacks.onMouseLeaveFromRight();
+                }
+                if (mMouseHoveringAtBottom) {
+                    mMouseHoveringAtBottom = false;
+                    mCallbacks.onMouseLeaveFromBottom();
                 }
                 mCallbacks.onDown();
                 break;
@@ -211,16 +226,35 @@
                 break;
             case MotionEvent.ACTION_HOVER_MOVE:
                 if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
-                    if (!mMouseHoveringAtEdge && event.getY() == 0) {
+                    final float eventX = event.getX();
+                    final float eventY = event.getY();
+                    if (!mMouseHoveringAtLeft && eventX == 0) {
+                        mCallbacks.onMouseHoverAtLeft();
+                        mMouseHoveringAtLeft = true;
+                    } else if (mMouseHoveringAtLeft && eventX > 0) {
+                        mCallbacks.onMouseLeaveFromLeft();
+                        mMouseHoveringAtLeft = false;
+                    }
+                    if (!mMouseHoveringAtTop && eventY == 0) {
                         mCallbacks.onMouseHoverAtTop();
-                        mMouseHoveringAtEdge = true;
-                    } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
+                        mMouseHoveringAtTop = true;
+                    } else if (mMouseHoveringAtTop && eventY > 0) {
+                        mCallbacks.onMouseLeaveFromTop();
+                        mMouseHoveringAtTop = false;
+                    }
+                    if (!mMouseHoveringAtRight && eventX >= screenWidth - 1) {
+                        mCallbacks.onMouseHoverAtRight();
+                        mMouseHoveringAtRight = true;
+                    } else if (mMouseHoveringAtRight && eventX < screenWidth - 1) {
+                        mCallbacks.onMouseLeaveFromRight();
+                        mMouseHoveringAtRight = false;
+                    }
+                    if (!mMouseHoveringAtBottom && eventY >= screenHeight - 1) {
                         mCallbacks.onMouseHoverAtBottom();
-                        mMouseHoveringAtEdge = true;
-                    } else if (mMouseHoveringAtEdge
-                            && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
-                        mCallbacks.onMouseLeaveFromEdge();
-                        mMouseHoveringAtEdge = false;
+                        mMouseHoveringAtBottom = true;
+                    } else if (mMouseHoveringAtBottom && eventY < screenHeight - 1) {
+                        mCallbacks.onMouseLeaveFromBottom();
+                        mMouseHoveringAtBottom = false;
                     }
                 }
                 break;
@@ -373,9 +407,14 @@
         void onFling(int durationMs);
         void onDown();
         void onUpOrCancel();
+        void onMouseHoverAtLeft();
         void onMouseHoverAtTop();
+        void onMouseHoverAtRight();
         void onMouseHoverAtBottom();
-        void onMouseLeaveFromEdge();
+        void onMouseLeaveFromLeft();
+        void onMouseLeaveFromTop();
+        void onMouseLeaveFromRight();
+        void onMouseLeaveFromBottom();
         void onDebug();
     }
 }
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index fdb5f1f..8609e10 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -470,7 +470,6 @@
 
     // Whether the task is currently being drag-resized
     private boolean mDragResizing;
-    private int mDragResizeMode;
 
     // This represents the last resolved activity values for this task
     // NOTE: This value needs to be persisted with each task
@@ -2818,11 +2817,6 @@
         }
 
         final Task rootTask = getRootTask();
-        final DisplayContent displayContent = rootTask.getDisplayContent();
-        // It doesn't matter if we in particular are part of the resize, since we couldn't have
-        // a DimLayer anyway if we weren't visible.
-        final boolean dockedResizing = displayContent != null
-                && displayContent.mDividerControllerLocked.isResizing();
         if (inFreeformWindowingMode()) {
             boolean[] foundTop = { false };
             forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
@@ -2833,18 +2827,10 @@
 
         if (!matchParentBounds()) {
             // When minimizing the root docked task when going home, we don't adjust the task bounds
-            // so we need to intersect the task bounds with the root task bounds here.
-            //
-            // If we are Docked Resizing with snap points, the task bounds could be smaller than the
-            // root task bounds and so we don't even want to use them. Even if the app should not be
-            // resized the Dim should keep up with the divider.
-            if (dockedResizing) {
-                rootTask.getBounds(out);
-            } else {
-                rootTask.getBounds(mTmpRect);
-                mTmpRect.intersect(getBounds());
-                out.set(mTmpRect);
-            }
+            // so we need to intersect the task bounds with the root task bounds here..
+            rootTask.getBounds(mTmpRect);
+            mTmpRect.intersect(getBounds());
+            out.set(mTmpRect);
         } else {
             out.set(getBounds());
         }
@@ -2875,16 +2861,15 @@
         }
     }
 
-    void setDragResizing(boolean dragResizing, int dragResizeMode) {
+    void setDragResizing(boolean dragResizing) {
         if (mDragResizing != dragResizing) {
-            // No need to check if the mode is allowed if it's leaving dragResize
+            // No need to check if allowed if it's leaving dragResize
             if (dragResizing
-                    && !DragResizeMode.isModeAllowedForRootTask(getRootTask(), dragResizeMode)) {
-                throw new IllegalArgumentException("Drag resize mode not allow for root task id="
-                        + getRootTaskId() + " dragResizeMode=" + dragResizeMode);
+                    && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
+                throw new IllegalArgumentException("Drag resize not allow for root task id="
+                        + getRootTaskId());
             }
             mDragResizing = dragResizing;
-            mDragResizeMode = dragResizeMode;
             resetDragResizingChangeReported();
         }
     }
@@ -2893,10 +2878,6 @@
         return mDragResizing;
     }
 
-    int getDragResizeMode() {
-        return mDragResizeMode;
-    }
-
     void adjustBoundsForDisplayChangeIfNeeded(final DisplayContent displayContent) {
         if (displayContent == null) {
             return;
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 5b32149..9b3fb6b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -27,7 +27,6 @@
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
 import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -368,7 +367,7 @@
 
     private void endDragLocked() {
         mResizing = false;
-        mTask.setDragResizing(false, DRAG_RESIZE_MODE_FREEFORM);
+        mTask.setDragResizing(false);
     }
 
     /** Returns true if the move operation should be ended. */
@@ -380,7 +379,7 @@
 
         if (mCtrlType != CTRL_NONE) {
             resizeDrag(x, y);
-            mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
+            mTask.setDragResizing(true);
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index ce41ae7..6737052 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1305,6 +1305,11 @@
         if (parent != null) {
             parent.onChildVisibleRequestedChanged(this);
         }
+
+        // Notify listeners about visibility change.
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            mListeners.get(i).onVisibleRequestedChanged(mVisibleRequested);
+        }
         return true;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainerListener.java b/services/core/java/com/android/server/wm/WindowContainerListener.java
index ac1fe17..c1ee254 100644
--- a/services/core/java/com/android/server/wm/WindowContainerListener.java
+++ b/services/core/java/com/android/server/wm/WindowContainerListener.java
@@ -27,4 +27,13 @@
 
     /** Called when {@link WindowContainer#removeImmediately()} is invoked. */
     default void onRemoved() {}
+
+    /**
+     * Only invoked if the child successfully requested a visibility change.
+     *
+     * @param isVisibleRequested The current {@link WindowContainer#isVisibleRequested()} of this
+     *                           {@link WindowContainer} (not of the child).
+     * @see WindowContainer#onChildVisibleRequestedChanged(WindowContainer)
+     */
+    default void onVisibleRequestedChanged(boolean isVisibleRequested) { }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 3242c2c..b83f423 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -563,7 +563,7 @@
     /** Mapping from an InputWindowHandle token to the server's Window object. */
     final HashMap<IBinder, WindowState> mInputToWindowMap = new HashMap<>();
 
-    /** Global service lock used by the package the owns this service. */
+    /** Global service lock used by the package that owns this service. */
     final WindowManagerGlobalLock mGlobalLock;
 
     /**
@@ -3165,15 +3165,41 @@
     }
 
     @Override
-    public void moveDisplayToTop(int displayId) {
+    public void moveDisplayToTopIfAllowed(int displayId) {
+        moveDisplayToTopInternal(displayId);
+        syncInputTransactions(true /* waitForAnimations */);
+    }
+
+    /**
+     * Moves the given display to the top. If it cannot be moved to the top this method does
+     * nothing.
+     */
+    void moveDisplayToTopInternal(int displayId) {
         synchronized (mGlobalLock) {
             final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
             if (displayContent != null && mRoot.getTopChild() != displayContent) {
+                // Check whether anything prevents us from moving the display to the top.
+                if (!displayContent.canStealTopFocus()) {
+                    ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+                            "Not moving display (displayId=%d) to top. Top focused displayId=%d. "
+                                    + "Reason: FLAG_STEAL_TOP_FOCUS_DISABLED",
+                            displayId, mRoot.getTopFocusedDisplayContent().getDisplayId());
+                    return;
+                }
+
+                if (mPerDisplayFocusEnabled) {
+                    ProtoLog.i(WM_DEBUG_FOCUS_LIGHT,
+                            "Not moving display (displayId=%d) to top. Top focused displayId=%d. "
+                                    + "Reason: config_perDisplayFocusEnabled", displayId,
+                            mRoot.getTopFocusedDisplayContent().getDisplayId());
+                    return;
+                }
+
+                // Nothing prevented us from moving the display to the top. Let's do it!
                 displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP,
                         displayContent, true /* includingParents */);
             }
         }
-        syncInputTransactions(true /* waitForAnimations */);
     }
 
     @Override
@@ -7140,20 +7166,6 @@
         return 0;
     }
 
-    void setDockedRootTaskResizing(boolean resizing) {
-        getDefaultDisplayContentLocked().getDockedDividerController().setResizing(resizing);
-        requestTraversal();
-    }
-
-    @Override
-    public void setDockedTaskDividerTouchRegion(Rect touchRegion) {
-        synchronized (mGlobalLock) {
-            final DisplayContent dc = getDefaultDisplayContentLocked();
-            dc.getDockedDividerController().setTouchRegion(touchRegion);
-            dc.updateTouchExcludeRegion();
-        }
-    }
-
     void setForceDesktopModeOnExternalDisplays(boolean forceDesktopModeOnExternalDisplays) {
         synchronized (mGlobalLock) {
             mForceDesktopModeOnExternalDisplays = forceDesktopModeOnExternalDisplays;
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 060784d..e2c9c17 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -53,6 +53,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
@@ -821,54 +822,6 @@
         return 0;
     }
 
-    private int runSetLetterboxIsHorizontalReachabilityEnabled(PrintWriter pw)
-            throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetLetterboxIsVerticalReachabilityEnabled(PrintWriter pw)
-            throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsVerticalReachabilityEnabled(enabled);
-        }
-        return 0;
-    }
-
     private int runSetLetterboxDefaultPositionForHorizontalReachability(PrintWriter pw)
             throws RemoteException {
         @LetterboxHorizontalReachabilityPosition final int position;
@@ -931,32 +884,13 @@
         return 0;
     }
 
-    private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsEducationEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(PrintWriter pw)
+    private int runSetBooleanFlag(PrintWriter pw, Consumer<Boolean> setter)
             throws RemoteException {
         String arg = getNextArg();
+        if (arg == null) {
+            getErrPrintWriter().println("Error: expected true, 1, false, 0, but got empty input.");
+            return -1;
+        }
         final boolean enabled;
         switch (arg) {
             case "true":
@@ -973,30 +907,7 @@
         }
 
         synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(enabled);
-        }
-        return 0;
-    }
-
-    private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) {
-        String arg = getNextArg();
-        final boolean enabled;
-        switch (arg) {
-            case "true":
-            case "1":
-                enabled = true;
-                break;
-            case "false":
-            case "0":
-                enabled = false;
-                break;
-            default:
-                getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
-                return -1;
-        }
-
-        synchronized (mInternal.mGlobalLock) {
-            mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled);
+            setter.accept(enabled);
         }
         return 0;
     }
@@ -1039,10 +950,12 @@
                     runSetLetterboxVerticalPositionMultiplier(pw);
                     break;
                 case "--isHorizontalReachabilityEnabled":
-                    runSetLetterboxIsHorizontalReachabilityEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsHorizontalReachabilityEnabled);
                     break;
                 case "--isVerticalReachabilityEnabled":
-                    runSetLetterboxIsVerticalReachabilityEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsVerticalReachabilityEnabled);
                     break;
                 case "--defaultPositionForHorizontalReachability":
                     runSetLetterboxDefaultPositionForHorizontalReachability(pw);
@@ -1051,13 +964,23 @@
                     runSetLetterboxDefaultPositionForVerticalReachability(pw);
                     break;
                 case "--isEducationEnabled":
-                    runSetLetterboxIsEducationEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration::setIsEducationEnabled);
                     break;
                 case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
-                    runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setIsSplitScreenAspectRatioForUnresizableAppsEnabled);
                     break;
                 case "--isTranslucentLetterboxingEnabled":
-                    runSetTranslucentLetterboxingEnabled(pw);
+                    runSetBooleanFlag(pw, mLetterboxConfiguration
+                            ::setTranslucentLetterboxingOverrideEnabled);
+                    break;
+                case "--isCameraCompatRefreshEnabled":
+                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+                            .setCameraCompatRefreshEnabled(enabled));
+                    break;
+                case "--isCameraCompatRefreshCycleThroughStopEnabled":
+                    runSetBooleanFlag(pw, enabled -> mLetterboxConfiguration
+                            .setCameraCompatRefreshCycleThroughStopEnabled(enabled));
                     break;
                 default:
                     getErrPrintWriter().println(
@@ -1104,27 +1027,34 @@
                         mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
                         break;
                     case "isHorizontalReachabilityEnabled":
-                        mLetterboxConfiguration.getIsHorizontalReachabilityEnabled();
+                        mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
                         break;
                     case "isVerticalReachabilityEnabled":
-                        mLetterboxConfiguration.getIsVerticalReachabilityEnabled();
+                        mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
                         break;
                     case "defaultPositionForHorizontalReachability":
-                        mLetterboxConfiguration.getDefaultPositionForHorizontalReachability();
+                        mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
                         break;
                     case "defaultPositionForVerticalReachability":
-                        mLetterboxConfiguration.getDefaultPositionForVerticalReachability();
+                        mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
                         break;
                     case "isEducationEnabled":
-                        mLetterboxConfiguration.getIsEducationEnabled();
+                        mLetterboxConfiguration.resetIsEducationEnabled();
                         break;
                     case "isSplitScreenAspectRatioForUnresizableAppsEnabled":
                         mLetterboxConfiguration
-                                .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+                                .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
                         break;
                     case "isTranslucentLetterboxingEnabled":
                         mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
                         break;
+                    case "isCameraCompatRefreshEnabled":
+                        mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+                        break;
+                    case "isCameraCompatRefreshCycleThroughStopEnabled":
+                        mLetterboxConfiguration
+                                .resetCameraCompatRefreshCycleThroughStopEnabled();
+                        break;
                     default:
                         getErrPrintWriter().println(
                                 "Error: Unrecognized letterbox style option: " + arg);
@@ -1226,6 +1156,8 @@
             mLetterboxConfiguration.resetIsEducationEnabled();
             mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
             mLetterboxConfiguration.resetTranslucentLetterboxingEnabled();
+            mLetterboxConfiguration.resetCameraCompatRefreshEnabled();
+            mLetterboxConfiguration.resetCameraCompatRefreshCycleThroughStopEnabled();
         }
     }
 
@@ -1234,9 +1166,17 @@
             pw.println("Corner radius: "
                     + mLetterboxConfiguration.getLetterboxActivityCornersRadius());
             pw.println("Horizontal position multiplier: "
-                    + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+                    + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(
+                            false /* isInBookMode */));
             pw.println("Vertical position multiplier: "
-                    + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier());
+                    + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
+                            false /* isInTabletopMode */));
+            pw.println("Horizontal position multiplier (book mode): "
+                    + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier(
+                            true /* isInBookMode */));
+            pw.println("Vertical position multiplier (tabletop mode): "
+                    + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier(
+                            true /* isInTabletopMode */));
             pw.println("Aspect ratio: "
                     + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
             pw.println("Default min aspect ratio for unresizable apps: "
@@ -1253,15 +1193,21 @@
                     mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
             pw.println("Current position for horizontal reachability:"
                     + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
-                        mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability()));
+                    mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(false)));
             pw.println("Current position for vertical reachability:"
                     + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
-                        mLetterboxConfiguration.getLetterboxPositionForVerticalReachability()));
+                    mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(false)));
             pw.println("Is education enabled: "
                     + mLetterboxConfiguration.getIsEducationEnabled());
             pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
                     + mLetterboxConfiguration
                             .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+
+            pw.println("    Is activity \"refresh\" in camera compatibility treatment enabled: "
+                    + mLetterboxConfiguration.isCameraCompatRefreshEnabled());
+            pw.println("    Refresh using \"stopped -> resumed\" cycle: "
+                    + mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled());
+
             pw.println("Background type: "
                     + LetterboxConfiguration.letterboxBackgroundTypeToString(
                             mLetterboxConfiguration.getLetterboxBackgroundType()));
@@ -1471,7 +1417,12 @@
         pw.println("        unresizable apps.");
         pw.println("      --isTranslucentLetterboxingEnabled [true|1|false|0]");
         pw.println("        Whether letterboxing for translucent activities is enabled.");
-
+        pw.println("      --isCameraCompatRefreshEnabled [true|1|false|0]");
+        pw.println("        Whether camera compatibility refresh is enabled.");
+        pw.println("      --isCameraCompatRefreshCycleThroughStopEnabled [true|1|false|0]");
+        pw.println("        Whether activity \"refresh\" in camera compatibility treatment should");
+        pw.println("        happen using the \"stopped -> resumed\" cycle rather than");
+        pw.println("        \"paused -> resumed\" cycle.");
         pw.println("  reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
         pw.println("      |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
         pw.println("      |horizontalPositionMultiplier|verticalPositionMultiplier");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 4ec1153..b624e80 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -51,7 +51,6 @@
 import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -722,7 +721,7 @@
         }
 
         if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING) != 0) {
-            tr.setDragResizing(c.getDragResizing(), DRAG_RESIZE_MODE_FREEFORM);
+            tr.setDragResizing(c.getDragResizing());
         }
 
         final int childWindowingMode = c.getActivityWindowingMode();
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index dcd30bb..91452c6 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -52,6 +52,7 @@
 import android.app.IApplicationThread;
 import android.app.ProfilerInfo;
 import android.app.servertransaction.ConfigurationChangeItem;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -206,6 +207,7 @@
     /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
     private volatile boolean mHasCachedConfiguration;
 
+    private int mTopActivityDeviceId = VirtualDeviceManager.DEVICE_ID_DEFAULT;
     /**
      * Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
      * registered.
@@ -868,13 +870,13 @@
     // TODO(b/199277729): Consider whether we need to add special casing for edge cases like
     //  activity-embeddings etc.
     void updateAppSpecificSettingsForAllActivitiesInPackage(String packageName, Integer nightMode,
-            LocaleList localesOverride) {
+            LocaleList localesOverride, @Configuration.GrammaticalGender int gender) {
         for (int i = mActivities.size() - 1; i >= 0; --i) {
             final ActivityRecord r = mActivities.get(i);
             // Activities from other packages could be sharing this process. Only propagate updates
             // to those activities that are part of the package whose app-specific settings changed
             if (packageName.equals(r.packageName)
-                    && r.applyAppSpecificConfig(nightMode, localesOverride)
+                    && r.applyAppSpecificConfig(nightMode, localesOverride, gender)
                     && r.isVisibleRequested()) {
                 r.ensureActivityConfiguration(0 /* globalChanges */, true /* preserveWindow */);
             }
@@ -1381,8 +1383,16 @@
     @Override
     public void onConfigurationChanged(Configuration newGlobalConfig) {
         super.onConfigurationChanged(newGlobalConfig);
+
+        // If deviceId for the top-activity changed, schedule passing it to the app process.
+        boolean topActivityDeviceChanged = false;
+        int deviceId = getTopActivityDeviceId();
+        if (deviceId != mTopActivityDeviceId) {
+            topActivityDeviceChanged = true;
+        }
+
         final Configuration config = getConfiguration();
-        if (mLastReportedConfiguration.equals(config)) {
+        if (mLastReportedConfiguration.equals(config) & !topActivityDeviceChanged) {
             // Nothing changed.
             if (Build.IS_DEBUGGABLE && mHasImeService) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
@@ -1396,7 +1406,34 @@
             mHasPendingConfigurationChange = true;
             return;
         }
-        dispatchConfiguration(config);
+
+        // TODO(b/263402938): Add tests that capture the deviceId dispatch to the client.
+        mTopActivityDeviceId = deviceId;
+        dispatchConfiguration(config, topActivityDeviceChanged ? mTopActivityDeviceId
+                : VirtualDeviceManager.DEVICE_ID_INVALID);
+    }
+
+    private int getTopActivityDeviceId() {
+        ActivityRecord topActivity = getTopNonFinishingActivity();
+        int updatedDeviceId = mTopActivityDeviceId;
+        if (topActivity != null && topActivity.mDisplayContent != null) {
+            updatedDeviceId = mAtm.mTaskSupervisor.getDeviceIdForDisplayId(
+                    topActivity.mDisplayContent.mDisplayId);
+        }
+        return updatedDeviceId;
+    }
+
+    @Nullable
+    private ActivityRecord getTopNonFinishingActivity() {
+        if (mActivities.isEmpty()) {
+            return null;
+        }
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            if (!mActivities.get(i).finishing) {
+                return mActivities.get(i);
+            }
+        }
+        return null;
     }
 
     @Override
@@ -1423,6 +1460,10 @@
     }
 
     void dispatchConfiguration(Configuration config) {
+        dispatchConfiguration(config, getTopActivityDeviceId());
+    }
+
+    void dispatchConfiguration(Configuration config, int deviceId) {
         mHasPendingConfigurationChange = false;
         if (mThread == null) {
             if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1449,10 +1490,16 @@
             }
         }
 
-        scheduleConfigurationChange(mThread, config);
+        scheduleConfigurationChange(mThread, config, deviceId);
     }
 
     private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
+        // By default send invalid deviceId as no-op signal so it's not updated on the client side.
+        scheduleConfigurationChange(thread, config, VirtualDeviceManager.DEVICE_ID_INVALID);
+    }
+
+    private void scheduleConfigurationChange(IApplicationThread thread, Configuration config,
+            int deviceId) {
         ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
                 config);
         if (Build.IS_DEBUGGABLE && mHasImeService) {
@@ -1462,7 +1509,7 @@
         mHasCachedConfiguration = false;
         try {
             mAtm.getLifecycleManager().scheduleTransaction(thread,
-                    ConfigurationChangeItem.obtain(config));
+                    ConfigurationChangeItem.obtain(config, deviceId));
         } catch (Exception e) {
             Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 3ca254d..ae31ee8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -34,9 +34,6 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
-import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER;
-import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM;
-import static android.view.WindowCallbacks.RESIZE_MODE_INVALID;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.systemBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
@@ -122,8 +119,6 @@
 import static com.android.server.wm.AnimationSpecProto.MOVE;
 import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
 import static com.android.server.wm.DisplayContent.logsGestureExclusionRestrictions;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
-import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
 import static com.android.server.wm.IdentifierProto.USER_ID;
@@ -369,7 +364,6 @@
     boolean mHidden = true;    // Used to determine if to show child windows.
     private boolean mDragResizing;
     private boolean mDragResizingChangeReported = true;
-    private int mResizeMode;
     private boolean mRedrawForSyncReported;
 
     /**
@@ -3942,24 +3936,14 @@
         if (isDragResizeChanged) {
             setDragResizing();
         }
-        int resizeMode = RESIZE_MODE_INVALID;
-        if (isDragResizing()) {
-            switch (getResizeMode()) {
-                case DRAG_RESIZE_MODE_FREEFORM:
-                    resizeMode = RESIZE_MODE_FREEFORM;
-                    break;
-                case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
-                    resizeMode = RESIZE_MODE_DOCKED_DIVIDER;
-                    break;
-            }
-        }
+        final boolean isDragResizing = isDragResizing();
 
         markRedrawForSyncReported();
 
         try {
             mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
                     getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
-                    syncWithBuffers ? mSyncSeqId : -1, resizeMode);
+                    syncWithBuffers ? mSyncSeqId : -1, isDragResizing);
             if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
                     .getMergedConfiguration().windowConfiguration.getRotation()) {
                 mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
@@ -4202,10 +4186,6 @@
         super.resetDragResizingChangeReported();
     }
 
-    int getResizeMode() {
-        return mResizeMode;
-    }
-
     private boolean computeDragResizing() {
         final Task task = getTask();
         if (task == null) {
@@ -4228,8 +4208,7 @@
             return true;
         }
 
-        return getDisplayContent().mDividerControllerLocked.isResizing()
-                && !task.inFreeformWindowingMode() && !isGoneForLayout();
+        return false;
     }
 
     void setDragResizing() {
@@ -4238,25 +4217,12 @@
             return;
         }
         mDragResizing = resizing;
-        final Task task = getTask();
-        if (task != null && task.isDragResizing()) {
-            mResizeMode = task.getDragResizeMode();
-        } else {
-            mResizeMode = mDragResizing && getDisplayContent().mDividerControllerLocked.isResizing()
-                    ? DRAG_RESIZE_MODE_DOCKED_DIVIDER
-                    : DRAG_RESIZE_MODE_FREEFORM;
-        }
     }
 
     boolean isDragResizing() {
         return mDragResizing;
     }
 
-    boolean isDockedResizing() {
-        return (mDragResizing && getResizeMode() == DRAG_RESIZE_MODE_DOCKED_DIVIDER)
-                || (isChildWindow() && getParentWindow().isDockedResizing());
-    }
-
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
@@ -5932,10 +5898,6 @@
             //    level. Because the animation runs before display is rotated, task bounds should
             //    represent the frames in display space coordinates.
             outFrame.set(getTask().getBounds());
-        } else if (isDockedResizing()) {
-            // If we are animating while docked resizing, then use the root task bounds as the
-            // animation target (which will be different than the task bounds)
-            outFrame.set(getTask().getParent().getBounds());
         } else {
             outFrame.set(getParentFrame());
         }
@@ -6276,10 +6238,7 @@
     @Override
     public void handleTapOutsideFocusInsideSelf() {
         final DisplayContent displayContent = getDisplayContent();
-        if (!displayContent.isOnTop()) {
-            displayContent.getParent().positionChildAt(WindowContainer.POSITION_TOP, displayContent,
-                    true /* includingParents */);
-        }
+        mWmService.moveDisplayToTopInternal(getDisplayId());
         mWmService.handleTaskFocusChange(getTask(), mActivityRecord);
     }
 
diff --git a/services/core/java/com/android/server/wm/package-info.java b/services/core/java/com/android/server/wm/package-info.java
new file mode 100644
index 0000000..6ab5835
--- /dev/null
+++ b/services/core/java/com/android/server/wm/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+
+/**
+ * @hide
+ * TODO(b/146466118) remove this javadoc tag
+ */
+@android.annotation.Hide
+package com.android.server.wm;
diff --git a/services/core/java/com/android/server/wm/utils/StateMachine.java b/services/core/java/com/android/server/wm/utils/StateMachine.java
index 91a5dc4..350784e 100644
--- a/services/core/java/com/android/server/wm/utils/StateMachine.java
+++ b/services/core/java/com/android/server/wm/utils/StateMachine.java
@@ -177,19 +177,18 @@
     }
 
     /**
-     * Process an event. Search handler for a given event and {@link Handler#handle(int)}. If the
-     * handler cannot handle the event, delegate it to a handler for a parent of the given state.
+     * Process an event. Search handler for a given event and {@link Handler#handle(int, Object)}.
+     * If the handler cannot handle the event, delegate it to a handler for a parent of the given
+     * state.
      *
      * @param event Type of an event.
      */
     public void handle(int event, @Nullable Object param) {
-        int state = mState;
-        while (state != 0) {
+        for (int state = mState;; state >>= 4) {
             final Handler h = mStateHandlers.get(state);
-            if (h != null && h.handle(event, param)) {
+            if ((h != null && h.handle(event, param)) || state == 0) {
                 return;
             }
-            state >>= 4;
         }
     }
 
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.cpp b/services/core/jni/gnss/GnssMeasurementCallback.cpp
index d37f3bd..a1c5708 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.cpp
+++ b/services/core/jni/gnss/GnssMeasurementCallback.cpp
@@ -58,6 +58,7 @@
 jmethodID method_gnssAgcBuilderBuild;
 jmethodID method_gnssMeasurementsEventBuilderCtor;
 jmethodID method_gnssMeasurementsEventBuilderSetClock;
+jmethodID method_gnssMeasurementsEventBuilderSetFullTracking;
 jmethodID method_gnssMeasurementsEventBuilderSetMeasurements;
 jmethodID method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls;
 jmethodID method_gnssMeasurementsEventBuilderBuild;
@@ -109,6 +110,10 @@
             env->GetMethodID(class_gnssMeasurementsEventBuilder, "setGnssAutomaticGainControls",
                              "([Landroid/location/GnssAutomaticGainControl;)"
                              "Landroid/location/GnssMeasurementsEvent$Builder;");
+    method_gnssMeasurementsEventBuilderSetFullTracking =
+            env->GetMethodID(class_gnssMeasurementsEventBuilder, "setFullTracking",
+                             "(Z)"
+                             "Landroid/location/GnssMeasurementsEvent$Builder;");
     method_gnssMeasurementsEventBuilderBuild =
             env->GetMethodID(class_gnssMeasurementsEventBuilder, "build",
                              "()Landroid/location/GnssMeasurementsEvent;");
@@ -228,7 +233,8 @@
 }
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray, jobjectArray gnssAgcArray) {
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray,
+                        bool hasFullTracking, jboolean isFullTracking) {
     jobject gnssMeasurementsEventBuilderObject =
             env->NewObject(class_gnssMeasurementsEventBuilder,
                            method_gnssMeasurementsEventBuilderCtor);
@@ -240,6 +246,11 @@
     callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
                                    method_gnssMeasurementsEventBuilderSetGnssAutomaticGainControls,
                                    gnssAgcArray);
+    if (hasFullTracking) {
+        callObjectMethodIgnoringResult(env, gnssMeasurementsEventBuilderObject,
+                                       method_gnssMeasurementsEventBuilderSetFullTracking,
+                                       isFullTracking);
+    }
     jobject gnssMeasurementsEventObject =
             env->CallObjectMethod(gnssMeasurementsEventBuilderObject,
                                   method_gnssMeasurementsEventBuilderBuild);
@@ -381,7 +392,14 @@
 
     jobjectArray gnssAgcArray = nullptr;
     gnssAgcArray = translateAllGnssAgcs(env, data.gnssAgcs);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray);
+    if (this->getInterfaceVersion() >= 3) {
+        setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+                           /*hasFullTracking=*/true, data.isFullTracking);
+    } else {
+        setMeasurementData(env, mCallbacksObj, clock, measurementArray, gnssAgcArray,
+                           /*hasFullTracking=*/false,
+                           /*isFullTracking=*/JNI_FALSE);
+    }
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
diff --git a/services/core/jni/gnss/GnssMeasurementCallback.h b/services/core/jni/gnss/GnssMeasurementCallback.h
index c8f1803..fde56881 100644
--- a/services/core/jni/gnss/GnssMeasurementCallback.h
+++ b/services/core/jni/gnss/GnssMeasurementCallback.h
@@ -48,7 +48,8 @@
 void GnssMeasurement_class_init_once(JNIEnv* env, jclass& clazz);
 
 void setMeasurementData(JNIEnv* env, jobject& callbacksObj, jobject clock,
-                        jobjectArray measurementArray, jobjectArray gnssAgcArray);
+                        jobjectArray measurementArray, jobjectArray gnssAgcArray,
+                        bool hasFullTracking, jboolean isFullTracking);
 
 class GnssMeasurementCallbackAidl : public hardware::gnss::BnGnssMeasurementCallback {
 public:
@@ -140,7 +141,9 @@
     size_t count = getMeasurementCount(data);
     jobjectArray measurementArray =
             translateAllGnssMeasurements(env, data.measurements.data(), count);
-    setMeasurementData(env, mCallbacksObj, clock, measurementArray, nullptr);
+    setMeasurementData(env, mCallbacksObj, clock, measurementArray, /*gnssAgcArray=*/nullptr,
+                       /*hasFullTracking=*/false,
+                       /*isFullTracking=*/JNI_FALSE);
 
     env->DeleteLocalRef(clock);
     env->DeleteLocalRef(measurementArray);
diff --git a/services/credentials/java/com/android/server/credentials/ClearRequestSession.java b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
new file mode 100644
index 0000000..6b254bf
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ClearRequestSession.java
@@ -0,0 +1,142 @@
+/*
+ * 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.credentials;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.ClearCredentialStateRequest;
+import android.credentials.IClearCredentialStateCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Central session for a single clearCredentialState request. This class listens to the
+ * responses from providers, and updates the provider(S) state.
+ */
+public final class ClearRequestSession extends RequestSession<ClearCredentialStateRequest,
+        IClearCredentialStateCallback>
+        implements ProviderSession.ProviderInternalCallback<Void> {
+    private static final String TAG = "GetRequestSession";
+
+    public ClearRequestSession(Context context, int userId,
+            IClearCredentialStateCallback callback, ClearCredentialStateRequest request,
+            String callingPackage) {
+        super(context, userId, request, callback, RequestInfo.TYPE_UNDEFINED, callingPackage);
+    }
+
+    /**
+     * Creates a new provider session, and adds it list of providers that are contributing to
+     * this session.
+     * @return the provider session created within this request session, for the given provider
+     * info.
+     */
+    @Override
+    @Nullable
+    public ProviderSession initiateProviderSession(CredentialProviderInfo providerInfo,
+            RemoteCredentialService remoteCredentialService) {
+        ProviderClearSession providerClearSession = ProviderClearSession
+                .createNewSession(mContext, mUserId, providerInfo,
+                        this, remoteCredentialService);
+        if (providerClearSession != null) {
+            Log.i(TAG, "In startProviderSession - provider session created and being added");
+            mProviders.put(providerClearSession.getComponentName().flattenToString(),
+                    providerClearSession);
+        }
+        return providerClearSession;
+    }
+
+    @Override // from provider session
+    public void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        Log.i(TAG, "in onStatusChanged with status: " + status);
+        if (ProviderSession.isTerminatingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged terminating status");
+            onProviderTerminated(componentName);
+        } else if (ProviderSession.isCompletionStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            onProviderResponseComplete(componentName);
+        }
+    }
+
+    @Override
+    public void onFinalResponseReceived(ComponentName componentName, Void response) {
+        respondToClientWithResponseAndFinish();
+    }
+
+    @Override
+    protected void onProviderResponseComplete(ComponentName componentName) {
+        if (!isAnyProviderPending()) {
+            onFinalResponseReceived(componentName, null);
+        }
+    }
+
+    @Override
+    protected void onProviderTerminated(ComponentName componentName) {
+        if (!isAnyProviderPending()) {
+            processResponses();
+        }
+    }
+
+    @Override
+    protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
+        //Not applicable for clearCredential as UI is not needed
+    }
+
+    @Override
+    public void onFinalErrorReceived(ComponentName componentName, String errorType,
+            String message) {
+        //Not applicable for clearCredential as response is not picked by the user
+    }
+
+    private void respondToClientWithResponseAndFinish() {
+        Log.i(TAG, "respondToClientWithResponseAndFinish");
+        try {
+            mClientCallback.onSuccess();
+        } catch (RemoteException e) {
+            Log.i(TAG, "Issue while propagating the response to the client");
+        }
+        finishSession();
+    }
+
+    private void respondToClientWithErrorAndFinish(String errorType, String errorMsg) {
+        Log.i(TAG, "respondToClientWithErrorAndFinish");
+        try {
+            mClientCallback.onError(errorType, errorMsg);
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        finishSession();
+    }
+    private void processResponses() {
+        for (ProviderSession session: mProviders.values()) {
+            if (session.isProviderResponseSet()) {
+                // If even one provider responded successfully, send back the response
+                // TODO: Aggregate other exceptions
+                respondToClientWithResponseAndFinish();
+                return;
+            }
+        }
+        // TODO: Replace with properly defined error type
+        respondToClientWithErrorAndFinish("unknown", "All providers failed");
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 1f74e93..d29f86e 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -314,9 +314,45 @@
         @Override
         public ICancellationSignal clearCredentialState(ClearCredentialStateRequest request,
                 IClearCredentialStateCallback callback, String callingPackage) {
-            // TODO: implement.
-            Log.i(TAG, "clearCredentialSession");
+            Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage);
+            // TODO : Implement cancellation
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // New request session, scoped for this request only.
+            final ClearRequestSession session =
+                    new ClearRequestSession(
+                            getContext(),
+                            UserHandle.getCallingUserId(),
+                            callback,
+                            request,
+                            callingPackage);
+
+            // Initiate all provider sessions
+            // TODO: Determine if provider needs to have clear capability in their manifest
+            List<ProviderSession> providerSessions =
+                    initiateProviderSessions(session, List.of());
+
+            if (providerSessions.isEmpty()) {
+                try {
+                    // TODO("Replace with properly defined error type")
+                    callback.onError("unknown_type",
+                            "No providers available to fulfill request.");
+                } catch (RemoteException e) {
+                    Log.i(TAG, "Issue invoking onError on IClearCredentialStateCallback "
+                            + "callback: " + e.getMessage());
+                }
+            }
+
+            // Iterate over all provider sessions and invoke the request
+            providerSessions.forEach(
+                    providerClearSession -> {
+                        providerClearSession
+                                .getRemoteCredentialService()
+                                .onClearCredentialState(
+                                        (android.service.credentials.ClearCredentialStateRequest)
+                                                providerClearSession.getProviderRequest(),
+                                        /* callback= */ providerClearSession);
+                    });
             return cancelTransport;
         }
     }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index 08fdeed..c03d505 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -76,7 +76,7 @@
     @GuardedBy("mLock")
     public ProviderSession initiateProviderSessionForRequestLocked(
             RequestSession requestSession, List<String> requestOptions) {
-        if (!isServiceCapableLocked(requestOptions)) {
+        if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) {
             Log.i(TAG, "Service is not capable");
             return null;
         }
@@ -88,9 +88,7 @@
         }
         final RemoteCredentialService remoteService = new RemoteCredentialService(
                 getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
-        ProviderSession providerSession =
-                requestSession.initiateProviderSession(mInfo, remoteService);
-        return providerSession;
+        return requestSession.initiateProviderSession(mInfo, remoteService);
     }
 
     /** Return true if at least one capability found. */
diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
new file mode 100644
index 0000000..020552a
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java
@@ -0,0 +1,119 @@
+/*
+ * 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.credentials;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.credentials.ClearCredentialStateException;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.ClearCredentialStateRequest;
+import android.service.credentials.CredentialProviderInfo;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ *
+ * @hide
+ */
+public final class ProviderClearSession extends ProviderSession<ClearCredentialStateRequest,
+        Void>
+        implements
+        RemoteCredentialService.ProviderCallbacks<Void> {
+    private static final String TAG = "ProviderClearSession";
+
+    private ClearCredentialStateException mProviderException;
+
+    /** Creates a new provider session to be used by the request session. */
+    @Nullable public static ProviderClearSession createNewSession(
+            Context context,
+            @UserIdInt int userId,
+            CredentialProviderInfo providerInfo,
+            ClearRequestSession clearRequestSession,
+            RemoteCredentialService remoteCredentialService) {
+        ClearCredentialStateRequest providerRequest =
+                createProviderRequest(
+                        clearRequestSession.mClientRequest,
+                        clearRequestSession.mClientCallingPackage);
+        return new ProviderClearSession(context, providerInfo, clearRequestSession, userId,
+                    remoteCredentialService, providerRequest);
+    }
+
+    @Nullable
+    private static ClearCredentialStateRequest createProviderRequest(
+            android.credentials.ClearCredentialStateRequest clientRequest,
+            String clientCallingPackage
+    ) {
+        // TODO: Determine if provider needs to declare clear capability in manifest
+        return new ClearCredentialStateRequest(
+                new CallingAppInfo(clientCallingPackage, new ArraySet<>()),
+                clientRequest.getData());
+    }
+
+    public ProviderClearSession(Context context,
+            CredentialProviderInfo info,
+            ProviderInternalCallback callbacks,
+            int userId, RemoteCredentialService remoteCredentialService,
+            ClearCredentialStateRequest providerRequest) {
+        super(context, info, providerRequest, callbacks, userId, remoteCredentialService);
+        setStatus(Status.PENDING);
+    }
+
+    @Override
+    public void onProviderResponseSuccess(@Nullable Void response) {
+        Log.i(TAG, "in onProviderResponseSuccess");
+        mProviderResponseSet = true;
+        updateStatusAndInvokeCallback(Status.COMPLETE);
+    }
+
+    /** Called when the provider response resulted in a failure. */
+    @Override // Callback from the remote provider
+    public void onProviderResponseFailure(int errorCode, Exception exception) {
+        if (exception instanceof ClearCredentialStateException) {
+            mProviderException = (ClearCredentialStateException) exception;
+        }
+        updateStatusAndInvokeCallback(toStatus(errorCode));
+    }
+
+    /** Called when provider service dies. */
+    @Override // Callback from the remote provider
+    public void onProviderServiceDied(RemoteCredentialService service) {
+        if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+            updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+        } else {
+            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+                    + "this should not happen");
+        }
+    }
+
+    @Nullable
+    @Override
+    protected ProviderData prepareUiData() {
+        //Not applicable for clearCredential as response is not picked by the user
+        return null;
+    }
+
+    @Override
+    protected void onUiEntrySelected(String entryType, String entryId,
+            ProviderPendingIntentResponse providerPendingIntentResponse) {
+        //Not applicable for clearCredential as response is not picked by the user
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index 7ecae9d..93e816a 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -51,6 +51,7 @@
     @Nullable protected Credential mFinalCredentialResponse;
     @NonNull protected final T mProviderRequest;
     @Nullable protected R mProviderResponse;
+    @NonNull protected Boolean mProviderResponseSet = false;
     @Nullable protected Pair<String, CredentialEntry> mUiRemoteEntry;
 
 
@@ -84,7 +85,8 @@
      */
     public static boolean isCompletionStatus(Status status) {
         return status == Status.CREDENTIAL_RECEIVED_FROM_INTENT
-                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION;
+                || status == Status.CREDENTIAL_RECEIVED_FROM_SELECTION
+                || status == Status.COMPLETE;
     }
 
     /**
@@ -130,7 +132,8 @@
         CREDENTIAL_RECEIVED_FROM_INTENT,
         PENDING_INTENT_INVOKED,
         CREDENTIAL_RECEIVED_FROM_SELECTION,
-        SAVE_ENTRIES_RECEIVED, CANCELED
+        SAVE_ENTRIES_RECEIVED, CANCELED,
+        COMPLETE
     }
 
     /** Converts exception to a provider session status. */
@@ -183,6 +186,11 @@
         return mProviderRequest;
     }
 
+    /** Returns whether the provider response is set. */
+    protected Boolean isProviderResponseSet() {
+        return mProviderResponse != null || mProviderResponseSet;
+    }
+
     /** Update the response state stored with the provider session. */
     @Nullable protected R getProviderResponse() {
         return mProviderResponse;
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
index 6049fd9..8cad6ac 100644
--- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -21,6 +21,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.credentials.ClearCredentialStateException;
 import android.credentials.CreateCredentialException;
 import android.credentials.GetCredentialException;
 import android.os.Handler;
@@ -30,10 +31,12 @@
 import android.service.credentials.BeginCreateCredentialResponse;
 import android.service.credentials.BeginGetCredentialRequest;
 import android.service.credentials.BeginGetCredentialResponse;
+import android.service.credentials.ClearCredentialStateRequest;
 import android.service.credentials.CredentialProviderErrors;
 import android.service.credentials.CredentialProviderService;
 import android.service.credentials.IBeginCreateCredentialCallback;
 import android.service.credentials.IBeginGetCredentialCallback;
+import android.service.credentials.IClearCredentialStateCallback;
 import android.service.credentials.ICredentialProviderService;
 import android.text.format.DateUtils;
 import android.util.Log;
@@ -195,6 +198,53 @@
                 handleExecutionResponse(result, error, cancellationSink, callback)));
     }
 
+    /** Main entry point to be called for executing a clearCredentialState call on the remote
+     * provider service.
+     * @param request the request to be sent to the provider
+     * @param callback the callback to be used to send back the provider response to the
+     *                 {@link ProviderClearSession} class that maintains provider state
+     */
+    public void onClearCredentialState(@NonNull ClearCredentialStateRequest request,
+            ProviderCallbacks<Void> callback) {
+        Log.i(TAG, "In onClearCredentialState in RemoteCredentialService");
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>();
+
+        CompletableFuture<Void> connectThenExecute =
+                postAsync(service -> {
+                    CompletableFuture<Void> clearCredentialFuture =
+                            new CompletableFuture<>();
+                    ICancellationSignal cancellationSignal = service.onClearCredentialState(
+                            request, new IClearCredentialStateCallback.Stub() {
+                                @Override
+                                public void onSuccess() {
+                                    Log.i(TAG, "In onSuccess onClearCredentialState "
+                                            + "in RemoteCredentialService");
+                                    clearCredentialFuture.complete(null);
+                                }
+
+                                @Override
+                                public void onFailure(String errorType, CharSequence message) {
+                                    Log.i(TAG, "In onFailure in RemoteCredentialService");
+                                    String errorMsg = message == null ? "" :
+                                            String.valueOf(message);
+                                    clearCredentialFuture.completeExceptionally(
+                                            new ClearCredentialStateException(errorType, errorMsg));
+                                }});
+                    CompletableFuture<Void> future = futureRef.get();
+                    if (future != null && future.isCancelled()) {
+                        dispatchCancellationSignal(cancellationSignal);
+                    } else {
+                        cancellationSink.set(cancellationSignal);
+                    }
+                    return clearCredentialFuture;
+                }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+
+        futureRef.set(connectThenExecute);
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() ->
+                handleExecutionResponse(result, error, cancellationSink, callback)));
+    }
+
     private <T> void handleExecutionResponse(T result,
             Throwable error,
             AtomicReference<ICancellationSignal> cancellationSink,
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
index 937fac9..6d7cb4c 100644
--- a/services/credentials/java/com/android/server/credentials/RequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -144,7 +144,7 @@
         mProviders.clear();
     }
 
-    private boolean isAnyProviderPending() {
+    boolean isAnyProviderPending() {
         for (ProviderSession session : mProviders.values()) {
             if (ProviderSession.isStatusWaitingForRemoteResponse(session.getStatus())) {
                 return true;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
index 4634ff5..e458145 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/ActiveAdmin.java
@@ -34,6 +34,7 @@
 import android.app.admin.DeviceAdminInfo;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.FactoryResetProtectionPolicy;
+import android.app.admin.PackagePolicy;
 import android.app.admin.PasswordPolicy;
 import android.app.admin.PreferentialNetworkServiceConfig;
 import android.app.admin.WifiSsidPolicy;
@@ -157,6 +158,9 @@
     private static final String TAG_SSID_ALLOWLIST = "ssid-allowlist";
     private static final String TAG_SSID_DENYLIST = "ssid-denylist";
     private static final String TAG_SSID = "ssid";
+    private static final String TAG_CROSS_PROFILE_CALLER_ID_POLICY = "caller-id-policy";
+    private static final String TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY = "contacts-policy";
+    private static final String TAG_PACKAGE_POLICY_PACKAGE_NAMES = "package-policy-packages";
     private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIGS =
             "preferential_network_service_configs";
     private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIG =
@@ -167,6 +171,8 @@
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
     private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
+    private static final String ATTR_PACKAGE_POLICY_MODE = "package-policy-type";
+
 
     DeviceAdminInfo info;
 
@@ -315,6 +321,12 @@
     // Time by which the profile should be turned on according to System.currentTimeMillis().
     long mProfileOffDeadline = 0;
 
+    // The package policy for Cross Profile Contacts Search
+    PackagePolicy mManagedProfileCallerIdAccess = null;
+
+    // The package policy for Cross Profile Contacts Search
+    PackagePolicy mManagedProfileContactsAccess = null;
+
     public String mAlwaysOnVpnPackage;
     public boolean mAlwaysOnVpnLockdown;
     boolean mCommonCriteriaMode;
@@ -626,6 +638,22 @@
         if (mtePolicy != DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY) {
             writeAttributeValueToXml(out, TAG_MTE_POLICY, mtePolicy);
         }
+        writePackagePolicy(out, TAG_CROSS_PROFILE_CALLER_ID_POLICY,
+                mManagedProfileCallerIdAccess);
+        writePackagePolicy(out, TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY,
+                mManagedProfileContactsAccess);
+    }
+
+    private void writePackagePolicy(TypedXmlSerializer out, String tag,
+            PackagePolicy packagePolicy) throws IOException {
+        if (packagePolicy == null) {
+            return;
+        }
+        out.startTag(null, tag);
+        out.attributeInt(null, ATTR_PACKAGE_POLICY_MODE, packagePolicy.getPolicyType());
+        writePackageListToXml(out, TAG_PACKAGE_POLICY_PACKAGE_NAMES,
+                new ArrayList<>(packagePolicy.getPackageNames()));
+        out.endTag(null, tag);
     }
 
     private List<String> ssidsToStrings(Set<WifiSsid> ssids) {
@@ -914,6 +942,10 @@
                 }
             } else if (TAG_MTE_POLICY.equals(tag)) {
                 mtePolicy = parser.getAttributeInt(null, ATTR_VALUE);
+            } else if (TAG_CROSS_PROFILE_CALLER_ID_POLICY.equals(tag)) {
+                mManagedProfileCallerIdAccess = readPackagePolicy(parser);
+            } else if (TAG_CROSS_PROFILE_CONTACTS_SEARCH_POLICY.equals(tag)) {
+                mManagedProfileContactsAccess = readPackagePolicy(parser);
             } else {
                 Slogf.w(LOG_TAG, "Unknown admin tag: %s", tag);
                 XmlUtils.skipCurrentTag(parser);
@@ -921,6 +953,14 @@
         }
     }
 
+    private PackagePolicy readPackagePolicy(TypedXmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        int policy = parser.getAttributeInt(null, ATTR_PACKAGE_POLICY_MODE);
+        Set<String> packageNames = new ArraySet<>(
+                readPackageList(parser, TAG_PACKAGE_POLICY_PACKAGE_NAMES));
+        return new PackagePolicy(policy, packageNames);
+    }
+
     private List<WifiSsid> readWifiSsids(TypedXmlPullParser parser, String tag)
             throws XmlPullParserException, IOException {
         List<String> ssidStrings = new ArrayList<>();
@@ -1106,6 +1146,21 @@
                 key -> UserRestrictionsUtils.isGlobal(adminType, key));
     }
 
+    void dumpPackagePolicy(IndentingPrintWriter pw, String name, PackagePolicy policy) {
+        pw.print(name);
+        pw.println(":");
+        if (policy != null) {
+            pw.increaseIndent();
+            pw.print("policyType=");
+            pw.println(policy.getPolicyType());
+            pw.println("packageNames:");
+            pw.increaseIndent();
+            policy.getPackageNames().forEach(item -> pw.println(item));
+            pw.decreaseIndent();
+            pw.decreaseIndent();
+        }
+    }
+
     void dump(IndentingPrintWriter pw) {
         pw.print("uid=");
         pw.println(getUid());
@@ -1258,6 +1313,13 @@
         pw.print("defaultEnabledRestrictionsAlreadySet=");
         pw.println(defaultEnabledRestrictionsAlreadySet);
 
+
+        dumpPackagePolicy(pw, "managedProfileCallerIdPolicy",
+                mManagedProfileCallerIdAccess);
+
+        dumpPackagePolicy(pw, "managedProfileContactsPolicy",
+                mManagedProfileContactsAccess);
+
         pw.print("isParent=");
         pw.println(isParent);
 
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 667f41f..8c2065e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -212,6 +212,7 @@
 import android.app.admin.FullyManagedDeviceProvisioningParams;
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.app.admin.NetworkEvent;
+import android.app.admin.PackagePolicy;
 import android.app.admin.ParcelableGranteeMap;
 import android.app.admin.ParcelableResource;
 import android.app.admin.PasswordMetrics;
@@ -759,6 +760,12 @@
     private EnterpriseSpecificIdCalculator mEsidCalculator;
 
     /**
+     * Contains the list of OEM Default Role Holders for Contact-related roles
+     * (DIALER, SMS, SYSTEM_CONTACTS)
+     */
+    private final Set<String> mContactSystemRoleHolders;
+
+    /**
      * Contains (package-user) pairs to remove. An entry (p, u) implies that removal of package p
      * is requested for user u.
      */
@@ -1907,6 +1914,7 @@
         if (!mHasFeature) {
             // Skip the rest of the initialization
             mSetupContentObserver = null;
+            mContactSystemRoleHolders = Collections.emptySet();
             return;
         }
 
@@ -1952,10 +1960,50 @@
             mDevicePolicyEngine.load();
         }
 
+        mContactSystemRoleHolders = fetchOemSystemHolders(/* roleResIds...= */
+                com.android.internal.R.string.config_defaultSms,
+                com.android.internal.R.string.config_defaultDialer,
+                com.android.internal.R.string.config_systemContacts
+        );
+
         // The binder caches are not enabled until the first invalidation.
         invalidateBinderCaches();
     }
 
+    /**
+     * Fetch the OEM System Holders for the supplied roleNames
+     *
+     * @param roleResIds the list of resource ids whose role holders are needed
+     * @return the set of packageNames that handle the requested roles
+     */
+    private @NonNull Set<String> fetchOemSystemHolders(int... roleResIds) {
+        Set<String> packageNames = new ArraySet<>();
+
+        for (int roleResId : roleResIds) {
+            String packageName = getDefaultRoleHolderPackageName(roleResId);
+            if (packageName != null) {
+                packageNames.add(packageName);
+            }
+        }
+
+        return Collections.unmodifiableSet(packageNames);
+    }
+
+
+    private @Nullable String getDefaultRoleHolderPackageName(int resId) {
+        String packageNameAndSignature = mContext.getString(resId);
+
+        if (TextUtils.isEmpty(packageNameAndSignature)) {
+            return null;
+        }
+
+        if (packageNameAndSignature.contains(":")) {
+            return packageNameAndSignature.split(":")[0];
+        }
+
+        return packageNameAndSignature;
+    }
+
     private Owners makeOwners(Injector injector, PolicyPathProvider pathProvider) {
         return new Owners(
                 injector.getUserManager(), injector.getUserManagerInternal(),
@@ -12103,10 +12151,14 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
-            if (admin.disableCallerId != disabled) {
-                admin.disableCallerId = disabled;
-                saveSettingsLocked(caller.getUserId());
+            if (disabled) {
+                admin.mManagedProfileCallerIdAccess =
+                        new PackagePolicy(PackagePolicy.PACKAGE_POLICY_ALLOWLIST);
+            } else {
+                admin.mManagedProfileCallerIdAccess =
+                        new PackagePolicy(PackagePolicy.PACKAGE_POLICY_BLOCKLIST);
             }
+            saveSettingsLocked(caller.getUserId());
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CALLER_ID_DISABLED)
@@ -12126,7 +12178,21 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
-            return admin.disableCallerId;
+            if (admin == null) {
+                return false;
+            }
+
+            if (admin.mManagedProfileCallerIdAccess == null) {
+                return admin.disableCallerId;
+            }
+
+            if (admin.mManagedProfileCallerIdAccess.getPolicyType()
+                    == PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM) {
+                Slogf.w(LOG_TAG, "Denying callerId due to PACKAGE_POLICY_SYSTEM policyType");
+            }
+
+            return admin.mManagedProfileCallerIdAccess.getPolicyType()
+                    != PackagePolicy.PACKAGE_POLICY_BLOCKLIST;
         }
     }
 
@@ -12139,11 +12205,124 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
-            return (admin != null) ? admin.disableCallerId : false;
+            if (admin == null) {
+                return false;
+            }
+
+            if (admin.mManagedProfileCallerIdAccess == null) {
+                return admin.disableCallerId;
+            }
+
+            return admin.mManagedProfileCallerIdAccess.getPolicyType()
+                    == PackagePolicy.PACKAGE_POLICY_ALLOWLIST;
         }
     }
 
     @Override
+    public void setManagedProfileCallerIdAccessPolicy(PackagePolicy policy) {
+        if (!mHasFeature) {
+            return;
+        }
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization((isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId())));
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+            admin.disableCallerId = false;
+            admin.mManagedProfileCallerIdAccess = policy;
+            saveSettingsLocked(caller.getUserId());
+        }
+    }
+
+    @Override
+    public PackagePolicy getManagedProfileCallerIdAccessPolicy() {
+        if (!mHasFeature) {
+            return null;
+        }
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization((isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId())));
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+            return (admin != null) ? admin.mManagedProfileCallerIdAccess : null;
+        }
+    }
+
+    @Override
+    public boolean hasManagedProfileCallerIdAccess(int userId, String packageName) {
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
+
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
+            if (admin != null) {
+                if (admin.mManagedProfileCallerIdAccess == null) {
+                    return !admin.disableCallerId;
+                }
+                return admin.mManagedProfileCallerIdAccess.isPackageAllowed(packageName,
+                        mContactSystemRoleHolders);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public void setManagedProfileContactsAccessPolicy(PackagePolicy policy) {
+        if (!mHasFeature) {
+            return;
+        }
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization((isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId())));
+
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+            admin.disableContactsSearch = false;
+            admin.mManagedProfileContactsAccess = policy;
+            saveSettingsLocked(caller.getUserId());
+        }
+    }
+
+    @Override
+    public PackagePolicy getManagedProfileContactsAccessPolicy() {
+        if (!mHasFeature) {
+            return null;
+        }
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization((isProfileOwner(caller)
+                && isManagedProfile(caller.getUserId())));
+
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
+            return (admin != null) ? admin.mManagedProfileContactsAccess : null;
+        }
+    }
+
+    @Override
+    public boolean hasManagedProfileContactsAccess(int userId, String packageName) {
+        Preconditions.checkArgumentNonnegative(userId, "Invalid userId");
+
+        final CallerIdentity caller = getCallerIdentity();
+        Preconditions.checkCallAuthorization(hasCrossUsersPermission(caller, userId));
+
+        synchronized (getLockObject()) {
+            ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
+            if (admin != null) {
+                if (admin.mManagedProfileContactsAccess == null) {
+                    return !admin.disableContactsSearch;
+                }
+
+                return admin.mManagedProfileContactsAccess.isPackageAllowed(packageName,
+                        mContactSystemRoleHolders);
+            }
+        }
+        return true;
+    }
+
+    @Override
     public void setCrossProfileContactsSearchDisabled(ComponentName who, boolean disabled) {
         if (!mHasFeature) {
             return;
@@ -12154,10 +12333,14 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
-            if (admin.disableContactsSearch != disabled) {
-                admin.disableContactsSearch = disabled;
-                saveSettingsLocked(caller.getUserId());
+            if (disabled) {
+                admin.mManagedProfileContactsAccess =
+                        new PackagePolicy(PackagePolicy.PACKAGE_POLICY_ALLOWLIST);
+            } else {
+                admin.mManagedProfileContactsAccess =
+                        new PackagePolicy(PackagePolicy.PACKAGE_POLICY_BLOCKLIST);
             }
+            saveSettingsLocked(caller.getUserId());
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED)
@@ -12177,7 +12360,14 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerLocked(caller.getUserId());
-            return admin.disableContactsSearch;
+            if (admin == null) {
+                return false;
+            }
+            if (admin.mManagedProfileContactsAccess == null) {
+                return admin.disableContactsSearch;
+            }
+            return admin.mManagedProfileContactsAccess.getPolicyType()
+                    != PackagePolicy.PACKAGE_POLICY_BLOCKLIST;
         }
     }
 
@@ -12190,7 +12380,19 @@
 
         synchronized (getLockObject()) {
             ActiveAdmin admin = getProfileOwnerAdminLocked(userId);
-            return (admin != null) ? admin.disableContactsSearch : false;
+            if (admin == null) {
+                return false;
+            }
+
+            if (admin.mManagedProfileContactsAccess == null) {
+                return admin.disableContactsSearch;
+            }
+            if (admin.mManagedProfileContactsAccess.getPolicyType()
+                    == PackagePolicy.PACKAGE_POLICY_ALLOWLIST_AND_SYSTEM) {
+                Slogf.w(LOG_TAG, "Denying contacts due to PACKAGE_POLICY_SYSTEM policyType");
+            }
+            return admin.mManagedProfileContactsAccess.getPolicyType()
+                    != PackagePolicy.PACKAGE_POLICY_BLOCKLIST;
         }
     }
 
@@ -19370,6 +19572,11 @@
                 }
                 admin.mtePolicy = flags;
                 saveSettingsLocked(caller.getUserId());
+
+                DevicePolicyEventLogger.createEvent(DevicePolicyEnums.SET_MTE_POLICY)
+                        .setInt(flags)
+                        .setAdmin(admin.info.getPackageName())
+                        .write();
             }
         }
     }
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bcb4ec9..5b9460a 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -132,6 +132,7 @@
 import com.android.server.display.color.ColorDisplayService;
 import com.android.server.dreams.DreamManagerService;
 import com.android.server.emergency.EmergencyAffordanceService;
+import com.android.server.grammaticalinflection.GrammaticalInflectionService;
 import com.android.server.gpu.GpuService;
 import com.android.server.graphics.fonts.FontManagerService;
 import com.android.server.hdmi.HdmiControlService;
@@ -425,6 +426,8 @@
             "com.android.server.sdksandbox.SdkSandboxManagerService$Lifecycle";
     private static final String AD_SERVICES_MANAGER_SERVICE_CLASS =
             "com.android.server.adservices.AdServicesManagerService$Lifecycle";
+    private static final String UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS =
+            "com.android.server.deviceconfig.DeviceConfigInit$Lifecycle";
 
     private static final String TETHERING_CONNECTOR_CLASS = "android.net.ITetheringConnector";
 
@@ -1049,6 +1052,17 @@
     private void startBootstrapServices(@NonNull TimingsTraceAndSlog t) {
         t.traceBegin("startBootstrapServices");
 
+        t.traceBegin("ArtModuleServiceInitializer");
+        // This needs to happen before DexUseManagerLocal init. We do it here to avoid colliding
+        // with a GC. ArtModuleServiceInitializer is a class from a separate dex file
+        // "service-art.jar", so referencing it involves the class linker. The class linker and the
+        // GC are mutually exclusive (b/263486535). Therefore, we do this here to force trigger the
+        // class linker earlier. If we did this later, especially after PackageManagerService init,
+        // the class linker would be consistently blocked by a GC because PackageManagerService
+        // allocates a lot of memory and almost certainly triggers a GC.
+        ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
+        t.traceEnd();
+
         // Start the watchdog as early as possible so we can crash the system server
         // if we deadlock during early boot
         t.traceBegin("StartWatchdog");
@@ -1235,8 +1249,6 @@
         t.traceBegin("DexUseManagerLocal");
         // DexUseManagerLocal needs to be loaded after PackageManagerLocal has been registered, but
         // before PackageManagerService starts processing binder calls to notifyDexLoad.
-        // DexUseManagerLocal may also call artd, so ensure ArtModuleServiceManager is instantiated.
-        ArtModuleServiceInitializer.setArtModuleServiceManager(new ArtModuleServiceManager());
         LocalManagerRegistry.addManager(
                 DexUseManagerLocal.class, DexUseManagerLocal.createInstance());
         t.traceEnd();
@@ -1512,6 +1524,8 @@
 
             t.traceBegin("InstallSystemProviders");
             mActivityManagerService.getContentProviderHelper().installSystemProviders();
+            // Device configuration used to be part of System providers
+            mSystemServiceManager.startService(UPDATABLE_DEVICE_CONFIG_SERVICE_CLASS);
             // Now that SettingsProvider is ready, reactivate SQLiteCompatibilityWalFlags
             SQLiteCompatibilityWalFlags.reset();
             t.traceEnd();
@@ -1765,6 +1779,14 @@
         }
         t.traceEnd();
 
+        t.traceBegin("StartGrammarInflectionService");
+        try {
+            mSystemServiceManager.startService(GrammaticalInflectionService.class);
+        } catch (Throwable e) {
+            reportWtf("starting GrammarInflectionService service", e);
+        }
+        t.traceEnd();
+
         t.traceBegin("UpdatePackagesIfNeeded");
         try {
             Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
diff --git a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
index c4d07fe..f4ecceb 100644
--- a/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
+++ b/services/permission/java/com/android/server/permission/access/collection/IndexedList.kt
@@ -99,3 +99,10 @@
     }
     return isChanged
 }
+
+inline fun <T, R> IndexedList<T>.mapNotNullIndexed(transform: (T) -> R?): IndexedList<R> =
+    IndexedList<R>().also { destination ->
+        forEachIndexed { _, element ->
+            transform(element)?.let { destination += it }
+        }
+    }
diff --git a/services/permission/java/com/android/server/permission/access/permission/Permission.kt b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
index 88989c4..35f00a7 100644
--- a/services/permission/java/com/android/server/permission/access/permission/Permission.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/Permission.kt
@@ -46,83 +46,86 @@
         @Suppress("DEPRECATION")
         get() = permissionInfo.protectionLevel
 
+    inline val protection: Int
+        get() = permissionInfo.protection
+
     inline val isInternal: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_INTERNAL
+        get() = protection == PermissionInfo.PROTECTION_INTERNAL
 
     inline val isNormal: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_NORMAL
+        get() = protection == PermissionInfo.PROTECTION_NORMAL
 
     inline val isRuntime: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS
+        get() = protection == PermissionInfo.PROTECTION_DANGEROUS
 
     inline val isSignature: Boolean
-        get() = permissionInfo.protection == PermissionInfo.PROTECTION_SIGNATURE
+        get() = protection == PermissionInfo.PROTECTION_SIGNATURE
+
+    inline val protectionFlags: Int
+        get() = permissionInfo.protectionFlags
 
     inline val isAppOp: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APPOP)
 
     inline val isAppPredictor: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR)
 
     inline val isCompanion: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_COMPANION)
 
     inline val isConfigurator: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_CONFIGURATOR)
 
     inline val isDevelopment: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_DEVELOPMENT)
 
     inline val isIncidentReportApprover: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER)
 
     inline val isInstaller: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTALLER)
 
     inline val isInstant: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_INSTANT)
 
     inline val isKnownSigner: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_KNOWN_SIGNER)
 
     inline val isOem: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_OEM)
 
     inline val isPre23: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRE23)
 
     inline val isPreInstalled: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PREINSTALLED)
 
     inline val isPrivileged: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_PRIVILEGED)
 
     inline val isRecents: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RECENTS)
 
     inline val isRetailDemo: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RETAIL_DEMO)
 
     inline val isRole: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_ROLE)
 
     inline val isRuntimeOnly: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY)
 
     inline val isSetup: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SETUP)
 
     inline val isSystemTextClassifier: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_SYSTEM_TEXT_CLASSIFIER)
 
     inline val isVendorPrivileged: Boolean
-        get() = permissionInfo.protectionFlags
-            .hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VENDOR_PRIVILEGED)
 
     inline val isVerifier: Boolean
-        get() = permissionInfo.protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
+        get() = protectionFlags.hasBits(PermissionInfo.PROTECTION_FLAG_VERIFIER)
 
     inline val isHardRestricted: Boolean
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_HARD_RESTRICTED)
@@ -133,12 +136,23 @@
     inline val isSoftRestricted: Boolean
         get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_SOFT_RESTRICTED)
 
+    inline val isHardOrSoftRestricted: Boolean
+        get() = permissionInfo.flags.hasBits(
+            PermissionInfo.FLAG_HARD_RESTRICTED or PermissionInfo.FLAG_SOFT_RESTRICTED
+        )
+
+    inline val isImmutablyRestricted: Boolean
+        get() = permissionInfo.flags.hasBits(PermissionInfo.FLAG_IMMUTABLY_RESTRICTED)
+
     inline val knownCerts: Set<String>
         get() = permissionInfo.knownCerts
 
     inline val hasGids: Boolean
         get() = gids.isNotEmpty()
 
+    inline val footprint: Int
+        get() = name.length + permissionInfo.calculateFootprint()
+
     fun getGidsForUser(userId: Int): IntArray =
         if (areGidsPerUser) {
             IntArray(gids.size) { i -> UserHandle.getUid(userId, gids[i]) }
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index 7117501..e2c2c49 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -40,6 +40,7 @@
 import android.os.UserHandle
 import android.os.UserManager
 import android.permission.IOnPermissionsChangeListener
+import android.permission.PermissionControllerManager
 import android.permission.PermissionManager
 import android.provider.Settings
 import android.util.DebugUtils
@@ -48,10 +49,12 @@
 import com.android.internal.compat.IPlatformCompat
 import com.android.internal.logging.MetricsLogger
 import com.android.internal.logging.nano.MetricsProto
+import com.android.internal.util.DumpUtils
 import com.android.internal.util.Preconditions
 import com.android.server.FgThread
 import com.android.server.LocalManagerRegistry
 import com.android.server.LocalServices
+import com.android.server.PermissionThread
 import com.android.server.ServiceThread
 import com.android.server.SystemConfig
 import com.android.server.permission.access.AccessCheckingService
@@ -80,6 +83,10 @@
 import libcore.util.EmptyArray
 import java.io.FileDescriptor
 import java.io.PrintWriter
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ExecutionException
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeoutException
 
 /**
  * Modern implementation of [PermissionManagerServiceInterface].
@@ -106,6 +113,17 @@
 
     private val mountedStorageVolumes = IndexedSet<String?>()
 
+    private lateinit var permissionControllerManager: PermissionControllerManager
+
+    /**
+     * A permission backup might contain apps that are not installed. In this case we delay the
+     * restoration until the app is installed.
+     *
+     * This array (`userId -> noDelayedBackupLeft`) is `true` for all the users where
+     * there is **no more** delayed backup left.
+     */
+    private val isDelayedPermissionBackupFinished = IntBooleanMap()
+
     fun initialize() {
         metricsLogger = MetricsLogger()
         packageManagerInternal = LocalServices.getService(PackageManagerInternal::class.java)
@@ -161,9 +179,7 @@
                 with(policy) { getPermissionGroups()[permissionGroupName] }
             } ?: return null
 
-            val isPermissionGroupVisible =
-                snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)
-            if (!isPermissionGroupVisible) {
+            if (!snapshot.isPackageVisibleToUid(permissionGroup.packageName, callingUid)) {
                 return null
             }
         }
@@ -199,9 +215,7 @@
                 with(policy) { getPermissions()[permissionName] }
             } ?: return null
 
-            val isPermissionVisible =
-                snapshot.isPackageVisibleToUid(permission.packageName, callingUid)
-            if (!isPermissionVisible) {
+            if (!snapshot.isPackageVisibleToUid(permission.packageName, callingUid)) {
                 return null
             }
 
@@ -274,12 +288,30 @@
         }
     }
 
-    override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> {
-        TODO("Not yet implemented")
-    }
+    override fun getAllPermissionsWithProtection(protection: Int): List<PermissionInfo> =
+        getPermissionsWithProtectionOrProtectionFlags { permission ->
+            permission.protection == protection
+        }
 
-    override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> {
-        TODO("Not yet implemented")
+    override fun getAllPermissionsWithProtectionFlags(protectionFlags: Int): List<PermissionInfo> =
+        getPermissionsWithProtectionOrProtectionFlags { permission ->
+            permission.protectionFlags.hasBits(protectionFlags)
+        }
+
+    private inline fun getPermissionsWithProtectionOrProtectionFlags(
+        predicate: (Permission) -> Boolean
+    ): List<PermissionInfo> {
+        service.getState {
+            with(policy) {
+                return getPermissions().mapNotNullIndexed { _, _, permission ->
+                    if (predicate(permission)) {
+                        permission.generatePermissionInfo(0)
+                    } else {
+                        null
+                    }
+                }
+            }
+        }
     }
 
     override fun getPermissionGids(permissionName: String, userId: Int): IntArray {
@@ -290,11 +322,100 @@
     }
 
     override fun addPermission(permissionInfo: PermissionInfo, async: Boolean): Boolean {
-        TODO("Not yet implemented")
+        val permissionName = permissionInfo.name
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        val callingUid = Binder.getCallingUid()
+        if (packageManagerLocal.withUnfilteredSnapshot().use { it.isUidInstantApp(callingUid) }) {
+            throw SecurityException("Instant apps cannot add permissions")
+        }
+        if (permissionInfo.labelRes == 0 && permissionInfo.nonLocalizedLabel == null) {
+            throw SecurityException("Label must be specified in permission")
+        }
+        val oldPermission: Permission?
+
+        service.mutateState {
+            val permissionTree = getAndEnforcePermissionTree(permissionName)
+            enforcePermissionTreeSize(permissionInfo, permissionTree)
+
+            oldPermission = with(policy) { getPermissions()[permissionName] }
+            if (oldPermission != null && !oldPermission.isDynamic) {
+                throw SecurityException(
+                    "Not allowed to modify non-dynamic permission $permissionName"
+                )
+            }
+
+            permissionInfo.packageName = permissionTree.permissionInfo.packageName
+            @Suppress("DEPRECATION")
+            permissionInfo.protectionLevel =
+                PermissionInfo.fixProtectionLevel(permissionInfo.protectionLevel)
+
+            val newPermission = Permission(
+                permissionInfo, true, Permission.TYPE_DYNAMIC, permissionTree.appId
+            )
+
+            with(policy) { addPermission(newPermission, !async) }
+        }
+
+        return oldPermission == null
     }
 
     override fun removePermission(permissionName: String) {
-        TODO("Not yet implemented")
+        val callingUid = Binder.getCallingUid()
+        if (packageManagerLocal.withUnfilteredSnapshot().use { it.isUidInstantApp(callingUid) }) {
+            throw SecurityException("Instant applications don't have access to this method")
+        }
+        service.mutateState {
+            getAndEnforcePermissionTree(permissionName)
+            val permission = with(policy) { getPermissions()[permissionName] } ?: return@mutateState
+
+            if (!permission.isDynamic) {
+                // TODO(b/67371907): switch to logging if it fails
+                throw SecurityException(
+                    "Not allowed to modify non-dynamic permission $permissionName"
+                )
+            }
+
+            with(policy) { removePermission(permission) }
+        }
+    }
+    private fun GetStateScope.getAndEnforcePermissionTree(permissionName: String): Permission {
+        val callingUid = Binder.getCallingUid()
+        val permissionTree = with(policy) { findPermissionTree(permissionName) }
+        if (permissionTree != null && permissionTree.appId == UserHandle.getAppId(callingUid)) {
+                return permissionTree
+        }
+
+        throw SecurityException(
+            "Calling UID $callingUid is not allowed to add to or remove from the permission tree"
+        )
+    }
+
+    private fun GetStateScope.enforcePermissionTreeSize(
+        permissionInfo: PermissionInfo,
+        permissionTree: Permission
+    ) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (permissionTree.appId != Process.SYSTEM_UID) {
+            val permissionTreeFootprint = calculatePermissionTreeFootprint(permissionTree)
+            if (permissionTreeFootprint + permissionInfo.calculateFootprint() >
+                MAX_PERMISSION_TREE_FOOTPRINT
+            ) {
+                throw SecurityException("Permission tree size cap exceeded")
+            }
+        }
+    }
+
+    private fun GetStateScope.calculatePermissionTreeFootprint(permissionTree: Permission): Int {
+        var size = 0
+        with(policy) {
+            getPermissions().forEachValueIndexed { _, permission ->
+                if (permissionTree.appId == permission.appId) {
+                    size += permission.footprint
+                }
+            }
+        }
+        return size
     }
 
     override fun checkUidPermission(uid: Int, permissionName: String): Int {
@@ -1099,30 +1220,300 @@
         with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
     }
 
+    override fun getAllowlistedRestrictedPermissions(
+        packageName: String,
+        allowlistedFlags: Int,
+        userId: Int
+    ): IndexedList<String>? {
+        requireNotNull(packageName) { "packageName cannot be null" }
+        Preconditions.checkFlagsArgument(allowlistedFlags, PERMISSION_ALLOWLIST_MASK)
+        Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
+
+        enforceCallingOrSelfCrossUserPermission(
+            userId, enforceFullPermission = false, enforceShellRestriction = false,
+            "getAllowlistedRestrictedPermissions"
+        )
+
+        if (!userManagerInternal.exists(userId)) {
+            Log.w(LOG_TAG, "AllowlistedRestrictedPermission api: Unknown user $userId")
+            return null
+        }
+
+        val callingUid = Binder.getCallingUid()
+        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+            .use { it.getPackageState(packageName) } ?: return null
+        val androidPackage = packageState.androidPackage ?: return null
+
+        val isCallerPrivileged = context.checkCallingOrSelfPermission(
+            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+        ) == PackageManager.PERMISSION_GRANTED
+
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM) &&
+            !isCallerPrivileged) {
+            throw SecurityException(
+                "Querying system allowlist requires " +
+                    Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            )
+        }
+
+        val isCallerInstallerOnRecord =
+            packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
+
+        if (allowlistedFlags.hasAnyBit(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+                PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw SecurityException(
+                    "Querying upgrade or installer allowlist requires being installer on record" +
+                        " or ${Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS}"
+                )
+            }
+        }
+
+        return getAllowlistedRestrictedPermissionsUnchecked(
+            packageState.appId, allowlistedFlags, userId
+        )
+    }
+
+    /**
+     * This method does not enforce checks on the caller, should only be called after
+     * required checks.
+     */
+    private fun getAllowlistedRestrictedPermissionsUnchecked(
+        appId: Int,
+        allowlistedFlags: Int,
+        userId: Int
+    ): IndexedList<String>? {
+        val permissionFlags = service.getState {
+            with(policy) { getUidPermissionFlags(appId, userId) }
+        } ?: return null
+
+        var queryFlags = 0
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM)) {
+            queryFlags = queryFlags or PermissionFlags.SYSTEM_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            queryFlags = queryFlags or PermissionFlags.UPGRADE_EXEMPT
+        }
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER)) {
+            queryFlags = queryFlags or PermissionFlags.INSTALLER_EXEMPT
+        }
+
+        return permissionFlags.mapNotNullIndexed { _, permissionName, flags ->
+            if (flags.hasAnyBit(queryFlags)) permissionName else null
+        }
+    }
+
     override fun addAllowlistedRestrictedPermission(
         packageName: String,
         permissionName: String,
-        flags: Int,
+        allowlistedFlags: Int,
         userId: Int
     ): Boolean {
-        TODO("Not yet implemented")
-    }
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        if (!enforceRestrictedPermission(permissionName)) {
+            return false
+        }
 
-    override fun getAllowlistedRestrictedPermissions(
-        packageName: String,
-        flags: Int,
-        userId: Int
-    ): MutableList<String> {
-        TODO("Not yet implemented")
+        val permissionNames = getAllowlistedRestrictedPermissions(
+            packageName, allowlistedFlags, userId
+        ) ?: IndexedList(1)
+
+        if (permissionName !in permissionNames) {
+            permissionNames += permissionName
+            return setAllowlistedRestrictedPermissions(
+                packageName, permissionNames, allowlistedFlags, userId, isAddingPermission = true
+            )
+        }
+        return false
     }
 
     override fun removeAllowlistedRestrictedPermission(
         packageName: String,
         permissionName: String,
-        flags: Int,
+        allowlistedFlags: Int,
         userId: Int
     ): Boolean {
-        TODO("Not yet implemented")
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        if (!enforceRestrictedPermission(permissionName)) {
+            return false
+        }
+
+        val permissions = getAllowlistedRestrictedPermissions(
+            packageName, allowlistedFlags, userId
+        ) ?: return false
+
+        if (permissions.remove(permissionName)) {
+            return setAllowlistedRestrictedPermissions(
+                packageName, permissions, allowlistedFlags, userId, isAddingPermission = false
+            )
+        }
+
+        return false
+    }
+
+    private fun enforceRestrictedPermission(permissionName: String): Boolean {
+        val permission = service.getState { with(policy) { getPermissions()[permissionName] } }
+        if (permission == null) {
+            Log.w(LOG_TAG, "permission definition for $permissionName does not exist")
+            return false
+        }
+
+        if (packageManagerLocal.withFilteredSnapshot()
+                .use { it.getPackageState(permission.packageName) } == null) {
+            return false
+        }
+
+        val isImmutablyRestrictedPermission =
+            permission.isHardOrSoftRestricted && permission.isImmutablyRestricted
+        if (isImmutablyRestrictedPermission && context.checkCallingOrSelfPermission(
+                Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+            ) != PackageManager.PERMISSION_GRANTED) {
+            throw SecurityException(
+                "Cannot modify allowlist of an immutably restricted permission: ${permission.name}"
+            )
+        }
+
+        return true
+    }
+
+    private fun setAllowlistedRestrictedPermissions(
+        packageName: String,
+        allowlistedPermissions: List<String>,
+        allowlistedFlags: Int,
+        userId: Int,
+        isAddingPermission: Boolean
+    ): Boolean {
+        Preconditions.checkArgument(allowlistedFlags.countOneBits() == 1)
+
+        val isCallerPrivileged = context.checkCallingOrSelfPermission(
+            Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+        ) == PackageManager.PERMISSION_GRANTED
+
+        val callingUid = Binder.getCallingUid()
+        val packageState = packageManagerLocal.withFilteredSnapshot(callingUid, userId)
+            .use { snapshot -> snapshot.packageStates[packageName] ?: return false }
+        val androidPackage = packageState.androidPackage ?: return false
+
+        val isCallerInstallerOnRecord =
+            packageManagerInternal.isCallerInstallerOfRecord(androidPackage, callingUid)
+
+        if (allowlistedFlags.hasBits(PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE)) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw SecurityException(
+                    "Modifying upgrade allowlist requires being installer on record or " +
+                        Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                )
+            }
+            if (isAddingPermission && !isCallerPrivileged) {
+                throw SecurityException(
+                    "Adding to upgrade allowlist requires" +
+                        Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS
+                )
+            }
+        }
+
+        setAllowlistedRestrictedPermissionsUnchecked(
+            androidPackage, packageState.appId, allowlistedPermissions, allowlistedFlags, userId
+        )
+
+        return true
+    }
+
+    /**
+     * This method does not enforce checks on the caller, should only be called after
+     * required checks.
+     */
+    private fun setAllowlistedRestrictedPermissionsUnchecked(
+        androidPackage: AndroidPackage,
+        appId: Int,
+        allowlistedPermissions: List<String>,
+        allowlistedFlags: Int,
+        userId: Int
+    ) {
+        service.mutateState {
+            with(policy) {
+                val permissionsFlags =
+                    getUidPermissionFlags(appId, userId) ?: return@mutateState
+
+                val permissions = getPermissions()
+                androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
+                    val permission = permissions[requestedPermission]
+                    if (permission == null || !permission.isHardOrSoftRestricted) {
+                        return@forEachIndexed
+                    }
+
+                    val oldFlags = permissionsFlags[requestedPermission] ?: 0
+                    val wasGranted = PermissionFlags.isPermissionGranted(oldFlags)
+
+                    var newFlags = oldFlags
+                    var mask = 0
+                    var allowlistFlagsCopy = allowlistedFlags
+                    while (allowlistFlagsCopy != 0) {
+                        val flag = 1 shl allowlistFlagsCopy.countTrailingZeroBits()
+                        allowlistFlagsCopy = allowlistFlagsCopy and flag.inv()
+                        when (flag) {
+                            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM -> {
+                                mask = mask or PermissionFlags.SYSTEM_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.SYSTEM_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.SYSTEM_EXEMPT
+                                    }
+                            }
+                            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE -> {
+                                mask = mask or PermissionFlags.UPGRADE_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.UPGRADE_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.UPGRADE_EXEMPT
+                                    }
+                            }
+                            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER -> {
+                                mask = mask or PermissionFlags.INSTALLER_EXEMPT
+                                newFlags =
+                                    if (allowlistedPermissions.contains(requestedPermission)) {
+                                        newFlags or PermissionFlags.INSTALLER_EXEMPT
+                                    } else {
+                                        newFlags andInv PermissionFlags.INSTALLER_EXEMPT
+                                    }
+                            }
+                        }
+                    }
+
+                    if (oldFlags == newFlags) {
+                        return@forEachIndexed
+                    }
+
+                    val wasAllowlisted = oldFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+                    val isAllowlisted = newFlags.hasAnyBit(PermissionFlags.MASK_EXEMPT)
+
+                    // If the permission is policy fixed as granted but it is no longer
+                    // on any of the allowlists we need to clear the policy fixed flag
+                    // as allowlisting trumps policy i.e. policy cannot grant a non
+                    // grantable permission.
+                    if (oldFlags.hasBits(PermissionFlags.POLICY_FIXED)) {
+                        if (!isAllowlisted && wasGranted) {
+                            mask = mask or PermissionFlags.POLICY_FIXED
+                            newFlags = newFlags andInv PermissionFlags.POLICY_FIXED
+                        }
+                    }
+
+                    // If we are allowlisting an app that does not support runtime permissions
+                    // we need to make sure it goes through the permission review UI at launch.
+                    if (androidPackage.targetSdkVersion < Build.VERSION_CODES.M &&
+                        !wasAllowlisted && isAllowlisted) {
+                        mask = mask or PermissionFlags.IMPLICIT
+                        newFlags = newFlags or PermissionFlags.IMPLICIT
+                    }
+
+                    updatePermissionFlags(
+                        appId, userId, requestedPermission, mask, newFlags
+                    )
+                }
+            }
+        }
     }
 
     override fun resetRuntimePermissions(androidPackage: AndroidPackage, userId: Int) {
@@ -1159,8 +1550,29 @@
         )
     }
 
+
+
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
-        TODO("Not yet implemented")
+        requireNotNull(permissionName) { "permissionName cannot be null" }
+        val packageNames = IndexedSet<String>()
+
+        val permission = service.getState {
+            with(policy) { getPermissions()[permissionName] }
+        }
+        if (permission == null || !permission.isAppOp) {
+            packageNames.toTypedArray()
+        }
+
+        packageManagerLocal.withUnfilteredSnapshot().use { snapshot ->
+            snapshot.packageStates.forEach packageStates@{ (_, packageState) ->
+                val androidPackage = packageState.androidPackage ?: return@packageStates
+                if (permissionName in androidPackage.requestedPermissions) {
+                    packageNames += androidPackage.packageName
+                }
+            }
+        }
+
+        return packageNames.toTypedArray()
     }
 
     override fun getAllAppOpPermissionPackages(): Map<String, Set<String>> {
@@ -1183,19 +1595,63 @@
     }
 
     override fun backupRuntimePermissions(userId: Int): ByteArray? {
-        TODO("Not yet implemented")
+        Preconditions.checkArgumentNonnegative(userId, "userId cannot be null")
+        val backup = CompletableFuture<ByteArray>()
+        permissionControllerManager.getRuntimePermissionBackup(
+            UserHandle.of(userId), PermissionThread.getExecutor(), backup::complete
+        )
+
+        return try {
+            backup.get(BACKUP_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        } catch (e: Exception) {
+            when (e) {
+                is TimeoutException, is InterruptedException, is ExecutionException -> {
+                    Log.e(LOG_TAG, "Cannot create permission backup for user $userId", e)
+                    null
+                }
+                else -> throw e
+            }
+        }
     }
 
     override fun restoreRuntimePermissions(backup: ByteArray, userId: Int) {
-        TODO("Not yet implemented")
+        requireNotNull(backup) { "backup" }
+        Preconditions.checkArgumentNonnegative(userId, "userId")
+
+        synchronized(isDelayedPermissionBackupFinished) {
+            isDelayedPermissionBackupFinished -= userId
+        }
+        permissionControllerManager.stageAndApplyRuntimePermissionsBackup(
+            backup, UserHandle.of(userId)
+        )
     }
 
     override fun restoreDelayedRuntimePermissions(packageName: String, userId: Int) {
-        TODO("Not yet implemented")
+        requireNotNull(packageName) { "packageName" }
+        Preconditions.checkArgumentNonnegative(userId, "userId")
+
+        synchronized(isDelayedPermissionBackupFinished) {
+            if (isDelayedPermissionBackupFinished.get(userId, false)) {
+                return
+            }
+        }
+        permissionControllerManager.applyStagedRuntimePermissionBackup(
+            packageName, UserHandle.of(userId), PermissionThread.getExecutor()
+        ) { hasMoreBackup ->
+            if (hasMoreBackup) {
+                return@applyStagedRuntimePermissionBackup
+            }
+            synchronized(isDelayedPermissionBackupFinished) {
+                isDelayedPermissionBackupFinished.put(userId, true)
+            }
+        }
     }
 
     override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>?) {
-        TODO("Not yet implemented")
+        if (!DumpUtils.checkDumpPermission(context, LOG_TAG, pw)) {
+            return
+        }
+        context.getSystemService(PermissionControllerManager::class.java)!!.dump(fd, args)
     }
 
     override fun getPermissionTEMP(
@@ -1231,7 +1687,10 @@
     }
 
     override fun onSystemReady() {
-        TODO("Not yet implemented")
+        // TODO STOPSHIP privappPermissionsViolationsfix check
+        permissionControllerManager = PermissionControllerManager(
+            context, PermissionThread.getHandler()
+        )
     }
 
     override fun onUserCreated(userId: Int) {
@@ -1250,17 +1709,17 @@
     }
 
     override fun onPackageAdded(
-        androidPackage: AndroidPackage,
+        packageState: PackageState,
         isInstantApp: Boolean,
         oldPackage: AndroidPackage?
     ) {
         synchronized(mountedStorageVolumes) {
-            if (androidPackage.volumeUuid !in mountedStorageVolumes) {
+            if (packageState.volumeUuid !in mountedStorageVolumes) {
                 // Wait for the storage volume to be mounted and batch the state mutation there.
                 return
             }
         }
-        service.onPackageAdded(androidPackage.packageName)
+        service.onPackageAdded(packageState.packageName)
     }
 
     override fun onPackageRemoved(androidPackage: AndroidPackage) {
@@ -1301,6 +1760,7 @@
     override fun onPackageUninstalled(
         packageName: String,
         appId: Int,
+        packageState: PackageState,
         androidPackage: AndroidPackage?,
         sharedUserPkgs: List<AndroidPackage>,
         userId: Int
@@ -1348,13 +1808,12 @@
         if (activityManager != null) {
             val appId = UserHandle.getAppId(uid)
             val userId = UserHandle.getUserId(uid)
-            val identity = Binder.clearCallingIdentity()
-            try {
-                activityManager.killUidForPermissionChange(appId, userId, reason)
-            } catch (e: RemoteException) {
-                /* ignore - same process */
-            } finally {
-                Binder.restoreCallingIdentity(identity)
+            Binder::class.withClearedCallingIdentity {
+                try {
+                    activityManager.killUidForPermissionChange(appId, userId, reason)
+                } catch (e: RemoteException) {
+                    /* ignore - same process */
+                }
             }
         }
     }
@@ -1677,5 +2136,15 @@
         private const val UNREQUESTABLE_MASK = PermissionFlags.RESTRICTION_REVOKED or
             PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED or
             PermissionFlags.USER_FIXED
+
+        private val BACKUP_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
+
+        /** Cap the size of permission trees that 3rd party apps can define; in characters of text  */
+        private const val MAX_PERMISSION_TREE_FOOTPRINT = 32768
+
+        private const val PERMISSION_ALLOWLIST_MASK =
+            PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE or
+            PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM or
+            PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER
     }
 }
diff --git a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
index 49759c0..4c3ffde 100644
--- a/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/UidPermissionPolicy.kt
@@ -191,6 +191,61 @@
         check(packageName !in newState.systemState.disabledSystemPackageStates) {
             "Package $packageName reported as removed before disabled system package is enabled"
         }
+
+        val changedPermissionNames = IndexedSet<String>()
+        trimPermissions(packageName, changedPermissionNames)
+        trimPermissionStates(appId)
+        changedPermissionNames.forEachIndexed { _, permissionName ->
+            evaluatePermissionStateForAllPackages(permissionName, null)
+        }
+    }
+
+    override fun MutateStateScope.onPackageUninstalled(
+        packageName: String,
+        appId: Int,
+        userId: Int
+    ) {
+        resetRuntimePermissions(packageName, appId, userId)
+    }
+
+    fun MutateStateScope.resetRuntimePermissions(
+        packageName: String,
+        appId: Int,
+        userId: Int
+    ) {
+        val androidPackage = newState.systemState.packageStates[packageName]?.androidPackage
+            ?: return
+        androidPackage.requestedPermissions.forEachIndexed { _, permissionName ->
+            val permission = newState.systemState.permissions[permissionName]
+                ?: return@forEachIndexed
+            if (permission.isRemoved) {
+                return@forEachIndexed
+            }
+            val isRequestedByOtherPackages = anyPackageInAppId(appId) { packageState ->
+                packageState.packageName != packageName &&
+                    permissionName in packageState.androidPackage!!.requestedPermissions
+            }
+            if (isRequestedByOtherPackages) {
+                return@forEachIndexed
+            }
+            val oldFlags = getPermissionFlags(appId, userId, permissionName)
+            if (oldFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)) {
+                return@forEachIndexed
+            }
+            var newFlags = oldFlags
+            newFlags = if (
+                newFlags.hasBits(PermissionFlags.ROLE) || newFlags.hasBits(PermissionFlags.PREGRANT)
+            ) {
+                newFlags or PermissionFlags.RUNTIME_GRANTED
+            } else {
+                newFlags andInv PermissionFlags.RUNTIME_GRANTED
+            }
+            newFlags = newFlags andInv USER_SETTABLE_MASK
+            if (newFlags.hasBits(PermissionFlags.LEGACY_GRANTED)) {
+                newFlags = newFlags or PermissionFlags.IMPLICIT
+            }
+            setPermissionFlags(appId, userId, permissionName, newFlags)
+        }
     }
 
     private fun MutateStateScope.adoptPermissions(
@@ -306,7 +361,7 @@
             // Different from the old implementation, which may add an (incomplete) signature
             // permission inside another package's permission tree, we now consistently ignore such
             // permissions.
-            val permissionTree = getPermissionTree(permissionName)
+            val permissionTree = findPermissionTree(permissionName)
             val newPackageName = newPermissionInfo.packageName
             if (permissionTree != null && newPackageName != permissionTree.packageName) {
                 Log.w(
@@ -427,7 +482,7 @@
         if (!permission.isDynamic) {
             return permission
         }
-        val permissionTree = getPermissionTree(permission.name) ?: return permission
+        val permissionTree = findPermissionTree(permission.name) ?: return permission
         @Suppress("DEPRECATION")
         return permission.copy(
             permissionInfo = PermissionInfo(permission.permissionInfo).apply {
@@ -436,18 +491,6 @@
         )
     }
 
-    private fun MutateStateScope.getPermissionTree(permissionName: String): Permission? =
-        newState.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
-            _, permissionTreeName, permissionTree ->
-            if (permissionName.startsWith(permissionTreeName) &&
-                permissionName.length > permissionTreeName.length &&
-                permissionName[permissionTreeName.length] == '.') {
-                permissionTree
-            } else {
-                null
-            }
-        }
-
     private fun MutateStateScope.trimPermissionStates(appId: Int) {
         val requestedPermissions = IndexedSet<String>()
         forEachPackageInAppId(appId) {
@@ -586,7 +629,9 @@
                 newFlags = newFlags or (oldFlags and PermissionFlags.RUNTIME_GRANTED)
             }
             if (permission.isRole) {
-                newFlags = newFlags or (oldFlags and PermissionFlags.ROLE)
+                newFlags = newFlags or (
+                    oldFlags and (PermissionFlags.ROLE or PermissionFlags.RUNTIME_GRANTED)
+                )
             }
             setPermissionFlags(appId, userId, permissionName, newFlags)
         } else if (permission.isRuntime) {
@@ -646,11 +691,7 @@
                             PermissionFlags.isAppOpGranted(accessBackgroundLocationFlags) &&
                                 !accessBackgroundLocationFlags.hasBits(PermissionFlags.IMPLICIT)
                     }
-                    // These are the permission flags that imply we shouldn't automatically
-                    // modify the permission grant state.
-                    val shouldRetainByMask = newFlags.hasAnyBit(
-                        PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
-                    )
+                    val shouldRetainByMask = newFlags.hasAnyBit(SYSTEM_OR_POLICY_FIXED_MASK)
                     if (shouldRetainAsNearbyDevices || shouldRetainByMask) {
                         if (wasGrantedByImplicit) {
                             newFlags = newFlags or PermissionFlags.RUNTIME_GRANTED
@@ -784,14 +825,13 @@
         if (packageState.packageName == PLATFORM_PACKAGE_NAME) {
             return true
         }
-        val androidPackage = packageState.androidPackage!!
-        if (!androidPackage.isPrivileged) {
+        if (!packageState.isPrivileged) {
             return true
         }
         if (permission.packageName !in newState.systemState.privilegedPermissionAllowlistPackages) {
             return true
         }
-        val allowlistState = getPrivilegedPermissionAllowlistState(androidPackage, permission.name)
+        val allowlistState = getPrivilegedPermissionAllowlistState(packageState, permission.name)
         if (allowlistState != null) {
             return allowlistState
         }
@@ -808,23 +848,23 @@
      * allowlist, or `null` if it's not in the allowlist.
      */
     private fun MutateStateScope.getPrivilegedPermissionAllowlistState(
-        androidPackage: AndroidPackage,
+        packageState: PackageState,
         permissionName: String
     ): Boolean? {
         val permissionAllowlist = newState.systemState.permissionAllowlist
         // TODO(b/261913353): STOPSHIP: Add AndroidPackage.apexModuleName. The below is only for
         //  passing compilation but won't actually work.
         // val apexModuleName = androidPackage.apexModuleName
-        val apexModuleName = androidPackage.packageName
-        val packageName = androidPackage.packageName
+        val apexModuleName = packageState.packageName
+        val packageName = packageState.packageName
         return when {
-            androidPackage.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
+            packageState.isVendor -> permissionAllowlist.getVendorPrivilegedAppAllowlistState(
                 packageName, permissionName
             )
-            androidPackage.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
+            packageState.isProduct -> permissionAllowlist.getProductPrivilegedAppAllowlistState(
                 packageName, permissionName
             )
-            androidPackage.isSystemExt ->
+            packageState.isSystemExt ->
                 permissionAllowlist.getSystemExtPrivilegedAppAllowlistState(
                     packageName, permissionName
                 )
@@ -898,13 +938,14 @@
             val shouldGrant = if (packageState.isUpdatedSystemApp) {
                 // For updated system applications, a privileged/oem permission
                 // is granted only if it had been defined by the original application.
-                val disabledSystemPackage = newState.systemState
-                    .disabledSystemPackageStates[packageState.packageName]?.androidPackage
+                val disabledSystemPackageState = newState.systemState
+                    .disabledSystemPackageStates[packageState.packageName]
+                val disabledSystemPackage = disabledSystemPackageState?.androidPackage
                 disabledSystemPackage != null &&
                     permission.name in disabledSystemPackage.requestedPermissions &&
-                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackage, permission)
+                    shouldGrantPrivilegedOrOemPermission(disabledSystemPackageState, permission)
             } else {
-                shouldGrantPrivilegedOrOemPermission(androidPackage, permission)
+                shouldGrantPrivilegedOrOemPermission(packageState, permission)
             }
             if (shouldGrant) {
                 return true
@@ -989,18 +1030,18 @@
     }
 
     private fun MutateStateScope.shouldGrantPrivilegedOrOemPermission(
-        androidPackage: AndroidPackage,
+        packageState: PackageState,
         permission: Permission
     ): Boolean {
         val permissionName = permission.name
-        val packageName = androidPackage.packageName
+        val packageName = packageState.packageName
         when {
             permission.isPrivileged -> {
-                if (androidPackage.isPrivileged) {
+                if (packageState.isPrivileged) {
                     // In any case, don't grant a privileged permission to privileged vendor apps,
                     // if the permission's protectionLevel does not have the extra vendorPrivileged
                     // flag.
-                    if (androidPackage.isVendor && !permission.isVendorPrivileged) {
+                    if (packageState.isVendor && !permission.isVendorPrivileged) {
                         Log.w(
                             LOG_TAG, "Permission $permissionName cannot be granted to privileged" +
                             " vendor app $packageName because it isn't a vendorPrivileged" +
@@ -1012,7 +1053,7 @@
                 }
             }
             permission.isOem -> {
-                if (androidPackage.isOem) {
+                if (packageState.isOem) {
                     val allowlistState = newState.systemState.permissionAllowlist
                         .getOemAppAllowlistState(packageName, permissionName)
                     checkNotNull(allowlistState) {
@@ -1050,6 +1091,26 @@
         with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
     }
 
+    fun GetStateScope.getPermissionTrees(): IndexedMap<String, Permission> =
+        state.systemState.permissionTrees
+
+    fun GetStateScope.findPermissionTree(permissionName: String): Permission? =
+        state.systemState.permissionTrees.firstNotNullOfOrNullIndexed {
+                _, permissionTreeName, permissionTree ->
+            if (permissionName.startsWith(permissionTreeName) &&
+                permissionName.length > permissionTreeName.length &&
+                permissionName[permissionTreeName.length] == '.') {
+                permissionTree
+            } else {
+                null
+            }
+        }
+
+    fun MutateStateScope.addPermissionTree(permission: Permission) {
+        newState.systemState.permissionTrees[permission.name] = permission
+        newState.systemState.requestWrite()
+    }
+
     /**
      * returns all permission group definitions available in the system
      */
@@ -1062,6 +1123,16 @@
     fun GetStateScope.getPermissions(): IndexedMap<String, Permission> =
         state.systemState.permissions
 
+    fun MutateStateScope.addPermission(permission: Permission, sync: Boolean = false) {
+        newState.systemState.permissions[permission.name] = permission
+        newState.systemState.requestWrite(sync)
+    }
+
+    fun MutateStateScope.removePermission(permission: Permission) {
+        newState.systemState.permissions -= permission.name
+        newState.systemState.requestWrite()
+    }
+
     fun GetStateScope.getUidPermissionFlags(appId: Int, userId: Int): IndexedMap<String, Int>? =
         state.userStates[userId]?.uidPermissionFlags?.get(appId)
 
@@ -1159,6 +1230,24 @@
         private val NOTIFICATIONS_PERMISSIONS = indexedSetOf(
             Manifest.permission.POST_NOTIFICATIONS
         )
+
+        /**
+         * Mask for all permission flags that can be set by the user
+         */
+        private const val USER_SETTABLE_MASK =
+            PermissionFlags.USER_SET or
+                PermissionFlags.USER_FIXED or
+                PermissionFlags.APP_OP_REVOKED or
+                PermissionFlags.ONE_TIME or
+                PermissionFlags.HIBERNATION or
+                PermissionFlags.USER_SELECTED
+
+        /**
+         * Mask for all permission flags that imply we shouldn't automatically modify the
+         * permission grant state.
+         */
+        private const val SYSTEM_OR_POLICY_FIXED_MASK =
+            PermissionFlags.SYSTEM_FIXED or PermissionFlags.POLICY_FIXED
     }
 
     /**
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
index ffa2729..c6b355c 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -28,6 +28,8 @@
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -35,6 +37,7 @@
 import static org.mockito.Mockito.when;
 
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.inputmethod.EditorInfo;
@@ -47,11 +50,14 @@
 import com.android.internal.inputmethod.InputMethodDebug;
 import com.android.internal.inputmethod.StartInputFlags;
 import com.android.internal.inputmethod.StartInputReason;
+import com.android.server.LocalServices;
+import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
+import org.mockito.Mock;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -84,6 +90,8 @@
             };
     private static final int DEFAULT_SOFT_INPUT_FLAG =
             StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR;
+    @Mock
+    VirtualDeviceManagerInternal mMockVdmInternal;
 
     @Parameterized.Parameters(name = "softInputState={0}, softInputAdjustment={1}")
     public static List<Object[]> softInputModeConfigs() {
@@ -256,6 +264,19 @@
                 mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
     }
 
+    @Test
+    public void startInputOrWindowGainedFocus_localeHintsOverride() throws RemoteException {
+        doReturn(mMockVdmInternal).when(
+                () -> LocalServices.getService(VirtualDeviceManagerInternal.class));
+        LocaleList overrideLocale = LocaleList.forLanguageTags("zh-CN");
+        doReturn(overrideLocale).when(mMockVdmInternal).getPreferredLocaleListForUid(anyInt());
+        mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
+
+        assertThat(startInputOrWindowGainedFocus(DEFAULT_SOFT_INPUT_FLAG,
+                true /* forwardNavigation */)).isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT);
+        assertThat(mEditorInfo.hintLocales).isEqualTo(overrideLocale);
+    }
+
     private void mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility) {
         when(mMockWindowManagerInternal.hasInputMethodClientFocus(
                         any(), anyInt(), anyInt(), anyInt()))
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index c8e2676..c6b7736 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -753,7 +753,7 @@
                 .setPVersionCode(pkg.getLongVersionCode())
                 .setPkgFlags(PackageInfoUtils.appInfoFlags(pkg, null))
                 .setPrivateFlags(PackageInfoUtils.appInfoPrivateFlags(pkg, null))
-                .setSharedUserId(pkg.getSharedUserLabel())
+                .setSharedUserId(pkg.getSharedUserLabelRes())
                 .build();
     }
 
@@ -761,9 +761,9 @@
 
     public static void assertPackagesEqual(AndroidPackage a, AndroidPackage b) {
         assertEquals(a.getBaseRevisionCode(), b.getBaseRevisionCode());
-        assertEquals(a.isBaseHardwareAccelerated(), b.isBaseHardwareAccelerated());
+        assertEquals(a.isHardwareAccelerated(), b.isHardwareAccelerated());
         assertEquals(a.getLongVersionCode(), b.getLongVersionCode());
-        assertEquals(a.getSharedUserLabel(), b.getSharedUserLabel());
+        assertEquals(a.getSharedUserLabelRes(), b.getSharedUserLabelRes());
         assertEquals(a.getInstallLocation(), b.getInstallLocation());
         assertEquals(a.isCoreApp(), b.isCoreApp());
         assertEquals(a.isRequiredForAllUsers(), b.isRequiredForAllUsers());
@@ -1036,8 +1036,8 @@
         permission.setParsedPermissionGroup(new ParsedPermissionGroupImpl());
 
         ((ParsedPackage) pkg.setBaseRevisionCode(100)
-                .setBaseHardwareAccelerated(true)
-                .setSharedUserLabel(100)
+                .setHardwareAccelerated(true)
+                .setSharedUserLabelRes(100)
                 .setInstallLocation(100)
                 .setRequiredForAllUsers(true)
                 .asSplit(
@@ -1062,7 +1062,7 @@
                 .setSdkLibVersionMajor(42)
                 .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"})
                 .setStaticSharedLibraryName("foo23")
-                .setStaticSharedLibVersion(100)
+                .setStaticSharedLibraryVersion(100)
                 .addUsesStaticLibrary("foo23", 100, new String[]{"digest"})
                 .addLibraryName("foo10")
                 .addUsesLibrary("foo11")
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index ce0b3a2..b5bd869 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -117,14 +117,14 @@
     @Before
     public void setupDefaultAbiBehavior() throws Exception {
         when(mMockPackageAbiHelper.derivePackageAbi(
-                any(AndroidPackage.class), anyBoolean(), nullable(String.class),
+                any(AndroidPackage.class), anyBoolean(), anyBoolean(), nullable(String.class),
                 any(File.class)))
                 .thenReturn(new Pair<>(
                         new PackageAbiHelper.Abis("derivedPrimary", "derivedSecondary"),
                         new PackageAbiHelper.NativeLibraryPaths(
                                 "derivedRootDir", true, "derivedNativeDir", "derivedNativeDir2")));
         when(mMockPackageAbiHelper.deriveNativeLibraryPaths(
-                any(AndroidPackage.class), anyBoolean(), any(File.class)))
+                any(AndroidPackage.class), anyBoolean(), anyBoolean(), any(File.class)))
                 .thenReturn(new PackageAbiHelper.NativeLibraryPaths(
                         "getRootDir", true, "getNativeDir", "getNativeDir2"
                 ));
@@ -273,7 +273,7 @@
     public void installStaticSharedLibrary() throws Exception {
         final ParsedPackage pkg = ((ParsedPackage) createBasicPackage("static.lib.pkg")
                 .setStaticSharedLibraryName("static.lib")
-                .setStaticSharedLibVersion(123L)
+                .setStaticSharedLibraryVersion(123L)
                 .hideAsParsed())
                 .setPackageName("static.lib.pkg.123")
                 .setVersionCodeMajor(1)
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
index d3107b0..f376e73 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
@@ -54,7 +54,7 @@
                 .hideAsFinal();
 
         // no change, not system
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -73,7 +73,7 @@
                 .setSystem(true)
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, true);
     }
 
     @Test
@@ -90,7 +90,7 @@
                 .hideAsFinal();
 
         // no change, not system
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -112,7 +112,7 @@
                 .setSystem(true)
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, true);
     }
 
     @Test
@@ -129,7 +129,7 @@
                 .hideAsFinal();
 
         // Libraries are removed because they are not available for non-system apps
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -151,7 +151,7 @@
 
         // No change is required because the package explicitly requests the HIDL libraries
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, true);
     }
 
     @Test
@@ -168,7 +168,7 @@
                 .hideAsFinal();
 
         // Libraries are removed because they are not available for apps targeting Q+
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -185,10 +185,11 @@
                 .hideAsFinal();
 
         // Libraries are removed because they are not available for apps targeting Q+
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, AndroidHidlUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, AndroidHidlUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
index 36308d2..9248da6 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -51,7 +51,7 @@
                 .addUsesOptionalLibrary("optional")
                 .hideAsParsed())
                 .hideAsFinal();
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -66,7 +66,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -81,10 +81,11 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, AndroidNetIpSecIkeUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, AndroidNetIpSecIkeUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
index 3782f5d..23a2c20 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
@@ -54,7 +54,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -73,7 +73,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -91,7 +91,7 @@
 
         // No change is required because although org.apache.http.legacy has been removed from
         // the bootclasspath the package explicitly requests it.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -109,7 +109,7 @@
 
         // No change is required because although org.apache.http.legacy has been removed from
         // the bootclasspath the package explicitly requests it.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -127,7 +127,7 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -145,10 +145,11 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, AndroidTestBaseUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, AndroidTestBaseUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
index a739607..2060caa 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
@@ -55,7 +55,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -73,10 +73,11 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, AndroidTestRunnerSplitUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, AndroidTestRunnerSplitUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
index 3977d0d..b3ad861 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
@@ -97,7 +97,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -115,7 +115,7 @@
         // note: target sdk is not what matters in this logic. It's the system SDK
         // should be removed because on 30+ (R+) it is implicit
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -131,7 +131,7 @@
 
         // note: target sdk is not what matters in this logic. It's the system SDK
         // nothing should change because the implicit from is only from a future platform release
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -151,7 +151,7 @@
 
         // note: target sdk is not what matters in this logic. It's the system SDK
         // nothing should change because the implicit from is only from a future platform release
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -166,7 +166,7 @@
                 .hideAsFinal();
 
         // should not be affected because it is still in the BCP in 30 / R
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -182,7 +182,7 @@
                 .hideAsFinal();
 
         // should be present because this was in BCP in 29 / Q
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -206,7 +206,7 @@
 
         // the library is now in the BOOTCLASSPATH (for the second time) so it doesn't need to be
         // listed
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -233,7 +233,7 @@
 
         // in this example, we are at the point where the library is not in the BOOTCLASSPATH.
         // Because the app targets Q / 29 (when this library was in the BCP) then we need to add it
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -258,7 +258,7 @@
         // in this example, we are at the point where the library is not in the BOOTCLASSPATH.
         // Because the app targets R/30 (when this library was removed from the BCP) then we don't
         //need to add it
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -284,7 +284,7 @@
 
         // in this example, we are at the point where the library is not in the BOOTCLASSPATH.
         // Because the app wants to use the library, it needs to be present
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -304,7 +304,7 @@
                 .hideAsFinal();
 
         // in this example, we are at the point where the library is still in the BCP
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -326,7 +326,7 @@
                 .hideAsFinal();
 
         // in this example, we are at the point where the library was removed from the BCP
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -343,7 +343,7 @@
                 .hideAsFinal();
 
         // in this example, we are at the point where the library was removed from the BCP
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -360,11 +360,12 @@
                 .hideAsFinal();
 
         // in this example, we are at the point where the library was removed from the BCP
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after,
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp,
                 () -> new ApexSharedLibraryUpdater(mSharedLibraries));
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
index a31c781..558c0e8 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
@@ -51,7 +51,7 @@
                 .addUsesOptionalLibrary("optional")
                 .hideAsParsed())
                 .hideAsFinal();
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -68,7 +68,7 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -85,10 +85,11 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, ComGoogleAndroidMapsUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, ComGoogleAndroidMapsUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
index f5e3f4e..7a2ac75 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
@@ -54,7 +54,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -73,7 +73,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -91,7 +91,7 @@
 
         // No change is required because although org.apache.http.legacy has been removed from
         // the bootclasspath the package explicitly requests it.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -109,7 +109,7 @@
 
         // No change is required because although org.apache.http.legacy has been removed from
         // the bootclasspath the package explicitly requests it.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -127,7 +127,7 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -145,10 +145,11 @@
 
         // No change is required because the package explicitly requests org.apache.http.legacy
         // and is targeted at the current version so does not need backwards compatibility.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, OrgApacheHttpLegacyUpdater::new);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp, OrgApacheHttpLegacyUpdater::new);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index b3648b1..c4b8e6f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -55,7 +55,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -93,7 +93,8 @@
         }
         after.addUsesLibrary(ORG_APACHE_HTTP_LEGACY);
 
-        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal(),
+                false);
     }
 
     /**
@@ -122,7 +123,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     /**
@@ -147,7 +148,8 @@
         after.addUsesLibrary(ANDROID_TEST_MOCK);
         after.addUsesLibrary(ANDROID_TEST_RUNNER);
 
-        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal(),
+                false);
     }
 
     /**
@@ -164,7 +166,8 @@
         ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT);
 
-        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal(),
+                false);
     }
 
     /**
@@ -181,7 +184,8 @@
         ParsingPackage after = PackageImpl.forTesting(PACKAGE_NAME)
                 .setTargetSdkVersion(Build.VERSION_CODES.CUR_DEVELOPMENT);
 
-        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal());
+        checkBackwardsCompatibility(before, ((ParsedPackage) after.hideAsParsed()).hideAsFinal(),
+                false);
     }
 
     /**
@@ -200,7 +204,9 @@
         assertThat(lastUpdater).isInstanceOf(ApexSharedLibraryUpdater.class);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
-        checkBackwardsCompatibility(before, after, PackageBackwardCompatibility::getInstance);
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
+        checkBackwardsCompatibility(before, after, isSystemApp,
+                PackageBackwardCompatibility::getInstance);
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
index 2450a14..33fc261 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageSharedLibraryUpdaterTest.java
@@ -31,8 +31,8 @@
     protected static final String PACKAGE_NAME = "org.package.name";
 
     static void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
-            Supplier<PackageSharedLibraryUpdater> updaterSupplier) {
-        updaterSupplier.get().updatePackage(before, false);
+            boolean isSystemApp, Supplier<PackageSharedLibraryUpdater> updaterSupplier) {
+        updaterSupplier.get().updatePackage(before, isSystemApp, false);
         check(before.hideAsFinal(), after);
     }
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
index 2825c69..8918233 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
@@ -55,7 +55,7 @@
                 .hideAsFinal();
 
         // No change required.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -72,7 +72,7 @@
                 .hideAsFinal();
 
         // No change required.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -89,7 +89,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -106,7 +106,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -121,7 +121,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -138,7 +138,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -156,16 +156,17 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
         // TODO(b/72538146) - Cannot use constructor reference here because it is also used in
         // PackageBackwardCompatibility and that seems to create a package-private lambda in
         // android.content.pm which this then tries to reuse but fails because it cannot access
         // package-private classes/members because the test is loaded by a different ClassLoader
         // than the lambda.
-        checkBackwardsCompatibility(before, after,
+        checkBackwardsCompatibility(before, after, isSystemApp,
                 () -> new RemoveUnnecessaryAndroidTestBaseLibrary());
     }
 
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
index c0da8a7..3e9ec0e 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
@@ -55,7 +55,7 @@
                 .hideAsFinal();
 
         // No change required.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -72,7 +72,7 @@
                 .hideAsFinal();
 
         // No change required.
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -89,7 +89,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -106,7 +106,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -123,7 +123,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -140,7 +140,7 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
     @Test
@@ -158,16 +158,17 @@
                 .hideAsParsed())
                 .hideAsFinal();
 
-        checkBackwardsCompatibility(before, after);
+        checkBackwardsCompatibility(before, after, false);
     }
 
-    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after) {
+    private void checkBackwardsCompatibility(ParsedPackage before, AndroidPackage after,
+            boolean isSystemApp) {
         // TODO(b/72538146) - Cannot use constructor reference here because it is also used in
         // PackageBackwardCompatibility and that seems to create a package-private lambda in
         // android.content.pm which this then tries to reuse but fails because it cannot access
         // package-private classes/members because the test is loaded by a different ClassLoader
         // than the lambda.
-        checkBackwardsCompatibility(before, after,
+        checkBackwardsCompatibility(before, after, isSystemApp,
                 () -> new RemoveUnnecessaryOrgApacheHttpLegacyLibrary());
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/Android.bp b/services/tests/PackageManagerServiceTests/unit/Android.bp
index 1c6ba33..9b3b8c35 100644
--- a/services/tests/PackageManagerServiceTests/unit/Android.bp
+++ b/services/tests/PackageManagerServiceTests/unit/Android.bp
@@ -33,11 +33,16 @@
         "junit",
         "kotlin-test",
         "kotlin-reflect",
+        "mockito-target-extended-minus-junit4",
         "services.core",
         "servicestests-utils",
         "servicestests-core-utils",
         "truth-prebuilt",
     ],
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
     platform_apis: true,
     test_suites: ["device-tests"],
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
index 2ef7a1f..81f6c82 100644
--- a/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
+++ b/services/tests/PackageManagerServiceTests/unit/AndroidManifest.xml
@@ -18,6 +18,9 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.server.pm.test">
 
+    <!--required for using Mockito-->
+    <application android:debuggable="true" />
+
     <instrumentation
         android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.server.pm.test"
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 7bf9a9e..1619856 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -30,6 +30,7 @@
 import android.util.SparseArray
 import android.util.SparseIntArray
 import com.android.internal.R
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.component.ParsedActivityImpl
@@ -126,31 +127,47 @@
         "addUsesStaticLibrary",
         "getUsesStaticLibraries",
         "getUsesStaticLibrariesVersions",
-        "getUsesStaticLibrariesCertDigests"
+        "getUsesStaticLibrariesCertDigests",
+
+        // Tested through getSetByValue via AndroidPackageHidden APIs, to be removed eventually
+        "setOdm",
+        "setOem",
+        "setPrivileged",
+        "setProduct",
+        "setSystem",
+        "setSystemExt",
+        "setVendor",
+        "isOdm",
+        "isOem",
+        "isPrivileged",
+        "isProduct",
+        "isSystem",
+        "isSystemExt",
+        "isVendor",
     )
 
     override val baseParams = listOf(
+        AndroidPackage::getApplicationClassName,
         AndroidPackage::getAppComponentFactory,
         AndroidPackage::getAutoRevokePermissions,
         AndroidPackage::getBackupAgentName,
-        AndroidPackage::getBanner,
+        AndroidPackage::getBannerRes,
         AndroidPackage::getBaseApkPath,
         AndroidPackage::getBaseRevisionCode,
         AndroidPackage::getCategory,
         AndroidPackage::getClassLoaderName,
-        AndroidPackage::getClassName,
         AndroidPackage::getCompatibleWidthLimitDp,
         AndroidPackage::getCompileSdkVersion,
         AndroidPackage::getCompileSdkVersionCodeName,
-        AndroidPackage::getDataExtractionRules,
+        AndroidPackage::getDataExtractionRulesRes,
         AndroidPackage::getDescriptionRes,
-        AndroidPackage::getFullBackupContent,
+        AndroidPackage::getFullBackupContentRes,
         AndroidPackage::getGwpAsanMode,
         AndroidPackage::getIconRes,
         AndroidPackage::getInstallLocation,
         AndroidPackage::getLabelRes,
         AndroidPackage::getLargestWidthLimitDp,
-        AndroidPackage::getLogo,
+        AndroidPackage::getLogoRes,
         AndroidPackage::getLocaleConfigRes,
         AndroidPackage::getManageSpaceActivityName,
         AndroidPackage::getMaxSdkVersion,
@@ -178,15 +195,15 @@
         PackageImpl::getSecondaryCpuAbi,
         AndroidPackage::getSecondaryNativeLibraryDir,
         AndroidPackage::getSharedUserId,
-        AndroidPackage::getSharedUserLabel,
+        AndroidPackage::getSharedUserLabelRes,
         AndroidPackage::getSdkLibraryName,
         AndroidPackage::getSdkLibVersionMajor,
         AndroidPackage::getStaticSharedLibraryName,
-        AndroidPackage::getStaticSharedLibVersion,
+        AndroidPackage::getStaticSharedLibraryVersion,
         AndroidPackage::getTargetSandboxVersion,
         AndroidPackage::getTargetSdkVersion,
         AndroidPackage::getTaskAffinity,
-        AndroidPackage::getTheme,
+        AndroidPackage::getThemeRes,
         AndroidPackage::getUiOptions,
         AndroidPackage::getUid,
         AndroidPackage::getVersionName,
@@ -198,7 +215,7 @@
         AndroidPackage::isAllowNativeHeapPointerTagging,
         AndroidPackage::isAllowTaskReparenting,
         AndroidPackage::isBackupInForeground,
-        AndroidPackage::isBaseHardwareAccelerated,
+        AndroidPackage::isHardwareAccelerated,
         AndroidPackage::isCantSaveState,
         AndroidPackage::isCoreApp,
         AndroidPackage::isCrossProfile,
@@ -221,15 +238,11 @@
         AndroidPackage::isLargeHeap,
         AndroidPackage::isMultiArch,
         AndroidPackage::isNativeLibraryRootRequiresIsa,
-        AndroidPackage::isOdm,
-        AndroidPackage::isOem,
         AndroidPackage::isOnBackInvokedCallbackEnabled,
         AndroidPackage::isOverlay,
         AndroidPackage::isOverlayIsStatic,
         AndroidPackage::isPartiallyDirectBootAware,
         AndroidPackage::isPersistent,
-        AndroidPackage::isPrivileged,
-        AndroidPackage::isProduct,
         AndroidPackage::isProfileableByShell,
         AndroidPackage::isRequestLegacyExternalStorage,
         AndroidPackage::isRequiredForAllUsers,
@@ -240,17 +253,14 @@
         AndroidPackage::isStaticSharedLibrary,
         AndroidPackage::isStub,
         AndroidPackage::isSupportsRtl,
-        AndroidPackage::isSystem,
-        AndroidPackage::isSystemExt,
         AndroidPackage::isTestOnly,
         AndroidPackage::isUse32BitAbi,
         AndroidPackage::isUseEmbeddedDex,
         AndroidPackage::isUsesCleartextTraffic,
         AndroidPackage::isUsesNonSdkApi,
-        AndroidPackage::isVendor,
         AndroidPackage::isVisibleToInstantApps,
         AndroidPackage::isVmSafeMode,
-        AndroidPackage::isLeavingSharedUid,
+        AndroidPackage::isLeavingSharedUser,
         AndroidPackage::isResetEnabledSettingsOnAppDataCleared,
         AndroidPackage::getMaxAspectRatio,
         AndroidPackage::getMinAspectRatio,
@@ -283,7 +293,7 @@
         adder(AndroidPackage::getUsesOptionalLibraries, "testUsesOptionalLibrary"),
         adder(AndroidPackage::getUsesOptionalNativeLibraries, "testUsesOptionalNativeLibrary"),
         getSetByValue(
-            AndroidPackage::areAttributionsUserVisible,
+            AndroidPackage::isAttributionsUserVisible,
             PackageImpl::setAttributionsAreUserVisible,
             true
         ),
@@ -518,6 +528,38 @@
             }
         ),
         getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")),
+        getSetByValue({ AndroidPackageUtils.isOdm(it) }, "isOdm", PackageImpl::setOdm, true),
+        getSetByValue({ AndroidPackageUtils.isOem(it) }, "isOem", PackageImpl::setOem, true),
+        getSetByValue(
+            { AndroidPackageUtils.isPrivileged(it) },
+            "isPrivileged",
+            PackageImpl::setPrivileged,
+            true
+        ),
+        getSetByValue(
+            { AndroidPackageUtils.isProduct(it) },
+            "isProduct",
+            PackageImpl::setProduct,
+            true
+        ),
+        getSetByValue(
+            { AndroidPackageUtils.isVendor(it) },
+            "isVendor",
+            PackageImpl::setVendor,
+            true
+        ),
+        getSetByValue(
+            { AndroidPackageUtils.isSystem(it) },
+            "isSystem",
+            PackageImpl::setSystem,
+            true
+        ),
+        getSetByValue(
+            { AndroidPackageUtils.isSystemExt(it) },
+            "isSystemExt",
+            PackageImpl::setSystemExt,
+            true
+        ),
     )
 
     override fun initialObject() = PackageImpl.forParsing(
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
index 37bb935..bbeaa2f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParcelableComponentTest.kt
@@ -243,6 +243,29 @@
     )
 
     /**
+     * Variant of [getSetByValue] that allows specifying a non-member [getFunction]. Mostly used
+     * for AndroidPackageHidden for APIs which are hidden from the interface.
+     */
+    @Suppress("UNCHECKED_CAST")
+    protected fun <ObjectType, ReturnType, SetType : Any?, CompareType : Any?> getSetByValue(
+        getFunction: (ObjectType) -> ReturnType,
+        getFunctionName: String,
+        setFunction: KFunction2<ObjectType, SetType, Any?>,
+        value: CompareType,
+        transformGet: (ReturnType) -> CompareType = { it as CompareType },
+        transformSet: (CompareType) -> SetType = { it as SetType },
+        compare: (CompareType, CompareType) -> Boolean? = Objects::equals
+    ) = Param(
+        getFunctionName,
+        { transformGet(getFunction(it as ObjectType)) },
+        setFunction.name,
+        { setFunction.call(it.first() as ObjectType, transformSet(it[1] as CompareType)) },
+        { value },
+        { first, second -> compare(first as CompareType, second as CompareType) == true },
+        verifyFunctionName = false
+    )
+
+    /**
      * Variant of [getSetByValue] that allows specifying a [setFunction] with 2 inputs.
      */
     @Suppress("UNCHECKED_CAST")
@@ -407,7 +430,7 @@
                 - excludedMethods)
             .distinct()
 
-        val allTestedFunctions = params.flatMap {
+        val allTestedFunctions = params.filter(Param::verifyFunctionName).flatMap {
             listOfNotNull(it.getFunctionName, it.setFunctionName)
         }
         expect.that(allTestedFunctions).containsExactlyElementsIn(expectedFunctions)
@@ -427,6 +450,7 @@
         val setFunctionName: String?,
         val setFunction: (Array<Any?>) -> Unit,
         val value: () -> Any?,
-        val compare: (Any?, Any?) -> Boolean = Objects::equals
+        val compare: (Any?, Any?) -> Boolean = Objects::equals,
+        val verifyFunctionName: Boolean = true
     )
 }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
new file mode 100644
index 0000000..52fc91d
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/uninstall/UninstallCompleteCallbackTest.kt
@@ -0,0 +1,90 @@
+/*
+ * 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.pm.test.uninstall
+
+import android.app.PackageDeleteObserver
+import android.content.Intent
+import android.content.pm.IPackageDeleteObserver2
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.UninstallCompleteCallback
+import android.os.Parcel
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.doReturn
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations.initMocks
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+class UninstallCompleteCallbackTest {
+
+    val PACKAGE_NAME: String = "com.example.package"
+    val ERROR_MSG: String = "no error"
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    lateinit var mockAdapter: PackageDeleteObserver
+
+    val mockBinder: IPackageDeleteObserver2.Stub = object : IPackageDeleteObserver2.Stub() {
+        override fun onUserActionRequired(intent: Intent) {
+            mockAdapter.onUserActionRequired(intent)
+        }
+        override fun onPackageDeleted(basePackageName: String, returnCode: Int, msg: String) {
+            mockAdapter.onPackageDeleted(basePackageName, returnCode, msg)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+    }
+
+    @Test
+    fun testCallDelegation () {
+        doReturn(mockBinder).`when`(mockAdapter).binder
+
+        val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+        callback.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+
+        verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+            PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+    }
+
+    @Test
+    fun testClassIsParcelable() {
+        doReturn(mockBinder).`when`(mockAdapter).binder
+
+        val callback = UninstallCompleteCallback(mockAdapter.binder.asBinder())
+
+        val parcel = Parcel.obtain()
+        callback.writeToParcel(parcel, callback.describeContents())
+        parcel.setDataPosition(0)
+
+        val callbackFromParcel = UninstallCompleteCallback.CREATOR.createFromParcel(parcel)
+
+        callbackFromParcel.onUninstallComplete(PACKAGE_NAME, PackageManager.DELETE_SUCCEEDED,
+                ERROR_MSG)
+
+        verify(mockAdapter, times(1)).onPackageDeleted(PACKAGE_NAME,
+            PackageManager.DELETE_SUCCEEDED, ERROR_MSG)
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index e7c384b..83441bf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -40,9 +40,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
-import android.os.Bundle;
 import android.os.RecoverySystem;
-import android.os.RemoteCallback;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -69,6 +67,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.concurrent.Executor;
 
 /**
  * Test RescueParty.
@@ -111,7 +110,7 @@
     private PackageManager mPackageManager;
 
     @Captor
-    private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
+    private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor;
     @Captor
     private ArgumentCaptor<List<String>> mPackageListCaptor;
 
@@ -125,7 +124,6 @@
                         .spyStatic(SystemProperties.class)
                         .spyStatic(Settings.Global.class)
                         .spyStatic(Settings.Secure.class)
-                        .spyStatic(Settings.Config.class)
                         .spyStatic(SettingsToPropertiesMapper.class)
                         .spyStatic(RecoverySystem.class)
                         .spyStatic(RescueParty.class)
@@ -222,7 +220,8 @@
     @Test
     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
 
@@ -233,9 +232,9 @@
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
 
         final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
 
@@ -309,25 +308,27 @@
     @Test
     public void testNonPersistentAppCrashDetectionWithScopedResets() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
+
         // Fake DeviceConfig value changes
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
+        monitorCallback.onNamespaceUpdate(NAMESPACE1);
         verify(mMockPackageWatchdog).startObservingHealth(observer,
                 Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
+        monitorCallback.onNamespaceUpdate(NAMESPACE2);
         verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
                 mPackageListCaptor.capture(),
                 eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
+        monitorCallback.onNamespaceUpdate(NAMESPACE3);
         verify(mMockPackageWatchdog).startObservingHealth(observer,
                 Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
         assertTrue(mPackageListCaptor.getValue().containsAll(
@@ -364,20 +365,21 @@
     @Test
     public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
         // Fake DeviceConfig value changes
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
+        monitorCallback.onNamespaceUpdate(NAMESPACE1);
+        monitorCallback.onNamespaceUpdate(NAMESPACE2);
+        monitorCallback.onNamespaceUpdate(NAMESPACE3);
         // Perform and verify scoped resets
         final String[] expectedPackage1ResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
         final String[] expectedPackage2ResetNamespaces = new String[]{NAMESPACE2, NAMESPACE3};
@@ -549,20 +551,21 @@
     @Test
     public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
         // Fake DeviceConfig value changes
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
+        monitorCallback.onNamespaceUpdate(NAMESPACE1);
+        monitorCallback.onNamespaceUpdate(NAMESPACE2);
+        monitorCallback.onNamespaceUpdate(NAMESPACE3);
 
         doReturn("").when(() -> DeviceConfig.getString(
                 eq(RescueParty.NAMESPACE_CONFIGURATION),
@@ -578,7 +581,8 @@
     @Test
     public void testResetDeviceConfigForPackagesOnlyPresetMap() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
@@ -598,22 +602,23 @@
     @Test
     public void testResetDeviceConfigForPackagesBothMaps() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
         // Fake DeviceConfig value changes
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
+        monitorCallback.onNamespaceUpdate(NAMESPACE1);
+        monitorCallback.onNamespaceUpdate(NAMESPACE2);
+        monitorCallback.onNamespaceUpdate(NAMESPACE3);
+        monitorCallback.onNamespaceUpdate(NAMESPACE4);
 
         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
@@ -633,20 +638,21 @@
     @Test
     public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
         RescueParty.onSettingsProviderPublished(mMockContext);
-        verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
+        verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
+                any(Executor.class),
                 mMonitorCallbackCaptor.capture()));
 
         // Record DeviceConfig accesses
         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
-        RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
-        monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
+        DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
+        monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
         // Fake DeviceConfig value changes
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
-        monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
+        monitorCallback.onNamespaceUpdate(NAMESPACE1);
+        monitorCallback.onNamespaceUpdate(NAMESPACE2);
+        monitorCallback.onNamespaceUpdate(NAMESPACE3);
+        monitorCallback.onNamespaceUpdate(NAMESPACE4);
 
         String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
                 + NAMESPACE1 + "." + CALLING_PACKAGE2;
@@ -696,20 +702,4 @@
         RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
                 packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
     }
-
-    private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
-        Bundle result = new Bundle();
-        result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, Settings.EXTRA_ACCESS_CALLBACK);
-        result.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage);
-        result.putString(Settings.EXTRA_NAMESPACE, namespace);
-        return result;
-    }
-
-    private Bundle getConfigNamespaceUpdateBundle(String updatedNamespace) {
-        Bundle result = new Bundle();
-        result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE,
-                Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK);
-        result.putString(Settings.EXTRA_NAMESPACE, updatedNamespace);
-        return result;
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
index b3dc3ed..e2c338a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActiveServicesTest.java
@@ -32,6 +32,7 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.compat.CompatChanges;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ServiceInfo;
 import android.os.SystemClock;
@@ -40,6 +41,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -194,6 +196,47 @@
                 rd2 + btwn + extra, rd2 + (btwn + extra) * 2});
     }
 
+    @Test
+    public void testGetProcessNameForService() throws Exception {
+        // Regular service
+        final ServiceInfo regularService = new ServiceInfo();
+        regularService.processName = "com.foo";
+        String processName = ActiveServices.getProcessNameForService(regularService, null, null,
+                null, false, false);
+        assertEquals("com.foo", processName);
+
+        // Isolated service
+        final ServiceInfo isolatedService = new ServiceInfo();
+        isolatedService.processName = "com.foo";
+        isolatedService.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+        final ComponentName component = new ComponentName("com.foo", "barService");
+        processName = ActiveServices.getProcessNameForService(isolatedService, component,
+                null, null, false, false);
+        assertEquals("com.foo:barService", processName);
+
+        // Isolated service in shared isolated process
+        final ServiceInfo isolatedServiceShared1 = new ServiceInfo();
+        isolatedServiceShared1.flags = ServiceInfo.FLAG_ISOLATED_PROCESS;
+        final String instanceName = "pool";
+        final String callingPackage = "com.foo";
+        final String sharedIsolatedProcessName1 = ActiveServices.getProcessNameForService(
+                isolatedServiceShared1, null, callingPackage, instanceName, false, true);
+        assertEquals("com.foo:ishared:pool", sharedIsolatedProcessName1);
+
+        // Bind another one in the same isolated process
+        final ServiceInfo isolatedServiceShared2 = new ServiceInfo(isolatedServiceShared1);
+        final String sharedIsolatedProcessName2 = ActiveServices.getProcessNameForService(
+                isolatedServiceShared2, null, callingPackage, instanceName, false, true);
+        assertEquals(sharedIsolatedProcessName1, sharedIsolatedProcessName2);
+
+        // Simulate another app trying to do the bind
+        final ServiceInfo isolatedServiceShared3 = new ServiceInfo(isolatedServiceShared1);
+        final String otherCallingPackage = "com.bar";
+        final String sharedIsolatedProcessName3 = ActiveServices.getProcessNameForService(
+                isolatedServiceShared3, null, otherCallingPackage, instanceName, false, true);
+        Assert.assertNotEquals(sharedIsolatedProcessName2, sharedIsolatedProcessName3);
+    }
+
     private void prepareTestRescheduleServiceRestarts() {
         mService = mock(ActivityManagerService.class);
         mService.mConstants = mock(ActivityManagerConstants.class);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
index 09df96f..e01a9a9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -69,70 +69,70 @@
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_notSupported() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_notSupported() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(false);
 
-        int [] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int [] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).isNull();
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDisplaysAtAll() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_noDisplaysAtAll() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(true);
         mockGetDisplays();
 
-        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int[] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).isNull();
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_defaultDisplayOnly() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_defaultDisplayOnly() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(true);
         mockGetDisplays(mDefaultDisplay);
 
-        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int[] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).isNull();
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDefaultDisplay() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_noDefaultDisplay() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(true);
         mockGetDisplays(validDisplay(42));
 
-        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int[] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).isNull();
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_mixed() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(true);
         mockGetDisplays(mDefaultDisplay, validDisplay(42), invalidDisplay(108));
 
-        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int[] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).isNotNull();
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).asList().containsExactly(42);
     }
 
     // Extra test to make sure the array is properly copied...
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed_invalidFirst() {
+    public void testGetDisplayIdsForStartingBackgroundUsers_mixed_invalidFirst() {
         mockUmIsUsersOnSecondaryDisplaysEnabled(true);
         mockGetDisplays(invalidDisplay(108), mDefaultDisplay, validDisplay(42));
 
-        int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int[] displayIds = mInjector.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingBackgroundUsers()")
                 .that(displayIds).asList().containsExactly(42);
     }
 
@@ -160,6 +160,6 @@
 
     private void mockUmIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
         Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
-        doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
+        doReturn(enabled).when(() -> UserManager.isVisibleBackgroundUsersEnabled());
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index c0688d1..52027e7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -57,6 +57,7 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.testutils.MockitoUtilsKt;
 
 import org.junit.After;
 import org.junit.Before;
@@ -137,11 +138,12 @@
         // Mock LocalServices.getService(PackageManagerInternal.class).getPackageStateInternal
         // and getPackage dependency needed by AppOpsService
         PackageManagerInternal mockPackageManagerInternal = mock(PackageManagerInternal.class);
-        PackageStateInternal mockMyPSInternal = mock(PackageStateInternal.class);
         AndroidPackage mockMyPkg = mock(AndroidPackage.class);
-        when(mockMyPkg.isPrivileged()).thenReturn(false);
-        when(mockMyPkg.getUid()).thenReturn(mMyUid);
         when(mockMyPkg.getAttributions()).thenReturn(Collections.emptyList());
+        PackageStateInternal mockMyPSInternal = mock(PackageStateInternal.class);
+        when(mockMyPSInternal.isPrivileged()).thenReturn(false);
+        when(mockMyPSInternal.getAppId()).thenReturn(mMyUid);
+        when(mockMyPSInternal.getAndroidPackage()).thenReturn(mockMyPkg);
 
         when(mockPackageManagerInternal.getPackageStateInternal(sMyPackageName))
                 .thenReturn(mockMyPSInternal);
diff --git a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
similarity index 80%
rename from services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
index cd2f205..3480af6 100644
--- a/services/tests/servicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 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.
@@ -18,6 +18,9 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+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.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -25,11 +28,11 @@
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupAnnotations.BackupDestination;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 import android.content.Context;
@@ -37,6 +40,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.platform.test.annotations.Presubmit;
+import android.util.FeatureFlagUtils;
 
 import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -47,15 +51,21 @@
 import com.android.server.backup.transport.BackupTransportClient;
 import com.android.server.backup.transport.TransportConnection;
 import com.android.server.backup.utils.BackupEligibilityRules;
+import com.android.server.backup.utils.BackupManagerMonitorUtils;
 
 import com.google.common.collect.ImmutableSet;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.function.IntConsumer;
 
 @Presubmit
@@ -63,6 +73,7 @@
 public class UserBackupManagerServiceTest {
     private static final String TEST_PACKAGE = "package1";
     private static final String[] TEST_PACKAGES = new String[] { TEST_PACKAGE };
+    private static final String TEST_TRANSPORT = "transport";
     private static final int WORKER_THREAD_TIMEOUT_MILLISECONDS = 1;
 
     @Mock Context mContext;
@@ -70,21 +81,38 @@
     @Mock IBackupObserver mBackupObserver;
     @Mock PackageManager mPackageManager;
     @Mock TransportConnection mTransportConnection;
+    @Mock TransportManager mTransportManager;
     @Mock BackupTransportClient mBackupTransport;
     @Mock BackupEligibilityRules mBackupEligibilityRules;
     @Mock LifecycleOperationStorage mOperationStorage;
 
+    private MockitoSession mSession;
     private TestBackupService mService;
 
     @Before
     public void setUp() throws Exception {
+        mSession = mockitoSession()
+                .initMocks(this)
+                .mockStatic(BackupManagerMonitorUtils.class)
+                .mockStatic(FeatureFlagUtils.class)
+                // TODO(b/263239775): Remove unnecessary stubbing.
+                .strictness(Strictness.LENIENT)
+                .startMocking();
         MockitoAnnotations.initMocks(this);
 
-        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage);
+        mService = new TestBackupService(mContext, mPackageManager, mOperationStorage,
+                mTransportManager);
         mService.setEnabled(true);
         mService.setSetupComplete(true);
     }
 
+    @After
+    public void tearDown() {
+        if (mSession != null) {
+            mSession.finishMocking();
+        }
+    }
+
     @Test
     public void initializeBackupEnableState_doesntWriteStateToDisk() {
         mService.initializeBackupEnableState();
@@ -201,6 +229,26 @@
                 .cancelOperation(anyInt(), anyBoolean(), any(IntConsumer.class));
     }
 
+    @Test
+    public void testReportDelayedRestoreResult_sendsLogsToMonitor() throws Exception {
+        PackageInfo packageInfo = getPackageInfo(TEST_PACKAGE);
+        when(mPackageManager.getPackageInfoAsUser(anyString(),
+                any(PackageManager.PackageInfoFlags.class), anyInt())).thenReturn(packageInfo);
+        when(mTransportManager.getCurrentTransportName()).thenReturn(TEST_TRANSPORT);
+        when(mTransportManager.getTransportClientOrThrow(eq(TEST_TRANSPORT), anyString()))
+                .thenReturn(mTransportConnection);
+        when(mTransportConnection.connectOrThrow(any())).thenReturn(mBackupTransport);
+        when(mBackupTransport.getBackupManagerMonitor()).thenReturn(mBackupManagerMonitor);
+
+
+        List<DataTypeResult> results = Arrays.asList(new DataTypeResult(/* dataType */ "type_1"),
+                new DataTypeResult(/* dataType */ "type_2"));
+        mService.reportDelayedRestoreResult(TEST_PACKAGE, results);
+
+        verify(() -> BackupManagerMonitorUtils.sendAgentLoggingResults(
+                eq(mBackupManagerMonitor), eq(packageInfo), eq(results)));
+    }
+
     private static PackageInfo getPackageInfo(String packageName) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.applicationInfo = new ApplicationInfo();
@@ -215,8 +263,8 @@
         private volatile Thread mWorkerThread = null;
 
         TestBackupService(Context context, PackageManager packageManager,
-                LifecycleOperationStorage operationStorage) {
-            super(context, packageManager, operationStorage);
+                LifecycleOperationStorage operationStorage, TransportManager transportManager) {
+            super(context, packageManager, operationStorage, transportManager);
         }
 
         @Override
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 0dfad43..79fbc87 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -839,7 +839,7 @@
     private static JobStatus createJob(int uid, int jobId, @Nullable String sourcePackageName) {
         return JobStatus.createFromJobInfo(
                 new JobInfo.Builder(jobId, new ComponentName("foo", "bar")).build(), uid,
-                sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest");
+                sourcePackageName, UserHandle.getUserId(uid), "JobConcurrencyManagerTest", null);
     }
 
     private static final class TypeConfig {
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 6374c66..a8b8f91 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -197,7 +197,7 @@
     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
             int callingUid) {
         return JobStatus.createFromJobInfo(
-                jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
+                jobInfoBuilder.build(), callingUid, "com.android.test", 0, "JSSTest", testTag);
     }
 
     private void grantRunLongJobsPermission(boolean grant) {
@@ -254,6 +254,12 @@
 
         ConnectivityController connectivityController = mService.getConnectivityController();
         spyOn(connectivityController);
+        mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS = 15 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_DATA_TRANSFER_LIMIT_MS = 60 * MINUTE_IN_MILLIS;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
+        mService.mConstants.RUNTIME_MIN_USER_INITIATED_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
+        mService.mConstants.RUNTIME_USER_INITIATED_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS;
 
         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(ejMax));
@@ -268,7 +274,7 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
         grantRunLongJobsPermission(false); // Without permission
-        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
         grantRunLongJobsPermission(true); // With permission
         doReturn(ConnectivityController.UNKNOWN_TIME)
@@ -288,12 +294,16 @@
         assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
                 mService.getMinJobExecutionGuaranteeMs(jobDT));
         // UserInitiated
+        grantRunLongJobsPermission(false);
+        // Permission isn't granted, so it should just be treated as a regular data transfer job.
+        assertEquals(mService.mConstants.RUNTIME_MIN_DATA_TRANSFER_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUIDT));
+        // Permission isn't granted, so it should just be treated as a regular job.
+        assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
+                mService.getMinJobExecutionGuaranteeMs(jobUI));
+        grantRunLongJobsPermission(true); // With permission
         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,
@@ -1111,7 +1121,7 @@
                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     expected,
-                    mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+                    mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
         }
     }
 
@@ -1132,7 +1142,7 @@
         for (int i = 0; i < 500; ++i) {
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     JobScheduler.RESULT_SUCCESS,
-                    mService.scheduleAsPackage(job, null, 10123, null, 0, ""));
+                    mService.scheduleAsPackage(job, null, 10123, null, 0, "JSSTest", ""));
         }
     }
 
@@ -1153,7 +1163,8 @@
         for (int i = 0; i < 500; ++i) {
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     JobScheduler.RESULT_SUCCESS,
-                    mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, ""));
+                    mService.scheduleAsPackage(job, null, 10123, "proxied.package", 0, "JSSTest",
+                            ""));
         }
     }
 
@@ -1177,7 +1188,7 @@
             assertEquals("Got unexpected result for schedule #" + (i + 1),
                     expected,
                     mService.scheduleAsPackage(job, null, 10123, job.getService().getPackageName(),
-                            0, ""));
+                            0, "JSSTest", ""));
         }
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 7c435be..f334a6a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -194,7 +194,7 @@
     private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
-                jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+                jobInfo, callingUid, packageName, SOURCE_USER_ID, "BCTest", testTag);
         js.serviceProcessName = "testProcess";
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
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 42e22f3..32e5c83 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
@@ -1316,7 +1316,7 @@
 
     private static JobStatus createJobStatus(JobInfo.Builder job, int uid,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis) {
-        return new JobStatus(job.build(), uid, null, -1, 0, null,
+        return new JobStatus(job.build(), uid, null, -1, 0, null, null,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 398acb8..1e65b38 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -195,7 +195,7 @@
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
         JobInfo jobInfo = job.build();
         JobStatus js = JobStatus.createFromJobInfo(
-                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag);
         js.enqueueTime = FROZEN_TIME;
         return js;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 7f522b0..d2ee9ff 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -920,12 +920,12 @@
             long latestRunTimeElapsedMillis) {
         final JobInfo job = new JobInfo.Builder(101, new ComponentName("foo", "bar"))
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY).build();
-        return new JobStatus(job, 0, null, -1, 0, null, earliestRunTimeElapsedMillis,
+        return new JobStatus(job, 0, null, -1, 0, null, null, earliestRunTimeElapsedMillis,
                 latestRunTimeElapsedMillis, 0, 0, null, 0, 0);
     }
 
     private static JobStatus createJobStatus(JobInfo job) {
-        JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest");
+        JobStatus jobStatus = JobStatus.createFromJobInfo(job, 0, null, -1, "JobStatusTest", null);
         jobStatus.serviceProcessName = "testProcess";
         return jobStatus;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index b949b3b..fb59ea2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -184,7 +184,7 @@
     private static JobStatus createJobStatus(String testTag, String packageName, int callingUid,
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
-                jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+                jobInfo, callingUid, packageName, SOURCE_USER_ID, "PCTest", testTag);
         js.serviceProcessName = "testProcess";
         js.setStandbyBucket(FREQUENT_INDEX);
         // Make sure Doze and background-not-restricted don't affect tests.
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9aef674..6f713e0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -383,7 +383,7 @@
     private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
             JobInfo jobInfo) {
         JobStatus js = JobStatus.createFromJobInfo(
-                jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
+                jobInfo, callingUid, packageName, SOURCE_USER_ID, "QCTest", testTag);
         js.serviceProcessName = "testProcess";
         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
         js.setStandbyBucket(FREQUENT_INDEX);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
index 51d641b..b111757 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
@@ -137,7 +137,7 @@
                 .setMinimumLatency(Math.abs(jobId) + 1)
                 .build();
         return JobStatus.createFromJobInfo(
-                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, "SCTest", testTag);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
index d64c1b3..27efcfa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/TimeControllerTest.java
@@ -137,7 +137,7 @@
     private JobStatus createJobStatus(String testTag, JobInfo.Builder job) {
         JobInfo jobInfo = job.build();
         return JobStatus.createFromJobInfo(
-                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "TCTest", testTag);
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index 9067285..02fdfad 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -328,6 +328,6 @@
 
     private JobStatus createJobStatus(String testTag, JobInfo jobInfo) {
         return JobStatus.createFromJobInfo(
-                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, "TSRTest", testTag);
     }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index aabec22..2f909aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -49,6 +49,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -380,7 +381,7 @@
                 findFactory(results, "test.apex.rebootless").apexInfo);
         assertThat(factoryPkg.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
         assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
-        assertThat(factoryPkg.isSystem()).isTrue();
+        assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue();
     }
 
     @Test
@@ -410,7 +411,7 @@
                 findFactory(results, "test.apex.rebootless").apexInfo);
         assertThat(factoryPkg.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
         assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
-        assertThat(factoryPkg.isSystem()).isTrue();
+        assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue();
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 6dc45c3..9935a2f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -327,16 +327,16 @@
         whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
         whenever(mocks.settings.keySetManagerService).thenReturn(mocks.keySetManagerService)
         whenever(mocks.settings.snapshot()).thenReturn(mocks.settings)
-        whenever(mocks.packageAbiHelper.derivePackageAbi(
-                any(AndroidPackage::class.java), anyBoolean(), nullable(), any(File::class.java))) {
+        whenever(mocks.packageAbiHelper.derivePackageAbi(any(AndroidPackage::class.java),
+            anyBoolean(), anyBoolean(), nullable(), any(File::class.java))) {
             android.util.Pair(PackageAbiHelper.Abis("", ""),
                     PackageAbiHelper.NativeLibraryPaths("", false, "", ""))
         }
         whenever(mocks.userManagerInternal.getUsers(true, false, false)).thenReturn(DEFAULT_USERS)
         whenever(mocks.userManagerService.userIds).thenReturn(intArrayOf(0))
         whenever(mocks.userManagerService.exists(0)).thenReturn(true)
-        whenever(mocks.packageAbiHelper.deriveNativeLibraryPaths(
-                any(AndroidPackage::class.java), anyBoolean(), any(File::class.java))) {
+        whenever(mocks.packageAbiHelper.deriveNativeLibraryPaths(any(AndroidPackage::class.java),
+                anyBoolean(), anyBoolean(), any(File::class.java))) {
             PackageAbiHelper.NativeLibraryPaths("", false, "", "")
         }
         whenever(mocks.injector.bootstrap(any(PackageManagerService::class.java))) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index 28c78b2..cffd027 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -391,7 +391,7 @@
             libraries?.forEach { pkg.addLibraryName(it) }
             staticLibrary?.let {
                 pkg.setStaticSharedLibraryName(it)
-                pkg.setStaticSharedLibVersion(staticLibraryVersion)
+                pkg.setStaticSharedLibraryVersion(staticLibraryVersion)
                 pkg.setStaticSharedLibrary(true)
             }
             usesLibraries?.forEach { pkg.addUsesLibrary(it) }
@@ -435,7 +435,7 @@
             libraries?.forEach { addLibraryName(it) }
             staticLibrary?.let {
                 setStaticSharedLibraryName(it)
-                setStaticSharedLibVersion(staticLibraryVersion)
+                setStaticSharedLibraryVersion(staticLibraryVersion)
                 setStaticSharedLibrary(true)
             }
             usesLibraries?.forEach { addUsesLibrary(it) }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
index 58cff94..579621c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -15,19 +15,6 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_NULL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
-import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
-import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
-import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
-import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
-
-import org.junit.Test;
-
 /**
  * Tests for {@link UserVisibilityMediator} tests for devices that support concurrent Multiple
  * Users on Multiple Displays (A.K.A {@code MUMD}).
@@ -35,208 +22,10 @@
  * <p>Run as
  * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorMUMDTest}
  */
-public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
+public final class UserVisibilityMediatorMUMDTest
+        extends UserVisibilityMediatorVisibleBackgroundUserTestCase {
 
     public UserVisibilityMediatorMUMDTest() throws Exception {
         super(/* usersOnSecondaryDisplaysEnabled= */ true);
     }
-
-    @Test
-    public void testStartFgUser_onDefaultDisplay() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(USER_ID));
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(USER_ID);
-        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-        expectVisibleUsers(USER_ID);
-
-        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
-        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
-        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testSwitchFgUser_onDefaultDisplay() throws Exception {
-        int previousCurrentUserId = OTHER_USER_ID;
-        int currentUserId = USER_ID;
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(previousCurrentUserId),
-                onInvisible(previousCurrentUserId),
-                onVisible(currentUserId));
-        startForegroundUser(previousCurrentUserId);
-
-        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
-                DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(currentUserId);
-        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
-        expectVisibleUsers(currentUserId);
-
-        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
-        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
-
-        expectUserIsNotVisibleAtAll(previousCurrentUserId);
-        expectNoDisplayAssignedToUser(previousCurrentUserId);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(
-                onInvisible(INITIAL_CURRENT_USER_ID),
-                onVisible(PARENT_USER_ID),
-                onVisible(PROFILE_USER_ID));
-        startForegroundUser(PARENT_USER_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(PROFILE_USER_ID);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
-
-        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
-        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartFgUser_onInvalidDisplay() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForNoEvents();
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
-
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartBgUser_onInvalidDisplay() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForNoEvents();
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                INVALID_DISPLAY);
-
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        expectUserIsNotVisibleAtAll(USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartBgUser_onSecondaryDisplay_displayAvailable() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsVisible(USER_ID);
-        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
-        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
-
-        expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
-            throws Exception {
-        startDefaultProfile();
-
-        // Make sure they were visible before
-        expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
-
-        expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testStartBgUser_onSecondaryDisplay_displayAlreadyAssigned() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
-        startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        expectUserIsNotVisibleAtAll(USER_ID);
-        expectNoDisplayAssignedToUser(USER_ID);
-        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartBgUser_onSecondaryDisplay_userAlreadyAssigned() throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
-        startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
-                SECONDARY_DISPLAY_ID);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
-
-        expectUserIsVisible(USER_ID);
-        expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
-        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
-        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
-
-        expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
-
-        listener.verify();
-    }
-
-    @Test
-    public void testStartBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
-            throws Exception {
-        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
-        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
-
-        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
-                BG_VISIBLE, DEFAULT_DISPLAY);
-        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
-
-        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
-        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
-
-        listener.verify();
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
index 3d64c29..88709e1 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -61,7 +61,7 @@
         expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
         expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
 
-        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+        expectNoDisplayAssignedToUser(USER_NULL);
 
         listener.verify();
     }
@@ -99,7 +99,8 @@
     }
 
     @Test
-    public void testStartBgProfile_onDefaultDisplay_whenParentIsCurrentUser() throws Exception {
+    public void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+            throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(
                 onInvisible(INITIAL_CURRENT_USER_ID),
                 onVisible(PARENT_USER_ID),
@@ -123,7 +124,7 @@
     }
 
     @Test
-    public void testStartBgUser_onSecondaryDisplay() throws Exception {
+    public void testStartVisibleBgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
@@ -133,7 +134,7 @@
         expectUserIsNotVisibleAtAll(USER_ID);
         expectNoDisplayAssignedToUser(USER_ID);
 
-        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+        expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
index 74fd9ff..e4664d2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -109,12 +109,12 @@
     private Handler mHandler;
     protected AsyncUserVisibilityListener.Factory mListenerFactory;
 
-    private final boolean mUsersOnSecondaryDisplaysEnabled;
+    private final boolean mBackgroundUsersOnDisplaysEnabled;
 
     protected UserVisibilityMediator mMediator;
 
-    protected UserVisibilityMediatorTestCase(boolean usersOnSecondaryDisplaysEnabled) {
-        mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+    protected UserVisibilityMediatorTestCase(boolean backgroundUsersOnDisplaysEnabled) {
+        mBackgroundUsersOnDisplaysEnabled = backgroundUsersOnDisplaysEnabled;
     }
 
     @Before
@@ -123,7 +123,7 @@
         Thread thread = mHandler.getLooper().getThread();
         Log.i(TAG, "setFixtures(): using thread " + thread + " (from handler " + mHandler + ")");
         mListenerFactory = new AsyncUserVisibilityListener.Factory(mExpect, thread);
-        mMediator = new UserVisibilityMediator(mUsersOnSecondaryDisplaysEnabled, mHandler);
+        mMediator = new UserVisibilityMediator(mBackgroundUsersOnDisplaysEnabled, mHandler);
         mDumpableDumperRule.addDumpable(mMediator);
     }
 
@@ -149,7 +149,7 @@
 
         expectUserIsNotVisibleAtAll(USER_ID);
         expectNoDisplayAssignedToUser(USER_ID);
-        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
         listener.verify();
     }
@@ -163,13 +163,17 @@
 
         expectUserIsNotVisibleAtAll(USER_ID);
         expectNoDisplayAssignedToUser(USER_ID);
-        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
         listener.verify();
     }
 
     @Test
-    public final void testStartBgUser_onDefaultDisplay_visible() throws Exception {
+    public final void testStartVisibleBgUser_onDefaultDisplay() throws Exception {
+        visibleBgUserCannotBeStartedOnDefaultDisplayTest();
+    }
+
+    protected final void visibleBgUserCannotBeStartedOnDefaultDisplayTest() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
@@ -183,7 +187,7 @@
     }
 
     @Test
-    public final void testStartBgUser_onSecondaryDisplay_invisible() throws Exception {
+    public final void testStartBgUser_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG,
@@ -217,7 +221,7 @@
     }
 
     @Test
-    public final void testStopVisibleProfile() throws Exception {
+    public final void testStopVisibleBgProfile() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(
                 onInvisible(INITIAL_CURRENT_USER_ID),
                 onVisible(PARENT_USER_ID),
@@ -235,7 +239,8 @@
     }
 
     @Test
-    public final void testVisibleProfileBecomesInvisibleWhenParentIsSwitchedOut() throws Exception {
+    public final void testVisibleBgProfileBecomesInvisibleWhenParentIsSwitchedOut()
+            throws Exception {
         AsyncUserVisibilityListener listener = addListenerForEvents(
                 onInvisible(INITIAL_CURRENT_USER_ID),
                 onVisible(PARENT_USER_ID),
@@ -255,7 +260,7 @@
     }
 
     @Test
-    public final void testStartBgProfile_onDefaultDisplay_whenParentIsNotStarted()
+    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsNotStarted()
             throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
@@ -270,7 +275,7 @@
     }
 
     @Test
-    public final void testStartBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
+    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsStartedOnBg()
             throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
         startBackgroundUser(PARENT_USER_ID);
@@ -282,14 +287,14 @@
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
 
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
         listener.verify();
     }
 
     // Not supported - profiles can only be started on default display
     @Test
-    public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
+    public final void testStartVisibleBgProfile_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
@@ -298,13 +303,13 @@
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+        expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
 
     @Test
-    public final void testStartBgProfile_onSecondaryDisplay_invisible() throws Exception {
+    public final void testStartBgProfile_onSecondaryDisplay() throws Exception {
         AsyncUserVisibilityListener listener = addListenerForNoEvents();
 
         int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID, BG,
@@ -313,7 +318,7 @@
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+        expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
@@ -329,7 +334,7 @@
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
 
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectNoUserAssignedToDisplay(DEFAULT_DISPLAY);
+        expectInitialCurrentUserAssignedToDisplay(DEFAULT_DISPLAY);
 
         listener.verify();
     }
@@ -344,7 +349,7 @@
 
         expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
         expectNoDisplayAssignedToUser(PROFILE_USER_ID);
-        expectNoUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
+        expectInitialCurrentUserAssignedToDisplay(SECONDARY_DISPLAY_ID);
 
         listener.verify();
     }
@@ -477,7 +482,7 @@
     }
 
     protected void expectUserIsVisible(@UserIdInt int userId) {
-        expectWithMessage("mediator.isUserVisible(%s)", userId)
+        expectWithMessage("isUserVisible(%s)", userId)
                 .that(mMediator.isUserVisible(userId))
                 .isTrue();
     }
@@ -490,13 +495,13 @@
     }
 
     protected void expectUserIsVisibleOnDisplay(@UserIdInt int userId, int displayId) {
-        expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+        expectWithMessage("isUserVisible(%s, %s)", userId, displayId)
                 .that(mMediator.isUserVisible(userId, displayId))
                 .isTrue();
     }
 
     protected void expectUserIsNotVisibleOnDisplay(@UserIdInt int userId, int displayId) {
-        expectWithMessage("mediator.isUserVisible(%s, %s)", userId, displayId)
+        expectWithMessage("isUserVisible(%s, %s)", userId, displayId)
                 .that(mMediator.isUserVisible(userId, displayId))
                 .isFalse();
     }
@@ -504,13 +509,13 @@
     protected void expectUserIsNotVisibleOnDisplay(String when, @UserIdInt int userId,
             int displayId) {
         String suffix = TextUtils.isEmpty(when) ? "" : " on " + when;
-        expectWithMessage("mediator.isUserVisible(%s, %s)%s", userId, displayId, suffix)
+        expectWithMessage("isUserVisible(%s, %s)%s", userId, displayId, suffix)
                 .that(mMediator.isUserVisible(userId, displayId))
                 .isFalse();
     }
 
     protected void expectUserIsNotVisibleAtAll(@UserIdInt int userId) {
-        expectWithMessage("mediator.isUserVisible(%s)", userId)
+        expectWithMessage("isUserVisible(%s)", userId)
                 .that(mMediator.isUserVisible(userId))
                 .isFalse();
         expectUserIsNotVisibleOnDisplay(userId, DEFAULT_DISPLAY);
@@ -534,7 +539,7 @@
                 .that(mMediator.getUserAssignedToDisplay(displayId)).isEqualTo(userId);
     }
 
-    protected void expectNoUserAssignedToDisplay(int displayId) {
+    protected void expectInitialCurrentUserAssignedToDisplay(int displayId) {
         expectWithMessage("getUserAssignedToDisplay(%s)", displayId)
                 .that(mMediator.getUserAssignedToDisplay(displayId))
                 .isEqualTo(INITIAL_CURRENT_USER_ID);
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
new file mode 100644
index 0000000..66d7eb6
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorVisibleBackgroundUserTestCase.java
@@ -0,0 +1,280 @@
+/*
+ * 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.pm;
+
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_FAILURE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE;
+import static com.android.server.pm.UserManagerInternal.USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE;
+import static com.android.server.pm.UserVisibilityChangedEvent.onInvisible;
+import static com.android.server.pm.UserVisibilityChangedEvent.onVisible;
+import static com.android.server.pm.UserVisibilityMediator.INITIAL_CURRENT_USER_ID;
+
+import android.annotation.UserIdInt;
+
+import org.junit.Test;
+
+/**
+ * Base class for {@link UserVisibilityMediator} test classe on devices that support starting
+ * background users on visible displays (as defined by
+ * {@link android.os.UserManagerInternal#isVisibleBackgroundUsersSupported}).
+ */
+abstract class UserVisibilityMediatorVisibleBackgroundUserTestCase
+        extends UserVisibilityMediatorTestCase {
+
+    UserVisibilityMediatorVisibleBackgroundUserTestCase(boolean backgroundUsersOnDisplaysEnabled)
+            throws Exception {
+        super(backgroundUsersOnDisplaysEnabled);
+    }
+
+    @Test
+    public final void testStartFgUser_onDefaultDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_NULL, INVALID_DISPLAY);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testSwitchFgUser_onDefaultDisplay() throws Exception {
+        int previousCurrentUserId = OTHER_USER_ID;
+        int currentUserId = USER_ID;
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(previousCurrentUserId),
+                onInvisible(previousCurrentUserId),
+                onVisible(currentUserId));
+        startForegroundUser(previousCurrentUserId);
+
+        int result = mMediator.assignUserToDisplayOnStart(currentUserId, currentUserId, FG,
+                DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(currentUserId);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectVisibleUsers(currentUserId);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+
+        expectUserIsNotVisibleAtAll(previousCurrentUserId);
+        expectNoDisplayAssignedToUser(previousCurrentUserId);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartVisibleBgProfile_onDefaultDisplay_whenParentIsCurrentUser()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(
+                onInvisible(INITIAL_CURRENT_USER_ID),
+                onVisible(PARENT_USER_ID),
+                onVisible(PROFILE_USER_ID));
+        startForegroundUser(PARENT_USER_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(PROFILE_USER_ID);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(PARENT_USER_ID, PROFILE_USER_ID);
+
+        expectDisplayAssignedToUser(PROFILE_USER_ID, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, PARENT_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartFgUser_onInvalidDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, FG, INVALID_DISPLAY);
+
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartVisibleBgUser_onInvalidDisplay() throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForNoEvents();
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                INVALID_DISPLAY);
+
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartVisibleBgUser_onSecondaryDisplay_displayAvailable()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testVisibilityOfCurrentUserAndProfilesOnDisplayAssignedToAnotherUser()
+            throws Exception {
+        startDefaultProfile();
+
+        // Make sure they were visible before
+        expectUserIsNotVisibleOnDisplay("before", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("before", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_VISIBLE);
+
+        expectUserIsNotVisibleOnDisplay("after", PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay("after", PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public final void testStartVisibleBgUser_onSecondaryDisplay_displayAlreadyAssigned()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(OTHER_USER_ID));
+        startUserInSecondaryDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsNotVisibleAtAll(USER_ID);
+        expectNoDisplayAssignedToUser(USER_ID);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, OTHER_USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void testStartVisibleBgUser_onSecondaryDisplay_userAlreadyAssigned()
+            throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(USER_ID));
+        startUserInSecondaryDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(USER_ID, USER_ID, BG_VISIBLE,
+                SECONDARY_DISPLAY_ID);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_FAILURE);
+
+        expectUserIsVisible(USER_ID);
+        expectUserIsVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(USER_ID, INVALID_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY);
+        expectVisibleUsers(INITIAL_CURRENT_USER_ID, USER_ID);
+
+        expectDisplayAssignedToUser(USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, USER_ID);
+
+        listener.verify();
+    }
+
+    @Test
+    public final void
+            testStartVisibleBgProfile_onDefaultDisplay_whenParentVisibleOnSecondaryDisplay()
+                    throws Exception {
+        AsyncUserVisibilityListener listener = addListenerForEvents(onVisible(PARENT_USER_ID));
+        startUserInSecondaryDisplay(PARENT_USER_ID, OTHER_SECONDARY_DISPLAY_ID);
+
+        int result = mMediator.assignUserToDisplayOnStart(PROFILE_USER_ID, PARENT_USER_ID,
+                BG_VISIBLE, DEFAULT_DISPLAY);
+        assertStartUserResult(result, USER_ASSIGNMENT_RESULT_SUCCESS_INVISIBLE);
+
+        expectUserIsNotVisibleAtAll(PROFILE_USER_ID);
+        expectNoDisplayAssignedToUser(PROFILE_USER_ID);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, PARENT_USER_ID);
+
+        listener.verify();
+    }
+
+    private void currentUserVisibilityWhenNoDisplayIsAssignedTest(@UserIdInt int currentUserId) {
+        // Conditions below are asserted on other tests, but they're explicitly checked in the 2
+        // tests below as well
+        expectUserIsVisible(currentUserId);
+        expectUserIsVisibleOnDisplay(currentUserId, DEFAULT_DISPLAY);
+        expectUserIsNotVisibleOnDisplay(currentUserId, SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(currentUserId, OTHER_SECONDARY_DISPLAY_ID);
+        expectUserIsNotVisibleOnDisplay(currentUserId, INVALID_DISPLAY);
+
+        expectDisplayAssignedToUser(currentUserId, DEFAULT_DISPLAY);
+        expectUserAssignedToDisplay(DEFAULT_DISPLAY, currentUserId);
+        expectUserAssignedToDisplay(SECONDARY_DISPLAY_ID, currentUserId);
+        expectUserAssignedToDisplay(OTHER_SECONDARY_DISPLAY_ID, currentUserId);
+        expectUserAssignedToDisplay(INVALID_DISPLAY, currentUserId);
+    }
+
+    @Test
+    public final void testCurrentUserVisibilityWhenNoDisplayIsAssigned_onBoot() throws Exception {
+        currentUserVisibilityWhenNoDisplayIsAssignedTest(INITIAL_CURRENT_USER_ID);
+    }
+
+    @Test
+    public final void testCurrentUserVisibilityWhenNoDisplayIsAssigned_afterSwitch()
+            throws Exception {
+        startForegroundUser(USER_ID);
+
+        currentUserVisibilityWhenNoDisplayIsAssignedTest(USER_ID);
+        expectUserIsNotVisibleAtAll(INITIAL_CURRENT_USER_ID);
+        expectDisplayAssignedToUser(INITIAL_CURRENT_USER_ID, INVALID_DISPLAY);
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
index 34b17c7..c85ed26 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ThermalManagerServiceMockingTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.hardware.thermal.CoolingType;
@@ -84,6 +85,42 @@
     }
 
     @Test
+    public void setCallback_illegalState_aidl() throws Exception {
+        Mockito.doThrow(new IllegalStateException()).when(
+                mAidlHalMock).registerThermalChangedCallback(Mockito.any());
+        verifyWrapperStatusOnCallbackError();
+    }
+
+    @Test
+    public void setCallback_illegalArgument_aidl() throws Exception {
+        Mockito.doThrow(new IllegalStateException()).when(
+                mAidlHalMock).registerThermalChangedCallback(Mockito.any());
+        verifyWrapperStatusOnCallbackError();
+    }
+
+
+    void verifyWrapperStatusOnCallbackError() throws RemoteException {
+        android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
+        halT1.type = TemperatureType.MODEM;
+        halT1.name = "test1";
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(Mockito.anyInt())).thenReturn(
+                new android.hardware.thermal.Temperature[]{
+                        halT1
+                });
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        Temperature expectedT1 = new Temperature(halT1.value, halT1.type, halT1.name,
+                halT1.throttlingStatus);
+        List<Temperature> expectedRet = List.of(expectedT1);
+        // test that even if the callback fails to register without hal connection error, the
+        // wrapper should still work
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
     public void getCurrentTemperatures_withFilter_aidl() throws RemoteException {
         android.hardware.thermal.Temperature halT1 = new android.hardware.thermal.Temperature();
         halT1.type = TemperatureType.MODEM;
@@ -135,7 +172,42 @@
         List<Temperature> expectedRet = List.of(
                 new Temperature(halTInvalid.value, halTInvalid.type, halTInvalid.name,
                         ThrottlingSeverity.NONE));
-        assertEquals(expectedRet, ret);
+        assertTrue("Got temperature list as " + ret + " with different values compared to "
+                + expectedRet, expectedRet.containsAll(ret));
+    }
+
+    @Test
+    public void getCurrentTemperatures_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatures()).thenThrow(new IllegalArgumentException());
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(TemperatureType.MODEM)).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getCurrentTemperatures_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatures()).thenThrow(new IllegalStateException());
+        List<Temperature> ret = mAidlWrapper.getCurrentTemperatures(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatures();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperaturesWithType(TemperatureType.MODEM)).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getCurrentTemperatures(true, TemperatureType.MODEM);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperaturesWithType(
+                TemperatureType.MODEM);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
     }
 
     @Test
@@ -187,6 +259,40 @@
     }
 
     @Test
+    public void getCurrentCoolingDevices_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenThrow(new IllegalArgumentException());
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getCurrentCoolingDevices(true, CoolingType.SPEAKER);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.SPEAKER);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getCurrentCoolingDevices_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getCoolingDevices()).thenThrow(new IllegalStateException());
+        List<CoolingDevice> ret = mAidlWrapper.getCurrentCoolingDevices(false, 0);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevices();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getCoolingDevicesWithType(Mockito.anyInt())).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getCurrentCoolingDevices(true, CoolingType.SPEAKER);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getCoolingDevicesWithType(
+                CoolingType.SPEAKER);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
     public void getTemperatureThresholds_withFilter_aidl() throws RemoteException {
         TemperatureThreshold halT1 = new TemperatureThreshold();
         halT1.name = "test1";
@@ -215,4 +321,44 @@
         assertArrayEquals(halT1.hotThrottlingThresholds, threshold.hotThrottlingThresholds, 0.1f);
         assertArrayEquals(halT1.coldThrottlingThresholds, threshold.coldThrottlingThresholds, 0.1f);
     }
+
+    @Test
+    public void getTemperatureThresholds_illegalArgument_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenThrow(
+                new IllegalArgumentException());
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenThrow(
+                new IllegalArgumentException());
+        ret = mAidlWrapper.getTemperatureThresholds(false,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
+
+    @Test
+    public void getTemperatureThresholds_illegalState_aidl() throws RemoteException {
+        Mockito.when(mAidlHalMock.getTemperatureThresholdsWithType(Mockito.anyInt())).thenThrow(
+                new IllegalStateException());
+        List<TemperatureThreshold> ret = mAidlWrapper.getTemperatureThresholds(true,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholdsWithType(
+                Temperature.TYPE_SOC);
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+
+        Mockito.when(mAidlHalMock.getTemperatureThresholds()).thenThrow(
+                new IllegalStateException());
+        ret = mAidlWrapper.getTemperatureThresholds(false,
+                Temperature.TYPE_SOC);
+        Mockito.verify(mAidlHalMock, Mockito.times(1)).getTemperatureThresholds();
+        assertNotNull(ret);
+        assertEquals(0, ret.size());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index 57f5777..e168596 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -207,7 +207,7 @@
         addA11yWindowInfo(mA11yWindowInfos, PIP_WINDOWID, true, Display.DEFAULT_DISPLAY);
         addA11yWindowInfo(mA11yWindowInfosOnSecondDisplay, WINDOWID_ONSECONDDISPLAY, false,
                 SECONDARY_DISPLAY_ID);
-        when(mMockA11yWindowManager.getDisplayListLocked()).thenReturn(mDisplayList);
+        when(mMockA11yWindowManager.getDisplayListLocked(anyInt())).thenReturn(mDisplayList);
         when(mMockA11yWindowManager.getWindowListLocked(Display.DEFAULT_DISPLAY))
                 .thenReturn(mA11yWindowInfos);
         when(mMockA11yWindowManager.findA11yWindowInfoByIdLocked(WINDOWID))
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index acbcad5..e66a1d4 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.accessibility;
 
+import static com.android.server.accessibility.AbstractAccessibilityServiceConnection.DISPLAY_TYPE_DEFAULT;
 import static com.android.server.accessibility.AccessibilityWindowManagerTest.DisplayIdMatcher.displayId;
 import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowChangesMatcher.a11yWindowChanges;
 import static com.android.server.accessibility.AccessibilityWindowManagerTest.WindowIdMatcher.a11yWindowId;
@@ -823,7 +824,8 @@
         // Starts tracking window of second display.
         startTrackingPerDisplay(SECONDARY_DISPLAY_ID);
 
-        final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked();
+        final ArrayList<Integer> displayList = mA11yWindowManager.getDisplayListLocked(
+                DISPLAY_TYPE_DEFAULT);
         assertTrue(displayList.equals(mExpectedDisplayList));
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 0d6f326..e9dc082 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -834,35 +834,35 @@
     }
 
     @Test
-    public void testGetSecondaryDisplayIdsForStartingBackgroundUsers() {
+    public void testGetDisplayIdsForStartingBackgroundUsers() {
         mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
 
-        int [] displayIds = mAms.getSecondaryDisplayIdsForStartingBackgroundUsers();
+        int [] displayIds = mAms.getDisplayIdsForStartingVisibleBackgroundUsers();
 
-        assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+        assertWithMessage("mAms.getDisplayIdsForStartingVisibleBackgroundUsers()")
                 .that(displayIds).asList().containsExactly(4, 8, 15, 16, 23, 42);
     }
 
     @Test
-    public void testStartUserInBackgroundOnSecondaryDisplay_invalidDisplay() {
+    public void testStartUserInBackgroundVisibleOnDisplay_invalidDisplay() {
         mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
 
         assertThrows(IllegalArgumentException.class,
-                () -> mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 666));
+                () -> mAms.startUserInBackgroundVisibleOnDisplay(USER_ID, 666));
 
         assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
                 .that(mInjector.usersStartedOnSecondaryDisplays).isEmpty();
     }
 
     @Test
-    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_failed() {
+    public void testStartUserInBackgroundVisibleOnDisplay_validDisplay_failed() {
         mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
         mInjector.returnValueForstartUserOnSecondaryDisplay = false;
 
-        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        boolean started = mAms.startUserInBackgroundVisibleOnDisplay(USER_ID, 42);
         Log.v(TAG, "Started: " + started);
 
-        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+        assertWithMessage("mAms.startUserInBackgroundOnDisplay(%s, 42)", USER_ID)
                 .that(started).isFalse();
         assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
                 .that(mInjector.usersStartedOnSecondaryDisplays)
@@ -870,16 +870,16 @@
     }
 
     @Test
-    public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_success() {
+    public void testStartUserInBackgroundVisibleOnDisplay_validDisplay_success() {
         mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
         mInjector.returnValueForstartUserOnSecondaryDisplay = true;
 
-        boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+        boolean started = mAms.startUserInBackgroundVisibleOnDisplay(USER_ID, 42);
         Log.v(TAG, "Started: " + started);
 
-        assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+        assertWithMessage("mAms.startUserInBackgroundOnDisplay(%s, 42)", USER_ID)
                 .that(started).isTrue();
-        assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+        assertWithMessage("UserController.startUserOnDisplay() calls")
                 .that(mInjector.usersStartedOnSecondaryDisplays)
                 .containsExactly(new Pair<>(USER_ID, 42));
     }
@@ -1004,12 +1004,12 @@
         }
 
         @Override
-        public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+        public int[] getDisplayIdsForStartingVisibleBackgroundUsers() {
             return secondaryDisplayIdsForStartingBackgroundUsers;
         }
 
         @Override
-        public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+        public boolean startUserInBackgroundVisibleOnDisplay(int userId, int displayId) {
             usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
             return returnValueForstartUserOnSecondaryDisplay;
         }
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 0dfe664..b146c27 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -243,11 +243,10 @@
     }
 
     @Test
-    public void testStartUserOnSecondaryDisplay() {
-        boolean started = mUserController.startUserOnSecondaryDisplay(TEST_USER_ID, 42);
+    public void testStartUserVisibleOnDisplay() {
+        boolean started = mUserController.startUserVisibleOnDisplay(TEST_USER_ID, 42);
 
-        assertWithMessage("startUserOnSecondaryDisplay(%s, %s)", TEST_USER_ID, 42).that(started)
-                .isTrue();
+        assertWithMessage("startUserOnDisplay(%s, %s)", TEST_USER_ID, 42).that(started).isTrue();
         verifyUserAssignedToDisplay(TEST_USER_ID, 42);
 
         verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
index cadc890..87ade96 100644
--- a/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/utils/BackupManagerMonitorUtilsTest.java
@@ -172,11 +172,30 @@
                 .when(agent)
                 .getLoggerResults(any());
 
-        IBackupManagerMonitor result =
+        IBackupManagerMonitor monitor =
                 BackupManagerMonitorUtils.monitorAgentLoggingResults(
                         mMonitorMock, packageInfo, agent);
 
-        assertThat(result).isEqualTo(mMonitorMock);
+        assertCorrectBundleSentToMonitor(monitor);
+    }
+
+    @Test
+    public void sendAgentLoggingResults_fillsBundleCorrectly() throws Exception {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = "test.package";
+        List<BackupRestoreEventLogger.DataTypeResult> loggingResults = new ArrayList<>();
+        loggingResults.add(new BackupRestoreEventLogger.DataTypeResult("testLoggingResult"));
+
+        IBackupManagerMonitor monitor = BackupManagerMonitorUtils.sendAgentLoggingResults(
+                mMonitorMock, packageInfo, loggingResults);
+
+        assertCorrectBundleSentToMonitor(monitor);
+    }
+
+    private void assertCorrectBundleSentToMonitor(IBackupManagerMonitor monitor) throws Exception {
+
+
+        assertThat(monitor).isEqualTo(mMonitorMock);
         ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
         verify(mMonitorMock).onEvent(bundleCaptor.capture());
         Bundle eventBundle = bundleCaptor.getValue();
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 0a4ae6f..316c980 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
@@ -35,7 +35,6 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.argThat;
-import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -83,6 +82,7 @@
 import android.os.IBinder;
 import android.os.IPowerManager;
 import android.os.IThermalService;
+import android.os.LocaleList;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
@@ -96,8 +96,9 @@
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.app.BlockedAppStreamingActivity;
 import com.android.server.LocalServices;
 import com.android.server.input.InputManagerInternal;
@@ -106,6 +107,7 @@
 import com.google.android.collect.Sets;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -132,7 +134,7 @@
     private static final String GOOGLE_DIALER_PACKAGE_NAME = "com.google.android.dialer";
     private static final String GOOGLE_MAPS_PACKAGE_NAME = "com.google.android.apps.maps";
     private static final String DEVICE_NAME = "device name";
-    private static final int DISPLAY_ID = 2;
+    private static final int DISPLAY_ID_1 = 2;
     private static final int DISPLAY_ID_2 = 3;
     private static final int DEVICE_OWNER_UID_1 = 50;
     private static final int DEVICE_OWNER_UID_2 = 51;
@@ -144,26 +146,27 @@
     private static final int VENDOR_ID = 5;
     private static final String UNIQUE_ID = "uniqueid";
     private static final String PHYS = "phys";
-    private static final int DEVICE_ID = 53;
+    private static final int INPUT_DEVICE_ID = 53;
     private static final int HEIGHT = 1800;
     private static final int WIDTH = 900;
     private static final int SENSOR_HANDLE = 64;
     private static final Binder BINDER = new Binder("binder");
     private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000;
-    private static final int VIRTUAL_DEVICE_ID = 42;
+    private static final int VIRTUAL_DEVICE_ID_1 = 42;
+    private static final int VIRTUAL_DEVICE_ID_2 = 43;
     private static final VirtualDpadConfig DPAD_CONFIG =
             new VirtualDpadConfig.Builder()
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
-                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setAssociatedDisplayId(DISPLAY_ID_1)
                     .build();
     private static final VirtualKeyboardConfig KEYBOARD_CONFIG =
             new VirtualKeyboardConfig.Builder()
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
-                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setAssociatedDisplayId(DISPLAY_ID_1)
                     .setLanguageTag(VirtualKeyboardConfig.DEFAULT_LANGUAGE_TAG)
                     .setLayoutType(VirtualKeyboardConfig.DEFAULT_LAYOUT_TYPE)
                     .build();
@@ -172,14 +175,14 @@
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
-                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setAssociatedDisplayId(DISPLAY_ID_1)
                     .build();
     private static final VirtualTouchscreenConfig TOUCHSCREEN_CONFIG =
             new VirtualTouchscreenConfig.Builder()
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
-                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setAssociatedDisplayId(DISPLAY_ID_1)
                     .setWidthInPixels(WIDTH)
                     .setHeightInPixels(HEIGHT)
                     .build();
@@ -189,10 +192,15 @@
                     .setVendorId(VENDOR_ID)
                     .setProductId(PRODUCT_ID)
                     .setInputDeviceName(DEVICE_NAME)
-                    .setAssociatedDisplayId(DISPLAY_ID)
+                    .setAssociatedDisplayId(DISPLAY_ID_1)
                     .build();
     private static final String TEST_SITE = "http://test";
 
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            Manifest.permission.CREATE_VIRTUAL_DEVICE);
+
     private Context mContext;
     private InputManagerMockHelper mInputManagerMockHelper;
     private VirtualDeviceImpl mDeviceImpl;
@@ -250,9 +258,9 @@
     private Intent createRestrictedActivityBlockedIntent(List displayCategories,
             String targetDisplayCategory) {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(displayCategories), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -302,10 +310,9 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        mContext = Mockito.spy(new ContextWrapper(InstrumentationRegistry.getTargetContext()));
+        mContext = Mockito.spy(new ContextWrapper(
+                InstrumentationRegistry.getInstrumentation().getTargetContext()));
         doReturn(mContext).when(mContext).createContextAsUser(eq(Process.myUserHandle()), anyInt());
-        doNothing().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn(
                 mDevicePolicyManagerMock);
 
@@ -320,7 +327,7 @@
         mInputController = new InputController(new Object(), mNativeWrapperMock,
                 new Handler(TestableLooper.get(this).getLooper()),
                 mContext.getSystemService(WindowManager.class), threadVerifier);
-        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID);
+        mSensorController = new SensorController(new Object(), VIRTUAL_DEVICE_ID_1);
 
         mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
                 MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0);
@@ -328,7 +335,7 @@
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
         mVdm = mVdms.new VirtualDeviceManagerImpl();
-        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID, DEVICE_OWNER_UID_1);
+        mDeviceImpl = createVirtualDevice(VIRTUAL_DEVICE_ID_1, DEVICE_OWNER_UID_1);
     }
 
     @Test
@@ -345,17 +352,17 @@
 
     @Test
     public void getDeviceIdForDisplayId_nonExistentDisplayId_returnsDefault() {
-        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.remove(DISPLAY_ID_1);
 
-        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID))
+        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(DEVICE_ID_DEFAULT);
     }
 
     @Test
     public void getDeviceIdForDisplayId_withValidVirtualDisplayId_returnsDeviceId() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
 
-        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID))
+        assertThat(mVdm.getDeviceIdForDisplayId(DISPLAY_ID_1))
                 .isEqualTo(mDeviceImpl.getDeviceId());
     }
 
@@ -391,7 +398,7 @@
                 .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
                 .build();
         mDeviceImpl = new VirtualDeviceImpl(mContext,
-                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID,
+                mAssociationInfo, new Binder(), /* ownerUid */ 0, VIRTUAL_DEVICE_ID_1,
                 mInputController, mSensorController,
                 /* onDeviceCloseListener= */ (int deviceId) -> {},
                 mPendingTrampolineCallback, mActivityListener, mRunningAppsChangedCallback, params);
@@ -409,15 +416,12 @@
 
     @Test
     public void getDeviceOwnerUid_twoDevices_returnsCorrectId() {
-        int firstDeviceId = mDeviceImpl.getDeviceId();
-        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
+        createVirtualDevice(VIRTUAL_DEVICE_ID_2, DEVICE_OWNER_UID_2);
 
-        createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
-
-        int secondDeviceOwner = mLocalService.getDeviceOwnerUid(secondDeviceId);
+        int secondDeviceOwner = mLocalService.getDeviceOwnerUid(VIRTUAL_DEVICE_ID_2);
         assertThat(secondDeviceOwner).isEqualTo(DEVICE_OWNER_UID_2);
 
-        int firstDeviceOwner = mLocalService.getDeviceOwnerUid(firstDeviceId);
+        int firstDeviceOwner = mLocalService.getDeviceOwnerUid(VIRTUAL_DEVICE_ID_1);
         assertThat(firstDeviceOwner).isEqualTo(DEVICE_OWNER_UID_1);
     }
 
@@ -438,7 +442,7 @@
     public void getDeviceIdsForUid_differentUidOnDevice_returnsNull() {
         GenericWindowPolicyController gwpc =
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
         gwpc.onRunningAppsChanged(Sets.newArraySet(UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
@@ -449,7 +453,7 @@
     public void getDeviceIdsForUid_oneUidOnDevice_returnsCorrectId() {
         GenericWindowPolicyController gwpc =
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
         gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
@@ -460,7 +464,7 @@
     public void getDeviceIdsForUid_twoUidsOnDevice_returnsCorrectId() {
         GenericWindowPolicyController gwpc =
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1);
         gwpc.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
 
         Set<Integer> deviceIds = mLocalService.getDeviceIdsForUid(UID_1);
@@ -469,9 +473,8 @@
 
     @Test
     public void getDeviceIdsForUid_twoDevicesUidOnOne_returnsCorrectId() {
-        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
-
-        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+        VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
+                DEVICE_OWNER_UID_2);
 
         GenericWindowPolicyController gwpc =
                 secondDevice.createWindowPolicyController(new ArrayList<>());
@@ -484,14 +487,13 @@
 
     @Test
     public void getDeviceIdsForUid_twoDevicesUidOnBoth_returnsCorrectId() {
-        int secondDeviceId = VIRTUAL_DEVICE_ID + 1;
-
-        VirtualDeviceImpl secondDevice = createVirtualDevice(secondDeviceId, DEVICE_OWNER_UID_2);
+        VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
+                DEVICE_OWNER_UID_2);
         GenericWindowPolicyController gwpc1 =
                 mDeviceImpl.createWindowPolicyController(new ArrayList<>());
         GenericWindowPolicyController gwpc2 =
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>());
-        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID);
+                secondDevice.createWindowPolicyController(new ArrayList<>());
+        mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc1, DISPLAY_ID_1);
         secondDevice.onVirtualDisplayCreatedLocked(gwpc2, DISPLAY_ID_2);
         gwpc1.onRunningAppsChanged(Sets.newArraySet(UID_1));
         gwpc2.onRunningAppsChanged(Sets.newArraySet(UID_1, UID_2));
@@ -502,33 +504,90 @@
     }
 
     @Test
+    public void getPreferredLocaleListForApp_keyboardAttached_returnLocaleHints() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+
+        mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
+
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
+
+        LocaleList localeList = mLocalService.getPreferredLocaleListForUid(UID_1);
+        assertThat(localeList).isEqualTo(
+                LocaleList.forLanguageTags(KEYBOARD_CONFIG.getLanguageTag()));
+    }
+
+    @Test
+    public void getPreferredLocaleListForApp_noKeyboardAttached_nullLocaleHints() {
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
+
+        // no preceding call to createVirtualKeyboard()
+        assertThat(mLocalService.getPreferredLocaleListForUid(UID_1)).isNull();
+    }
+
+    @Test
+    public void getPreferredLocaleListForApp_appOnMultipleVD_localeOnFirstVDReturned() {
+        VirtualDeviceImpl secondDevice = createVirtualDevice(VIRTUAL_DEVICE_ID_2,
+                DEVICE_OWNER_UID_2);
+        Binder secondBinder = new Binder("secondBinder");
+        VirtualKeyboardConfig firstKeyboardConfig =
+                new VirtualKeyboardConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
+                        .setLanguageTag("zh-CN")
+                        .build();
+        VirtualKeyboardConfig secondKeyboardConfig =
+                new VirtualKeyboardConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID_2)
+                        .setLanguageTag("fr-FR")
+                        .build();
+
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        secondDevice.mVirtualDisplayIds.add(DISPLAY_ID_2);
+
+        mDeviceImpl.createVirtualKeyboard(firstKeyboardConfig, BINDER);
+        secondDevice.createVirtualKeyboard(secondKeyboardConfig, secondBinder);
+
+        mVdms.notifyRunningAppsChanged(mDeviceImpl.getDeviceId(), Sets.newArraySet(UID_1));
+        mVdms.notifyRunningAppsChanged(secondDevice.getDeviceId(), Sets.newArraySet(UID_1));
+
+        LocaleList localeList = mLocalService.getPreferredLocaleListForUid(UID_1);
+        assertThat(localeList).isEqualTo(
+                LocaleList.forLanguageTags(firstKeyboardConfig.getLanguageTag()));
+    }
+
+    @Test
     public void onVirtualDisplayRemovedLocked_doesNotThrowException() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         // This call should not throw any exceptions.
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
     }
 
     @Test
     public void onVirtualDisplayCreatedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
 
-        mLocalService.onVirtualDisplayCreated(DISPLAY_ID);
+        mLocalService.onVirtualDisplayCreated(DISPLAY_ID_1);
         TestableLooper.get(this).processAllMessages();
 
-        verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID);
+        verify(mDisplayListener).onVirtualDisplayCreated(DISPLAY_ID_1);
     }
 
     @Test
     public void onVirtualDisplayRemovedLocked_listenersNotified() {
         mLocalService.registerVirtualDisplayListener(mDisplayListener);
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
 
-        mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID);
+        mLocalService.onVirtualDisplayRemoved(mDeviceImpl, DISPLAY_ID_1);
         TestableLooper.get(this).processAllMessages();
 
-        verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID);
+        verify(mDisplayListener).onVirtualDisplayRemoved(DISPLAY_ID_1);
     }
 
     @Test
@@ -585,10 +644,10 @@
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
                 nullable(String.class), anyInt(), eq(null));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(DISPLAY_ID), eq(null));
+                nullable(String.class), eq(DISPLAY_ID_1), eq(null));
     }
 
     @Test
@@ -597,13 +656,13 @@
         GenericWindowPolicyController gwpc = mDeviceImpl.createWindowPolicyController(
                 new ArrayList<>());
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         assertThrows(IllegalStateException.class,
-                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID));
+                () -> mDeviceImpl.onVirtualDisplayCreatedLocked(gwpc, DISPLAY_ID_1));
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(any(Binder.class), anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(DISPLAY_ID), eq(null));
+                nullable(String.class), eq(DISPLAY_ID_1), eq(null));
     }
 
     @Test
@@ -616,29 +675,29 @@
     @Test
     public void onVirtualDisplayRemovedLocked_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(DISPLAY_ID), eq(null));
+                nullable(String.class), eq(DISPLAY_ID_1), eq(null));
 
         IBinder wakeLock = wakeLockCaptor.getValue();
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
         verify(mIPowerManagerMock).releaseWakeLock(eq(wakeLock), anyInt());
     }
 
     @Test
     public void addVirtualDisplay_displayNotReleased_wakeLockIsReleased() throws RemoteException {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         ArgumentCaptor<IBinder> wakeLockCaptor = ArgumentCaptor.forClass(IBinder.class);
         TestableLooper.get(this).processAllMessages();
         verify(mIPowerManagerMock).acquireWakeLock(wakeLockCaptor.capture(),
                 anyInt(),
                 nullable(String.class), nullable(String.class), nullable(WorkSource.class),
-                nullable(String.class), eq(DISPLAY_ID), eq(null));
+                nullable(String.class), eq(DISPLAY_ID_1), eq(null));
         IBinder wakeLock = wakeLockCaptor.getValue();
 
         // Close the VirtualDevice without first notifying it of the VirtualDisplay removal.
@@ -672,13 +731,13 @@
 
     @Test
     public void createVirtualTouchscreen_zeroDisplayDimension_failsIllegalArgumentException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         final VirtualTouchscreenConfig zeroConfig =
                 new VirtualTouchscreenConfig.Builder()
                         .setVendorId(VENDOR_ID)
                         .setProductId(PRODUCT_ID)
                         .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
                         .setWidthInPixels(0)
                         .setHeightInPixels(0)
                         .build();
@@ -688,13 +747,13 @@
 
     @Test
     public void createVirtualTouchscreen_negativeDisplayDimension_failsIllegalArgumentException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         final VirtualTouchscreenConfig negativeConfig =
                 new VirtualTouchscreenConfig.Builder()
                         .setVendorId(VENDOR_ID)
                         .setProductId(PRODUCT_ID)
                         .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
                         .setWidthInPixels(-100)
                         .setHeightInPixels(-100)
                         .build();
@@ -705,20 +764,20 @@
 
     @Test
     public void createVirtualTouchscreen_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         VirtualTouchscreenConfig positiveConfig =
                 new VirtualTouchscreenConfig.Builder()
                         .setVendorId(VENDOR_ID)
                         .setProductId(PRODUCT_ID)
                         .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
                         .setWidthInPixels(600)
                         .setHeightInPixels(800)
                         .build();
         mDeviceImpl.createVirtualTouchscreen(positiveConfig, BINDER);
         assertWithMessage(
-            "Virtual touchscreen should create input device descriptor on successful creation"
-                + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+                "Virtual touchscreen should create input device descriptor on successful creation"
+                        + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
     }
 
     @Test
@@ -730,141 +789,141 @@
 
     @Test
     public void createVirtualNavigationTouchpad_zeroDisplayDimension_failsWithException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         assertThrows(IllegalArgumentException.class,
                 () -> {
-                final VirtualNavigationTouchpadConfig zeroConfig =
-                        new VirtualNavigationTouchpadConfig.Builder(
-                                /* touchpadHeight= */ 0, /* touchpadWidth= */ 0)
-                                .setVendorId(VENDOR_ID)
-                                .setProductId(PRODUCT_ID)
-                                .setInputDeviceName(DEVICE_NAME)
-                                .setAssociatedDisplayId(DISPLAY_ID)
-                                .build();
-                mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
-            });
+                    final VirtualNavigationTouchpadConfig zeroConfig =
+                            new VirtualNavigationTouchpadConfig.Builder(
+                                    /* touchpadHeight= */ 0, /* touchpadWidth= */ 0)
+                                    .setVendorId(VENDOR_ID)
+                                    .setProductId(PRODUCT_ID)
+                                    .setInputDeviceName(DEVICE_NAME)
+                                    .setAssociatedDisplayId(DISPLAY_ID_1)
+                                    .build();
+                    mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
+                });
     }
 
     @Test
     public void createVirtualNavigationTouchpad_negativeDisplayDimension_failsWithException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         assertThrows(IllegalArgumentException.class,
                 () -> {
-                    final VirtualNavigationTouchpadConfig zeroConfig =
+                    final VirtualNavigationTouchpadConfig negativeConfig =
                             new VirtualNavigationTouchpadConfig.Builder(
                                     /* touchpadHeight= */ -50, /* touchpadWidth= */ 50)
                                     .setVendorId(VENDOR_ID)
                                     .setProductId(PRODUCT_ID)
                                     .setInputDeviceName(DEVICE_NAME)
-                                    .setAssociatedDisplayId(DISPLAY_ID)
+                                    .setAssociatedDisplayId(DISPLAY_ID_1)
                                     .build();
-                mDeviceImpl.createVirtualNavigationTouchpad(zeroConfig, BINDER);
-            });
+                    mDeviceImpl.createVirtualNavigationTouchpad(negativeConfig, BINDER);
+                });
     }
 
     @Test
     public void createVirtualNavigationTouchpad_positiveDisplayDimension_successful() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         VirtualNavigationTouchpadConfig positiveConfig =
                 new VirtualNavigationTouchpadConfig.Builder(
-                            /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
+                        /* touchpadHeight= */ 50, /* touchpadWidth= */ 50)
                         .setVendorId(VENDOR_ID)
                         .setProductId(PRODUCT_ID)
                         .setInputDeviceName(DEVICE_NAME)
-                        .setAssociatedDisplayId(DISPLAY_ID)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
                         .build();
         mDeviceImpl.createVirtualNavigationTouchpad(positiveConfig, BINDER);
         assertWithMessage(
-            "Virtual navigation touchpad should create input device descriptor on successful "
-            + "creation"
-                + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
+                "Virtual navigation touchpad should create input device descriptor on successful "
+                        + "creation"
+                        + ".").that(mInputController.getInputDeviceDescriptors()).isNotEmpty();
     }
 
     @Test
     public void onAudioSessionStarting_noDisplay_failsSecurityException() {
         assertThrows(SecurityException.class,
                 () -> mDeviceImpl.onAudioSessionStarting(
-                        DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
+                        DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
     }
 
     @Test
     public void createVirtualDpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER));
+        }
     }
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
-                        BINDER));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG,
+                            BINDER));
+        }
     }
 
     @Test
     public void createVirtualSensor_noPermission_failsSecurityException() {
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(
-                SecurityException.class,
-                () -> mDeviceImpl.createVirtualSensor(
-                        BINDER,
-                        new VirtualSensorConfig.Builder(
-                                Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(
+                    SecurityException.class,
+                    () -> mDeviceImpl.createVirtualSensor(
+                            BINDER,
+                            new VirtualSensorConfig.Builder(
+                                    Sensor.TYPE_ACCELEROMETER, DEVICE_NAME).build()));
+        }
     }
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class,
-                () -> mDeviceImpl.onAudioSessionStarting(
-                        DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class,
+                    () -> mDeviceImpl.onAudioSessionStarting(
+                            DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
+        }
     }
 
     @Test
     public void onAudioSessionEnded_noPermission_failsSecurityException() {
-        doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
-                eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
-        assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+        try (DropShellPermissionsTemporarily drop = new DropShellPermissionsTemporarily()) {
+            assertThrows(SecurityException.class, () -> mDeviceImpl.onAudioSessionEnded());
+        }
     }
 
     @Test
     public void createVirtualDpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         mDeviceImpl.createVirtualDpad(DPAD_CONFIG, BINDER);
         assertWithMessage("Virtual dpad should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -874,7 +933,7 @@
 
     @Test
     public void createVirtualKeyboard_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
         assertWithMessage("Virtual keyboard should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -883,8 +942,50 @@
     }
 
     @Test
+    public void createVirtualKeyboard_keyboardCreated_localeUpdated() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        mDeviceImpl.createVirtualKeyboard(KEYBOARD_CONFIG, BINDER);
+        assertWithMessage("Virtual keyboard should register fd when the display matches")
+                .that(mInputController.getInputDeviceDescriptors())
+                .isNotEmpty();
+        verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString());
+        assertThat(mDeviceImpl.getDeviceLocaleList()).isEqualTo(
+                LocaleList.forLanguageTags(KEYBOARD_CONFIG.getLanguageTag()));
+    }
+
+    @Test
+    public void createVirtualKeyboard_keyboardWithoutExplicitLayoutInfo_localeUpdatedWithDefault() {
+        VirtualKeyboardConfig configWithoutExplicitLayoutInfo =
+                new VirtualKeyboardConfig.Builder()
+                        .setVendorId(VENDOR_ID)
+                        .setProductId(PRODUCT_ID)
+                        .setInputDeviceName(DEVICE_NAME)
+                        .setAssociatedDisplayId(DISPLAY_ID_1)
+                        .build();
+
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+        mDeviceImpl.createVirtualKeyboard(configWithoutExplicitLayoutInfo, BINDER);
+        assertWithMessage("Virtual keyboard should register fd when the display matches")
+                .that(mInputController.getInputDeviceDescriptors())
+                .isNotEmpty();
+        verify(mNativeWrapperMock).openUinputKeyboard(eq(DEVICE_NAME), eq(VENDOR_ID),
+                eq(PRODUCT_ID), anyString());
+        assertThat(mDeviceImpl.getDeviceLocaleList()).isEqualTo(
+                LocaleList.forLanguageTags(VirtualKeyboardConfig.DEFAULT_LANGUAGE_TAG));
+    }
+
+    @Test
+    public void virtualDeviceWithoutKeyboard_noLocaleUpdate() {
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
+
+        // no preceding call to createVirtualKeyboard()
+        assertThat(mDeviceImpl.getDeviceLocaleList()).isNull();
+    }
+
+    @Test
     public void createVirtualMouse_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         mDeviceImpl.createVirtualMouse(MOUSE_CONFIG, BINDER);
         assertWithMessage("Virtual mouse should register fd when the display matches").that(
                 mInputController.getInputDeviceDescriptors()).isNotEmpty();
@@ -894,21 +995,21 @@
 
     @Test
     public void createVirtualTouchscreen_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         mDeviceImpl.createVirtualTouchscreen(TOUCHSCREEN_CONFIG, BINDER);
         assertWithMessage("Virtual touchscreen should register fd when the display matches").that(
-            mInputController.getInputDeviceDescriptors()).isNotEmpty();
+                mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
 
     @Test
     public void createVirtualNavigationTouchpad_hasDisplay_obtainFileDescriptor() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         mDeviceImpl.createVirtualNavigationTouchpad(NAVIGATION_TOUCHPAD_CONFIG, BINDER);
         assertWithMessage("Virtual navigation touchpad should register fd when the display matches")
-            .that(
-            mInputController.getInputDeviceDescriptors()).isNotEmpty();
+                .that(
+                        mInputController.getInputDeviceDescriptors()).isNotEmpty();
         verify(mNativeWrapperMock).openUinputTouchscreen(eq(DEVICE_NAME), eq(VENDOR_ID),
                 eq(PRODUCT_ID), anyString(), eq(HEIGHT), eq(WIDTH));
     }
@@ -917,18 +1018,18 @@
     public void createVirtualKeyboard_inputDeviceId_obtainFromInputController() {
         final int fd = 1;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */ 1, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         assertWithMessage(
                 "InputController should return device id from InputDeviceDescriptor").that(
-                mInputController.getInputDeviceId(BINDER)).isEqualTo(DEVICE_ID);
+                mInputController.getInputDeviceId(BINDER)).isEqualTo(INPUT_DEVICE_ID);
     }
 
     @Test
     public void onAudioSessionStarting_hasVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
 
-        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
+        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         assertThat(mDeviceImpl.getVirtualAudioControllerForTesting()).isNotNull();
     }
@@ -936,8 +1037,8 @@
     @Test
     public void onAudioSessionEnded_noVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
-        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.onAudioSessionEnded();
 
@@ -947,8 +1048,8 @@
     @Test
     public void close_cleanVirtualAudioController() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
-        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID, mRoutingCallback, mConfigChangedCallback);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
+        mDeviceImpl.onAudioSessionStarting(DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback);
 
         mDeviceImpl.close();
 
@@ -982,10 +1083,12 @@
         final int keyCode = KeyEvent.KEYCODE_A;
         final int action = VirtualKeyEvent.ACTION_UP;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */1, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
 
-        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder().setKeyCode(keyCode)
-                .setAction(action).build());
+        mDeviceImpl.sendKeyEvent(BINDER, new VirtualKeyEvent.Builder()
+                .setKeyCode(keyCode)
+                .setAction(action)
+                .build());
         verify(mNativeWrapperMock).writeKeyEvent(fd, keyCode, action);
     }
 
@@ -1007,7 +1110,7 @@
         final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mDeviceImpl.sendButtonEvent(BINDER, new VirtualMouseButtonEvent.Builder()
                 .setButtonCode(buttonCode)
@@ -1021,7 +1124,7 @@
         final int buttonCode = VirtualMouseButtonEvent.BUTTON_BACK;
         final int action = VirtualMouseButtonEvent.ACTION_BUTTON_PRESS;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -1046,7 +1149,7 @@
         final float x = -0.2f;
         final float y = 0.7f;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mDeviceImpl.sendRelativeEvent(BINDER, new VirtualMouseRelativeEvent.Builder()
                 .setRelativeX(x).setRelativeY(y).build());
@@ -1059,7 +1162,7 @@
         final float x = -0.2f;
         final float y = 0.7f;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -1085,7 +1188,7 @@
         final float x = 0.5f;
         final float y = 1f;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId();
         mDeviceImpl.sendScrollEvent(BINDER, new VirtualMouseScrollEvent.Builder()
                 .setXAxisMovement(x)
@@ -1099,7 +1202,7 @@
         final float x = 0.5f;
         final float y = 1f;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */2, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
+                INPUT_DEVICE_ID);
         assertThrows(
                 IllegalStateException.class,
                 () ->
@@ -1131,9 +1234,14 @@
         final float y = 200.5f;
         final int action = VirtualTouchEvent.ACTION_UP;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
-        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
-                .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType).build());
+                INPUT_DEVICE_ID);
+        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
+                .setX(x)
+                .setY(y)
+                .setAction(action)
+                .setPointerId(pointerId)
+                .setToolType(toolType)
+                .build());
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, Float.NaN,
                 Float.NaN);
     }
@@ -1149,10 +1257,16 @@
         final float pressure = 1.0f;
         final float majorAxisSize = 10.0f;
         mInputController.addDeviceForTesting(BINDER, fd, /* type= */3, /* displayId= */ 1, PHYS,
-                DEVICE_ID);
-        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder().setX(x)
-                .setY(y).setAction(action).setPointerId(pointerId).setToolType(toolType)
-                .setPressure(pressure).setMajorAxisSize(majorAxisSize).build());
+                INPUT_DEVICE_ID);
+        mDeviceImpl.sendTouchEvent(BINDER, new VirtualTouchEvent.Builder()
+                .setX(x)
+                .setY(y)
+                .setAction(action)
+                .setPointerId(pointerId)
+                .setToolType(toolType)
+                .setPressure(pressure)
+                .setMajorAxisSize(majorAxisSize)
+                .build());
         verify(mNativeWrapperMock).writeTouchEvent(fd, pointerId, toolType, action, x, y, pressure,
                 majorAxisSize);
     }
@@ -1194,9 +1308,9 @@
     @Test
     public void openNonBlockedAppOnVirtualDisplay_doesNotStartBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1215,9 +1329,9 @@
     @Test
     public void openPermissionControllerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1236,9 +1350,9 @@
     @Test
     public void openSettingsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1257,9 +1371,9 @@
     @Test
     public void openVendingOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1278,9 +1392,9 @@
     @Test
     public void openGoogleDialerOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1299,9 +1413,9 @@
     @Test
     public void openGoogleMapsOnVirtualDisplay_startBlockedAlertActivity() {
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
         doNothing().when(mContext).startActivityAsUser(any(), any(), any());
 
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
@@ -1321,9 +1435,9 @@
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
+                DISPLAY_ID_1);
 
         gwpc.onRunningAppsChanged(uids);
         mDeviceImpl.onRunningAppsChanged(uids);
@@ -1336,10 +1450,10 @@
     public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                DISPLAY_ID);
-        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+                DISPLAY_ID_1);
+        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID_1);
 
         // This call should not throw any exceptions.
         gwpc.onRunningAppsChanged(uids);
@@ -1351,17 +1465,17 @@
     public void canActivityBeLaunched_activityCanLaunch() {
         Intent intent = new Intent(ACTION_VIEW, Uri.parse(TEST_SITE));
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                            DISPLAY_ID);
+                DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-            /* displayOnRemoveDevices */ true,
-            /* targetDisplayCategory */ null);
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
         assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
-            WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
-            .isTrue();
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isTrue();
     }
 
     @Test
@@ -1376,14 +1490,14 @@
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                            DISPLAY_ID);
+                DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-            /* displayOnRemoveDevices */ true,
-            /* targetDisplayCategory */ null);
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
         intentFilter.addDataScheme(IntentFilter.SCHEME_HTTP);
@@ -1392,8 +1506,8 @@
         // register interceptor and intercept intent
         mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
         assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
-            WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
-            .isFalse();
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isFalse();
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(interceptor).onIntentIntercepted(intentCaptor.capture());
         Intent cIntent = intentCaptor.getValue();
@@ -1404,8 +1518,8 @@
         // unregister interceptor and launch activity
         mDeviceImpl.unregisterIntentInterceptor(interceptor);
         assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
-            WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
-            .isTrue();
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isTrue();
     }
 
     @Test
@@ -1420,14 +1534,14 @@
         doReturn(interceptor).when(interceptor).queryLocalInterface(anyString());
 
         mDeviceImpl.onVirtualDisplayCreatedLocked(
-                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID);
+                mDeviceImpl.createWindowPolicyController(new ArrayList<>()), DISPLAY_ID_1);
         GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
-                            DISPLAY_ID);
+                DISPLAY_ID_1);
         ArrayList<ActivityInfo> activityInfos = getActivityInfoList(
                 NONBLOCKED_APP_PACKAGE_NAME,
                 NONBLOCKED_APP_PACKAGE_NAME,
-            /* displayOnRemoveDevices */ true,
-            /* targetDisplayCategory */ null);
+                /* displayOnRemoveDevices */ true,
+                /* targetDisplayCategory */ null);
 
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_VIEW);
         intentFilter.addDataScheme("mailto");
@@ -1436,8 +1550,8 @@
         mDeviceImpl.registerIntentInterceptor(interceptor, intentFilter);
 
         assertThat(gwpc.canActivityBeLaunched(activityInfos.get(0), intent,
-            WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID, /*isNewTask=*/false))
-            .isTrue();
+                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /*isNewTask=*/false))
+                .isTrue();
     }
 
     @Test
@@ -1484,4 +1598,18 @@
         mVdms.addVirtualDevice(virtualDeviceImpl);
         return virtualDeviceImpl;
     }
+
+    /** Helper class to drop permissions temporarily and restore them at the end of a test. */
+    static final class DropShellPermissionsTemporarily implements AutoCloseable {
+        DropShellPermissionsTemporarily() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+
+        @Override
+        public void close() {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity();
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index bd4058a..69a0b87 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -40,6 +40,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -73,6 +74,7 @@
 import android.test.mock.MockContentResolver;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TypedValue;
 import android.view.Display;
 import android.view.SurfaceControl.RefreshRateRange;
 import android.view.SurfaceControl.RefreshRateRanges;
@@ -102,6 +104,7 @@
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -2293,6 +2296,61 @@
                 new int[]{20});
     }
 
+    @Test
+    public void testSensorReloadOnDeviceSwitch() throws Exception {
+        // First, configure brightness zones or DMD won't register for sensor data.
+        final FakeDeviceConfig config = mInjector.getDeviceConfig();
+        config.setRefreshRateInHighZone(60);
+        config.setHighDisplayBrightnessThresholds(new int[] { 255 });
+        config.setHighAmbientBrightnessThresholds(new int[] { 8000 });
+
+        DisplayModeDirector director =
+                createDirectorFromRefreshRateArray(new float[] {60.f, 90.f}, 0);
+        setPeakRefreshRate(90 /*fps*/);
+        director.getSettingsObserver().setDefaultRefreshRate(90);
+        director.getBrightnessObserver().setDefaultDisplayState(Display.STATE_ON);
+
+        Sensor lightSensorOne = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        Sensor lightSensorTwo = TestUtils.createSensor(Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT);
+        SensorManager sensorManager = createMockSensorManager(lightSensorOne, lightSensorTwo);
+        when(sensorManager.getDefaultSensor(5)).thenReturn(lightSensorOne, lightSensorTwo);
+        director.start(sensorManager);
+        ArgumentCaptor<SensorEventListener> listenerCaptor =
+                ArgumentCaptor.forClass(SensorEventListener.class);
+        verify(sensorManager, Mockito.timeout(TimeUnit.SECONDS.toMillis(1)))
+                .registerListener(
+                        listenerCaptor.capture(),
+                        eq(lightSensorOne),
+                        anyInt(),
+                        any(Handler.class));
+
+        DisplayDeviceConfig ddcMock = mock(DisplayDeviceConfig.class);
+        when(ddcMock.getDefaultLowRefreshRate()).thenReturn(50);
+        when(ddcMock.getDefaultHighRefreshRate()).thenReturn(55);
+        when(ddcMock.getLowDisplayBrightnessThresholds()).thenReturn(new int[]{25});
+        when(ddcMock.getLowAmbientBrightnessThresholds()).thenReturn(new int[]{30});
+        when(ddcMock.getHighDisplayBrightnessThresholds()).thenReturn(new int[]{210});
+        when(ddcMock.getHighAmbientBrightnessThresholds()).thenReturn(new int[]{2100});
+
+        Resources resMock = mock(Resources.class);
+        when(resMock.getInteger(
+                com.android.internal.R.integer.config_displayWhiteBalanceBrightnessFilterHorizon))
+                .thenReturn(3);
+        ArgumentCaptor<TypedValue> valueArgumentCaptor = ArgumentCaptor.forClass(TypedValue.class);
+        doAnswer((Answer<Void>) invocation -> {
+            valueArgumentCaptor.getValue().type = 4;
+            valueArgumentCaptor.getValue().data = 13;
+            return null;
+        }).when(resMock).getValue(anyInt(), valueArgumentCaptor.capture(), eq(true));
+        when(mContext.getResources()).thenReturn(resMock);
+
+        director.defaultDisplayDeviceUpdated(ddcMock);
+
+        verify(sensorManager).unregisterListener(any(SensorEventListener.class));
+        verify(sensorManager).registerListener(any(SensorEventListener.class),
+                eq(lightSensorTwo), anyInt(), any(Handler.class));
+    }
+
     private Temperature getSkinTemp(@Temperature.ThrottlingStatus int status) {
         return new Temperature(30.0f, Temperature.TYPE_SKIN, "test_skin_temp", status);
     }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 5246107..5dd29fd 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -39,6 +39,7 @@
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.hardware.hdmi.HdmiPortInfo;
+import android.hardware.hdmi.IHdmiControlCallback;
 import android.hardware.tv.cec.V1_0.SendMessageResult;
 import android.media.AudioManager;
 import android.os.Looper;
@@ -96,6 +97,7 @@
     private int mTvPhysicalAddress;
     private int mTvLogicalAddress;
     private boolean mWokenUp;
+    private boolean mEarcBlocksArc;
     private List<DeviceEventListener> mDeviceEventListeners = new ArrayList<>();
 
     private class DeviceEventListener {
@@ -156,6 +158,11 @@
                     }
 
                     @Override
+                    boolean isPowerStandbyOrTransient() {
+                        return false;
+                    }
+
+                    @Override
                     AudioManager getAudioManager() {
                         return mAudioManager;
                     }
@@ -164,6 +171,11 @@
                     void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
                         mDeviceEventListeners.add(new DeviceEventListener(device, status));
                     }
+
+                    @Override
+                    protected boolean earcBlocksArcConnection() {
+                        return mEarcBlocksArc;
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
@@ -175,16 +187,18 @@
         mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
         HdmiPortInfo[] hdmiPortInfos = new HdmiPortInfo[2];
         hdmiPortInfos[0] =
-                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false);
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x1000, true, false, false, false);
         hdmiPortInfos[1] =
-                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true);
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
         mNativeWrapper.setPortInfo(hdmiPortInfos);
         mHdmiControlService.initService();
         mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
         mPowerManager = new FakePowerManagerWrapper(context);
         mHdmiControlService.setPowerManager(mPowerManager);
         mTvPhysicalAddress = 0x0000;
+        mEarcBlocksArc = false;
         mNativeWrapper.setPhysicalAddress(mTvPhysicalAddress);
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
         mTestLooper.dispatchAll();
         mHdmiCecLocalDeviceTv = mHdmiControlService.tv();
         mTvLogicalAddress = mHdmiCecLocalDeviceTv.getDeviceInfo().getLogicalAddress();
@@ -196,6 +210,20 @@
         mNativeWrapper.clearResultMessages();
     }
 
+    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 initialPowerStateIsStandby() {
         assertThat(mHdmiCecLocalDeviceTv.getPowerStatus()).isEqualTo(
@@ -426,9 +454,10 @@
     public void startArcAction_enable_portDoesNotSupportArc() {
         // Emulate Audio device on port 0x1000 (does not support ARC)
         mNativeWrapper.setPortConnectionStatus(1, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
 
         mHdmiCecLocalDeviceTv.startArcAction(true);
         HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
@@ -445,9 +474,10 @@
     public void startArcAction_disable_portDoesNotSupportArc() {
         // Emulate Audio device on port 0x1000 (does not support ARC)
         mNativeWrapper.setPortConnectionStatus(1, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
 
         mHdmiCecLocalDeviceTv.startArcAction(false);
         HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
@@ -464,9 +494,10 @@
     public void startArcAction_enable_portSupportsArc() {
         // Emulate Audio device on port 0x2000 (supports ARC)
         mNativeWrapper.setPortConnectionStatus(2, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
         mHdmiCecLocalDeviceTv.startArcAction(true);
@@ -485,9 +516,10 @@
     public void startArcAction_disable_portSupportsArc() {
         // Emulate Audio device on port 0x2000 (supports ARC)
         mNativeWrapper.setPortConnectionStatus(2, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
         mHdmiCecLocalDeviceTv.startArcAction(false);
@@ -522,9 +554,10 @@
     public void handleInitiateArc_portDoesNotSupportArc() {
         // Emulate Audio device on port 0x1000 (does not support ARC)
         mNativeWrapper.setPortConnectionStatus(1, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
 
         HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
                 ADDR_AUDIO_SYSTEM,
@@ -544,9 +577,10 @@
     public void handleInitiateArc_portSupportsArc() {
         // Emulate Audio device on port 0x2000 (supports ARC)
         mNativeWrapper.setPortConnectionStatus(2, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
@@ -575,6 +609,66 @@
     }
 
     @Test
+    public void handleTerminateArc_noAudioDevice() {
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(terminateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+    }
+
+    @Test
+    public void handleTerminateArc_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (does not support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(terminateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+    }
+
+    @Test
+    public void handleTerminateArc_portSupportsArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(terminateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+    }
+
+    @Test
     public void supportsRecordTvScreen() {
         HdmiCecMessage recordTvScreen = HdmiCecMessage.build(ADDR_RECORDER_1, mTvLogicalAddress,
                 Constants.MESSAGE_RECORD_TV_SCREEN, HdmiCecMessage.EMPTY_PARAM);
@@ -595,9 +689,10 @@
                 HdmiControlManager.SYSTEM_AUDIO_CONTROL_ENABLED);
         // Emulate Audio device on port 0x1000 (does not support ARC)
         mNativeWrapper.setPortConnectionStatus(1, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
         HdmiCecFeatureAction systemAudioAutoInitiationAction =
@@ -636,9 +731,9 @@
     public void hotplugDetectionAction_discoversDeviceAfterMessageReceived() {
         // Playback 1 sends a message before ACKing a poll
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.NACK);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildActiveSource(
+        HdmiCecMessage activeSource = HdmiCecMessageBuilder.buildActiveSource(
                 ADDR_PLAYBACK_1, ADDR_TV);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        mNativeWrapper.onCecMessage(activeSource);
         mTestLooper.dispatchAll();
 
         // Playback 1 begins ACKing polls, allowing detection by HotplugDetectionAction
@@ -812,12 +907,12 @@
         mTestLooper.dispatchAll();
 
         // <Feature Abort>[Not in correct mode] not sent
-        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
                 ADDR_TV,
                 ADDR_PLAYBACK_1,
                 Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
                 Constants.ABORT_NOT_IN_CORRECT_MODE);
-        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbortMessage);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(featureAbort);
 
         // <Set Audio Volume Level> uses volume range [0, 100]; STREAM_MUSIC uses range [0, 25]
         verify(mAudioManager).setStreamVolume(eq(AudioManager.STREAM_MUSIC), eq(5), anyInt());
@@ -838,12 +933,12 @@
         mTestLooper.dispatchAll();
 
         // <Feature Abort>[Not in correct mode] sent
-        HdmiCecMessage featureAbortMessage = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
                 ADDR_TV,
                 ADDR_PLAYBACK_1,
                 Constants.MESSAGE_SET_AUDIO_VOLUME_LEVEL,
                 Constants.ABORT_NOT_IN_CORRECT_MODE);
-        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbortMessage);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
 
         // AudioManager not notified of volume change
         verify(mAudioManager, never()).setStreamVolume(eq(AudioManager.STREAM_MUSIC), anyInt(),
@@ -853,11 +948,11 @@
     @Test
     public void tvSendRequestArcTerminationOnSleep() {
         // Emulate Audio device on port 0x2000 (supports ARC)
-
         mNativeWrapper.setPortConnectionStatus(2, true);
-        HdmiCecMessage hdmiCecMessage = HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
-        mNativeWrapper.onCecMessage(hdmiCecMessage);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
         mTestLooper.dispatchAll();
 
         mHdmiCecLocalDeviceTv.startArcAction(true);
@@ -898,4 +993,560 @@
         assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
     }
 
+    @Test
+    public void startArcAction_enable_earcBlocksArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = true;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_enable_earcDoesNotBlockArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = false;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcTermination);
+    }
+
+    @Test
+    public void startArcAction_disable_earcBlocksArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = true;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false);
+        mTestLooper.dispatchAll();
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(requestArcInitiation);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+    }
+
+    @Test
+    public void handleInitiateArc_earcBlocksArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = true;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM,
+                Constants.MESSAGE_INITIATE_ARC,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+    }
+
+    @Test
+    public void handleInitiateArc_earcDoesNotBlockArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = false;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(requestArcInitiation);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        // <Report ARC Initiated> should only be sent after SAD querying is done
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+
+        // Finish querying SADs
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+    }
+
+    @Test
+    public void handleTerminateArc_earcBlocksArc() {
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = true;
+
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(terminateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcTerminated = HdmiCecMessageBuilder.buildReportArcTerminated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcTerminated);
+    }
+
+    @Test
+    public void startArcAction_initiation_noAvr() {
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_initiation_portNotConnected() {
+        // Emulate Audio device on port 0x2000 (supports ARC)
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+        // Emulate port disconnect
+        mNativeWrapper.setPortConnectionStatus(2, false);
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+    }
+
+    @Test
+    public void startArcAction_initiation_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (Doesn´t support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+    }
+
+    @Test
+    public void startArcAction_initiation_indirectPhysicalAddress() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2320, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+    }
+
+    @Test
+    public void startArcAction_initiation_earcBlocksArc() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mEarcBlocksArc = true;
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+    }
+
+    @Test
+    public void startArcAction_initiation_messageNotAcked() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setMessageSendResult(
+                Constants.MESSAGE_REQUEST_ARC_INITIATION, SendMessageResult.NACK);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_initiation_timeout() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+    }
+
+    @Test
+    public void startArcAction_initiation_featureAbort() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV,
+                Constants.MESSAGE_REQUEST_ARC_INITIATION,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        mNativeWrapper.onCecMessage(featureAbort);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_initiation_success() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(true, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcInitiation = HdmiCecMessageBuilder.buildRequestArcInitiation(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcInitiation);
+
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+        mNativeWrapper.onCecMessage(initiateArc);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void startArcAction_termination_noAvr() {
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_termination_portDoesNotSupportArc() {
+        // Emulate Audio device on port 0x1000 (Doesn´t support ARC)
+        mNativeWrapper.setPortConnectionStatus(1, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x1000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_INCORRECT_MODE);
+    }
+
+    @Test
+    public void startArcAction_termination_messageNotAcked() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        mNativeWrapper.setMessageSendResult(
+                Constants.MESSAGE_REQUEST_ARC_TERMINATION, SendMessageResult.NACK);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_termination_timeout() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+        mTestLooper.moveTimeForward(TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TIMEOUT);
+    }
+
+    @Test
+    public void startArcAction_termination_featureAbort() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+
+        HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV,
+                Constants.MESSAGE_REQUEST_ARC_TERMINATION,
+                Constants.ABORT_NOT_IN_CORRECT_MODE);
+        mNativeWrapper.onCecMessage(featureAbort);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+    }
+
+    @Test
+    public void startArcAction_termination_success() {
+        // Emulate Audio device on port 0x2000 (Supports ARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        TestCallback callback = new TestCallback();
+
+        mHdmiCecLocalDeviceTv.startArcAction(false, callback);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+
+        HdmiCecMessage terminateArc = HdmiCecMessageBuilder.buildTerminateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+        mNativeWrapper.onCecMessage(terminateArc);
+        mTestLooper.dispatchAll();
+
+        assertThat(callback.getResult()).isEqualTo(HdmiControlManager.RESULT_SUCCESS);
+    }
+
+    @Test
+    public void enableEarc_terminateArc() {
+        // Emulate Audio device on port 0x2000 (supports ARC and eARC)
+        mNativeWrapper.setPortConnectionStatus(2, true);
+        HdmiCecMessage reportPhysicalAddress =
+                HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
+                        ADDR_AUDIO_SYSTEM, 0x2000, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+        mNativeWrapper.onCecMessage(reportPhysicalAddress);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage initiateArc = HdmiCecMessageBuilder.buildInitiateArc(
+                ADDR_AUDIO_SYSTEM,
+                ADDR_TV);
+
+        mNativeWrapper.onCecMessage(initiateArc);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage reportArcInitiated = HdmiCecMessageBuilder.buildReportArcInitiated(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+        // <Report ARC Initiated> should only be sent after SAD querying is done
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(reportArcInitiated);
+
+        // Finish querying SADs
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+        assertThat(mNativeWrapper.getResultMessages()).contains(SAD_QUERY);
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(reportArcInitiated);
+        mNativeWrapper.clearResultMessages();
+
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        HdmiCecMessage requestArcTermination = HdmiCecMessageBuilder.buildRequestArcTermination(
+                ADDR_TV,
+                ADDR_AUDIO_SYSTEM);
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestArcTermination);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
index b8a1ba3..a82a79f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecNetworkTest.java
@@ -605,4 +605,36 @@
 
         assertThat(mHdmiCecNetwork.getSafeCecDevicesLocked()).hasSize(1);
     }
+
+    @Test
+    public void disableCec_clearCecLocalDevices() {
+        mHdmiCecNetwork.clearLocalDevices();
+        mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
+                new HdmiCecLocalDeviceTv(mHdmiControlService));
+
+        assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+        assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+                HdmiCecLocalDeviceTv.class);
+        mHdmiControlService.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(0);
+    }
+
+    @Test
+    public void disableEarc_doNotClearCecLocalDevices() {
+        mHdmiCecNetwork.clearLocalDevices();
+        mHdmiCecNetwork.addLocalDevice(HdmiDeviceInfo.DEVICE_TV,
+                new HdmiCecLocalDeviceTv(mHdmiControlService));
+
+        assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+        assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+                HdmiCecLocalDeviceTv.class);
+        mHdmiControlService.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+
+        assertThat(mHdmiCecNetwork.getLocalDeviceList()).hasSize(1);
+        assertThat(mHdmiCecNetwork.getLocalDeviceList().get(0)).isInstanceOf(
+                HdmiCecLocalDeviceTv.class);
+    }
 }
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 49a0a9a52..aa49a62 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -22,6 +22,7 @@
 import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
 import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -29,7 +30,11 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.TestCase.assertEquals;
 
+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.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
@@ -127,19 +132,20 @@
         mLocalDevices.add(mPlaybackDeviceSpy);
         mHdmiPortInfo = new HdmiPortInfo[4];
         mHdmiPortInfo[0] =
-                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+                new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false, false);
         mHdmiPortInfo[1] =
-                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+                new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false, false);
         mHdmiPortInfo[2] =
-                new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+                new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, true, true);
         mHdmiPortInfo[3] =
-                new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+                new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false, false);
         mNativeWrapper.setPortInfo(mHdmiPortInfo);
         mHdmiControlServiceSpy.initService();
         mPowerManager = new FakePowerManagerWrapper(mContextSpy);
         mHdmiControlServiceSpy.setPowerManager(mPowerManager);
         mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mHdmiControlServiceSpy.setAudioManager(mAudioManager);
+        mHdmiControlServiceSpy.setEarcSupported(true);
 
         mTestLooper.dispatchAll();
     }
@@ -1082,6 +1088,259 @@
         assertThat(mHdmiControlServiceSpy.audioSystem()).isNull();
     }
 
+    @Test
+    public void disableEarc_clearEarcLocalDevice() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        mHdmiControlServiceSpy.addEarcLocalDevice(
+                new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
+        assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
+
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNull();
+    }
+
+    @Test
+    public void disableCec_doNotClearEarcLocalDevice() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        mHdmiControlServiceSpy.addEarcLocalDevice(
+                new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy));
+        assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
+
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+        assertThat(mHdmiControlServiceSpy.getEarcLocalDevice()).isNotNull();
+    }
+
+    @Test
+    public void enableCec_initializeCecLocalDevices() {
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlServiceSpy.setCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).initializeCecLocalDevices(anyInt());
+        verify(mHdmiControlServiceSpy, times(0)).initializeEarcLocalDevice(anyInt());
+    }
+
+    @Test
+    public void enableEarc_initializeEarcLocalDevices() {
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        mHdmiControlServiceSpy.setEarcEnabled(HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(0)).initializeCecLocalDevices(anyInt());
+        verify(mHdmiControlServiceSpy, times(1)).initializeEarcLocalDevice(anyInt());
+    }
+
+    @Test
+    public void disableCec_DoNotInformHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void disableEarc_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
+    }
+
+    @Test
+    public void enableCec_DoNotInformHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(anyBoolean(), anyBoolean());
+    }
+
+    @Test
+    public void enableEarc_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, true);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void bootWithEarcEnabled_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_ENABLED);
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED,
+                HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.initService();
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void bootWithEarcDisabled_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.initService();
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
+    }
+
+    @Test
+    public void wakeUpWithEarcEnabled_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_ENABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(true, false);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(false), anyBoolean());
+    }
+
+    @Test
+    public void wakeUpWithEarcDisabled_informHalAboutEarc() {
+        mHdmiControlServiceSpy.getHdmiCecConfig().setIntValue(
+                HdmiControlManager.SETTING_NAME_EARC_ENABLED,
+                HdmiControlManager.EARC_FEATURE_DISABLED);
+        mTestLooper.dispatchAll();
+        Mockito.clearInvocations(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+        verify(mHdmiControlServiceSpy, times(1)).setEarcEnabledInHal(false, false);
+        verify(mHdmiControlServiceSpy, times(0)).setEarcEnabledInHal(eq(true), anyBoolean());
+    }
+
+    @Test
+    public void earcIdle_blocksArcConnection() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+    }
+
+    @Test
+    public void earcPending_blocksArcConnection() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+    }
+
+    @Test
+    public void earcEnabled_blocksArcConnection() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isTrue();
+    }
+
+    @Test
+    public void arcPending_doesNotBlockArcConnection() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        assertThat(mHdmiControlServiceSpy.earcBlocksArcConnection()).isFalse();
+    }
+
+    @Test
+    public void earcStatusBecomesIdle_terminateArc() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_IDLE);
+        verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
+    }
+
+    @Test
+    public void earcStatusBecomesEnabled_doNothing() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+        verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+    }
+
+    @Test
+    public void earcStatusBecomesPending_doNothing() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+        verify(mHdmiControlServiceSpy, times(0)).startArcAction(anyBoolean(), any());
+    }
+
+    @Test
+    public void earcStatusBecomesNotEnabled_initiateArc() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+        verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(true), any());
+    }
+
+    @Test
+    public void earcStateWasArcPending_becomesEarcPending_terminateArc() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+        mTestLooper.dispatchAll();
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_PENDING);
+        verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
+    }
+
+    @Test
+    public void earcStateWasArcPending_becomesEarcEnabled_terminateArc() {
+        mHdmiControlServiceSpy.clearEarcLocalDevice();
+        HdmiEarcLocalDeviceTx localDeviceTx = new HdmiEarcLocalDeviceTx(mHdmiControlServiceSpy);
+        mHdmiControlServiceSpy.addEarcLocalDevice(localDeviceTx);
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_ARC_PENDING);
+        mTestLooper.dispatchAll();
+        localDeviceTx.handleEarcStateChange(Constants.HDMI_EARC_STATUS_EARC_CONNECTED);
+        verify(mHdmiControlServiceSpy, times(1)).startArcAction(eq(false), any());
+    }
+
     protected static class MockPlaybackDevice extends HdmiCecLocalDevicePlayback {
 
         private boolean mCanGoToStandby;
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
new file mode 100644
index 0000000..bf44e09
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiEarcLocalDeviceTxTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.hdmi;
+
+import static android.media.AudioProfile.AUDIO_ENCAPSULATION_TYPE_NONE;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_ARC_PENDING;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_CONNECTED;
+import static com.android.server.hdmi.Constants.HDMI_EARC_STATUS_EARC_PENDING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.media.AudioDescriptor;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+/** Tests for {@link HdmiEarcLocalDeviceTx} class. */
+public class HdmiEarcLocalDeviceTxTest {
+
+    private HdmiControlService mHdmiControlService;
+    private HdmiCecController mHdmiCecController;
+    private HdmiEarcLocalDevice mHdmiEarcLocalDeviceTx;
+    private FakeNativeWrapper mNativeWrapper;
+    private FakePowerManagerWrapper mPowerManager;
+    private byte[] mEarcCapabilities = new byte[]{
+            0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
+            0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, (byte) 0x83,
+            0x5f, 0x00, 0x00, 0x00, 0x00, 0x00};
+    private Looper mMyLooper;
+    private TestLooper mTestLooper = new TestLooper();
+
+    @Mock
+    private AudioManager mAudioManager;
+
+    @Captor
+    ArgumentCaptor<AudioDeviceAttributes> mAudioAttributesCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mMyLooper = mTestLooper.getLooper();
+
+        mHdmiControlService =
+                new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+                        Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+                        new FakeAudioDeviceVolumeManagerWrapper()) {
+                    @Override
+                    boolean isCecControlEnabled() {
+                        return true;
+                    }
+
+                    @Override
+                    boolean isTvDevice() {
+                        return true;
+                    }
+
+                    @Override
+                    protected void writeStringSystemProperty(String key, String value) {
+                        // do nothing
+                    }
+
+                    @Override
+                    boolean isPowerStandby() {
+                        return false;
+                    }
+
+                    @Override
+                    AudioManager getAudioManager() {
+                        return mAudioManager;
+                    }
+                };
+
+        mHdmiControlService.setIoLooper(mMyLooper);
+        mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+        mNativeWrapper = new FakeNativeWrapper();
+        mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+                mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+        mHdmiControlService.setCecController(mHdmiCecController);
+        mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(mHdmiControlService));
+        mHdmiControlService.initService();
+        mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+        mPowerManager = new FakePowerManagerWrapper(context);
+        mHdmiControlService.setPowerManager(mPowerManager);
+        mTestLooper.dispatchAll();
+        mHdmiControlService.initializeEarcLocalDevice(HdmiControlService.INITIATED_BY_BOOT_UP);
+        mHdmiEarcLocalDeviceTx = mHdmiControlService.getEarcLocalDevice();
+    }
+
+    @Test
+    public void earcGetsConnected_capsReportedInTime_sad() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
+        mTestLooper.dispatchAll();
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
+                0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
+                0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, 0x00, 0x5f,
+                0x00, 0x00, 0x00, 0x00, 0x00
+        });
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
+                mAudioAttributesCaptor.capture(), eq(1));
+        AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
+        List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
+        List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {15, 127, 7}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {21, 7, 80}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {61, 31, -64}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {87, 6, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {103, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 1})));
+        assertThat(descriptors).isEqualTo(expectedDescriptors);
+    }
+
+    @Test
+    public void earcGetsConnected_capsReportedInTime_sad_sadb() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
+        mTestLooper.dispatchAll();
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
+                0x01, 0x01, 0x1a, 0x35, 0x0f, 0x7f, 0x07, 0x15, 0x07, 0x50, 0x3d, 0x1f, (byte) 0xc0,
+                0x57, 0x06, 0x03, 0x67, 0x7e, 0x03, 0x5f, 0x7e, 0x03, 0x5f, 0x7e, 0x01, (byte) 0x83,
+                0x5f, 0x00, 0x00, 0x00, 0x00, 0x00});
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
+                mAudioAttributesCaptor.capture(), eq(1));
+        AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
+        List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
+        List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {15, 127, 7}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {21, 7, 80}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {61, 31, -64}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {87, 6, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {103, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 1}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_SADB, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {-125, 95, 0, 0})));
+        assertThat(descriptors).isEqualTo(expectedDescriptors);
+    }
+
+    @Test
+    public void earcGetsConnected_capsReportedInTime_sad_sadb_vsadb() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS - 200);
+        mTestLooper.dispatchAll();
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(new byte[]{
+                0x01, 0x01, 0x21, 0x35, 0x5F, 0x7E, 0x03, 0x5F, 0x7E, 0x01, 0x67, 0x7E, 0x03, 0x57,
+                0x06, 0x03, 0x3D, 0x1E, (byte) 0xC0, 0x15, 0x07, 0x50, 0x0F, 0x7F, 0x07,
+                (byte) 0x83, 0x5F, 0x00, 0x00, (byte) 0xE6, 0x11, 0x46, (byte) 0xD0, 0x00, 0x70,
+                0x00, 0x03, 0x01, (byte) 0x80, 0x00, (byte) 0x9D, (byte) 0xAD, (byte) 0x9E, 0x7B,
+                0x08, (byte) 0xC1, (byte) 0xA8, 0x23, (byte) 0x9B, 0x49, 0x5C, (byte) 0xF5, 0x6B,
+                (byte) 0xAC, 0x22, (byte) 0xC2, (byte) 0x80, 0x48, 0x67, 0x7F, 0x59, 0x1C, 0x20,
+                0x71, 0x35, 0x25, (byte) 0x9F, 0x43, 0x70, 0x1E, 0x32, 0x15, 0x60, (byte) 0xED,
+                (byte) 0xC8, 0x77, (byte) 0xA3, 0x24, 0x2E, (byte) 0xDA, (byte) 0x94, 0x6D, 0x35,
+                0x34, 0x0F, 0x30, 0x62, 0x1A, 0x3B, (byte) 0xC9, 0x5A, (byte) 0xE6, (byte) 0xD8,
+                0x22, 0x11, 0x56, (byte) 0xA6, (byte) 0x99, (byte) 0xCF, (byte) 0xE3, 0x1B,
+                (byte) 0x88, (byte) 0xA0, 0x2A, 0x5B, 0x6C, 0x5E, 0x53, 0x01, 0x47, 0x69, 0x51,
+                0x61, (byte) 0xC7, (byte) 0xCB, 0x1B, 0x28, 0x14, 0x23, 0x10, (byte) 0xB1, 0x34,
+                0x5E, 0x57, (byte) 0x97, (byte) 0xB3, 0x78, 0x03, 0x79, (byte) 0x8A, (byte) 0xFE,
+                0x1E, (byte) 0xC8, (byte) 0xAB, 0x14, 0x74, 0x73, (byte) 0xFA, (byte) 0xBB,
+                (byte) 0xF7, 0x4E, 0x00, (byte) 0xFC, 0x5C, (byte) 0xDC, (byte) 0x8B, (byte) 0xC9,
+                0x1E, 0x16, 0x35, (byte) 0xB1, (byte) 0x98, (byte) 0xEB, 0x2B, (byte) 0xE6,
+                (byte) 0xFC, (byte) 0xCC, 0x3C, 0x30, 0x19, 0x40, (byte) 0xC0, 0x50, (byte) 0xF2,
+                0x58, 0x30, 0x4B, 0x0C, 0x7A, (byte) 0xE0, (byte) 0xFF, 0x7A, 0x64, 0x78,
+                (byte) 0xF8, 0x56, (byte) 0xF8, 0x6E, 0x72, 0x42, 0x49, 0x4E, (byte) 0xA6,
+                (byte) 0x95, (byte) 0xF5, 0x4C, 0x4F, (byte) 0xFF, 0x7F, 0x21, (byte) 0xA2,
+                (byte) 0x98, 0x33, (byte) 0x90, (byte) 0xFD, 0x17, 0x08, 0x13, (byte) 0xB2, 0x00,
+                (byte) 0xA9, (byte) 0xB5, (byte) 0xBD, (byte) 0xB5, (byte) 0xC1, (byte) 0xC7, 0x45,
+                (byte) 0xD9, (byte) 0xDC, (byte) 0x8B, 0x58, (byte) 0xB3, 0x5D, 0x5E, 0x72,
+                (byte) 0xE6, (byte) 0x8D, (byte) 0xDD, 0x0B, 0x21, (byte) 0xF3, (byte) 0x9A,
+                (byte) 0x8E, 0x1B, 0x79, 0x59, (byte) 0xE1, 0x3F, (byte) 0xAC, 0x24, (byte) 0xA0,
+                (byte) 0xC8, 0x56, (byte) 0xFD, (byte) 0x85, (byte) 0x8F, 0x6A, (byte) 0x80, 0x41,
+                (byte) 0xA8, 0x5D, 0x2C, (byte) 0xC2, 0x69, (byte) 0xA1, 0x0D, (byte) 0x82, 0x04,
+                0x5D, (byte) 0xCA, (byte) 0xB4, (byte) 0x9F, 0x3A, 0x2D, (byte) 0xBF, 0x24});
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
+                mAudioAttributesCaptor.capture(), eq(1));
+        AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
+        List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
+        List<AudioDescriptor> expectedDescriptors = new ArrayList<AudioDescriptor>(Arrays.asList(
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {95, 126, 1}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {103, 126, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {87, 6, 3}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {61, 30, -64}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {21, 7, 80}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_EDID, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {15, 127, 7}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_SADB, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {-125, 95, 0, 0}),
+                new AudioDescriptor(AudioDescriptor.STANDARD_VSADB, AUDIO_ENCAPSULATION_TYPE_NONE,
+                        new byte[] {-26, 17, 70, -48, 0, 112, 0})));
+        assertThat(descriptors).isEqualTo(expectedDescriptors);
+    }
+
+    @Test
+    public void earcGetsConnected_capsReportedTooLate() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.moveTimeForward(HdmiEarcLocalDeviceTx.REPORT_CAPS_MAX_DELAY_MS + 1);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
+                mAudioAttributesCaptor.capture(), eq(1));
+        AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
+        List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
+        assertThat(descriptors).hasSize(0);
+        Mockito.clearInvocations(mAudioManager);
+
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
+    }
+
+    @Test
+    public void earcGetsConnected_earcGetsDisconnectedBeforeCapsReported() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.dispatchAll();
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_ARC_PENDING);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), eq(1));
+        verify(mAudioManager, times(1)).setWiredDeviceConnectionState(
+                mAudioAttributesCaptor.capture(), eq(0));
+        AudioDeviceAttributes attributes = mAudioAttributesCaptor.getValue();
+        List<AudioDescriptor> descriptors = attributes.getAudioDescriptors();
+        assertThat(descriptors).hasSize(0);
+        Mockito.clearInvocations(mAudioManager);
+
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
+    }
+
+    @Test
+    public void earcGetsConnected_earcBecomesPendingBeforeCapsReported() {
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_CONNECTED);
+        mTestLooper.dispatchAll();
+        mHdmiEarcLocalDeviceTx.handleEarcStateChange(HDMI_EARC_STATUS_EARC_PENDING);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
+        Mockito.clearInvocations(mAudioManager);
+
+        mHdmiEarcLocalDeviceTx.handleEarcCapabilitiesReported(mEarcCapabilities);
+        mTestLooper.dispatchAll();
+        verify(mAudioManager, times(0)).setWiredDeviceConnectionState(any(), anyInt());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/job/JobSetTest.java b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
index 62cc111..baa5421 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobSetTest.java
@@ -79,7 +79,7 @@
                 .setRequiresCharging(true)
                 .build();
         return JobStatus.createFromJobInfo(jobInfo, callingUid, mContext.getPackageName(),
-                mContext.getUserId(), "Test");
+                mContext.getUserId(), "Namespace", "Test");
     }
 
     @Test
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 0589b3a..1c44da1 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,8 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -15,6 +17,7 @@
 
 import android.app.job.JobInfo;
 import android.app.job.JobInfo.Builder;
+import android.app.job.JobWorkItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
@@ -31,6 +34,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.util.ArrayUtils;
 import com.android.server.LocalServices;
 import com.android.server.job.JobStore.JobSet;
 import com.android.server.job.controllers.JobStatus;
@@ -43,6 +47,10 @@
 import java.io.File;
 import java.time.Clock;
 import java.time.ZoneOffset;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 
 /**
  * Test reading and writing correctly from file.
@@ -135,8 +143,8 @@
                 .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);
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
         runWritingJobsToDisk(JobStatus1, JobStatus2);
 
         // Remove 1 job
@@ -188,8 +196,8 @@
                 .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);
+        final JobStatus JobStatus1 = JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+        final JobStatus JobStatus2 = JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
         runWritingJobsToDisk(JobStatus1, JobStatus2);
 
         // Remove all jobs
@@ -265,7 +273,7 @@
                 .setMinimumLatency(runFromMillis)
                 .setPersisted(true)
                 .build();
-        final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
+        final JobStatus ts = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null);
         ts.addInternalFlags(JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION);
         mTaskStoreUnderTest.add(ts);
         waitForPendingIo();
@@ -308,8 +316,10 @@
                 .build();
         final int uid1 = SOME_UID;
         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);
+        final JobStatus taskStatus1 =
+                JobStatus.createFromJobInfo(task1, uid1, null, -1, null, null);
+        final JobStatus taskStatus2 =
+                JobStatus.createFromJobInfo(task2, uid2, null, -1, null, null);
 
         runWritingJobsToDisk(taskStatus1, taskStatus2);
     }
@@ -364,7 +374,7 @@
         extras.putInt("into", 3);
         b.setExtras(extras);
         final JobInfo task = b.build();
-        JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(task, SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -384,7 +394,7 @@
                 .setRequiresCharging(true)
                 .setPersisted(true);
         JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID,
-                "com.google.android.gms", 0, null);
+                "com.android.test.app", 0, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -406,7 +416,8 @@
                 .setPeriodic(5*60*60*1000, 1*60*60*1000)
                 .setRequiresCharging(true)
                 .setPersisted(true);
-        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -435,7 +446,7 @@
                 invalidLateRuntimeElapsedMillis - TWO_HOURS;  // Early is (late - period).
         final Pair<Long, Long> persistedExecutionTimesUTC = new Pair<>(rtcNow, rtcNow + ONE_HOUR);
         final JobStatus js = new JobStatus(b.build(), SOME_UID, "somePackage",
-                0 /* sourceUserId */, 0, "someTag",
+                0 /* sourceUserId */, 0, "someNamespace", "someTag",
                 invalidEarlyRuntimeElapsedMillis, invalidLateRuntimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
                 persistedExecutionTimesUTC, 0 /* innerFlag */, 0 /* dynamicConstraints */);
@@ -464,7 +475,7 @@
                 .setOverrideDeadline(5000)
                 .setBias(42)
                 .setPersisted(true);
-        final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        final JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
         mTaskStoreUnderTest.add(js);
         waitForPendingIo();
 
@@ -475,13 +486,30 @@
     }
 
     @Test
+    public void testNamespacePersisted() throws Exception {
+        final String namespace = "my.test.namespace";
+        JobInfo.Builder b = new Builder(93, mComponent)
+                .setRequiresBatteryNotLow(true)
+                .setPersisted(true);
+        final JobStatus js =
+                JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, namespace, null);
+        mTaskStoreUnderTest.add(js);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertEquals("Namespace not correctly persisted.", namespace, loaded.getNamespace());
+    }
+
+    @Test
     public void testPriorityPersisted() throws Exception {
         final JobInfo job = new Builder(92, mComponent)
                 .setOverrideDeadline(5000)
                 .setPriority(JobInfo.PRIORITY_MIN)
                 .setPersisted(true)
                 .build();
-        final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null);
+        final JobStatus js = JobStatus.createFromJobInfo(job, SOME_UID, null, -1, null, null);
         mTaskStoreUnderTest.add(js);
         waitForPendingIo();
 
@@ -500,12 +528,14 @@
         JobInfo.Builder b = new Builder(42, mComponent)
                 .setOverrideDeadline(10000)
                 .setPersisted(false);
-        JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus jsNonPersisted = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
         mTaskStoreUnderTest.add(jsNonPersisted);
         b = new Builder(43, mComponent)
                 .setOverrideDeadline(10000)
                 .setPersisted(true);
-        JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus jsPersisted = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
         mTaskStoreUnderTest.add(jsPersisted);
         waitForPendingIo();
 
@@ -593,7 +623,8 @@
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresDeviceIdle(true)
                 .setPersisted(true);
-        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -612,7 +643,8 @@
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresCharging(true)
                 .setPersisted(true);
-        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -631,7 +663,8 @@
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresStorageNotLow(true)
                 .setPersisted(true);
-        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -650,7 +683,8 @@
         JobInfo.Builder b = new Builder(8, mComponent)
                 .setRequiresBatteryNotLow(true)
                 .setPersisted(true);
-        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null);
+        JobStatus taskStatus = JobStatus.createFromJobInfo(b.build(),
+                SOME_UID, null, -1, null, null);
 
         mTaskStoreUnderTest.add(taskStatus);
         waitForPendingIo();
@@ -664,20 +698,46 @@
                 taskStatus.getJob().isRequireBatteryNotLow());
     }
 
+    @Test
+    public void testJobWorkItems() throws Exception {
+        JobWorkItem item1 = new JobWorkItem.Builder().build();
+        item1.bumpDeliveryCount();
+        PersistableBundle bundle = new PersistableBundle();
+        bundle.putBoolean("test", true);
+        JobWorkItem item2 = new JobWorkItem.Builder().setExtras(bundle).build();
+        item2.bumpDeliveryCount();
+        JobWorkItem item3 = new JobWorkItem.Builder().setEstimatedNetworkBytes(1, 2).build();
+        JobWorkItem item4 = new JobWorkItem.Builder().setMinimumNetworkChunkBytes(3).build();
+        JobWorkItem item5 = new JobWorkItem.Builder().build();
+
+        JobInfo jobInfo = new JobInfo.Builder(0, mComponent)
+                .setPersisted(true)
+                .build();
+        JobStatus jobStatus =
+                JobStatus.createFromJobInfo(jobInfo, SOME_UID, null, -1, null, null);
+        jobStatus.executingWork = new ArrayList<>(List.of(item1, item2));
+        jobStatus.pendingWork = new ArrayList<>(List.of(item3, item4, item5));
+        assertPersistedEquals(jobStatus);
+    }
+
     /**
      * Helper function to kick a {@link JobInfo} through a persistence cycle and
      * assert that it's unchanged.
      */
     private void assertPersistedEquals(JobInfo firstInfo) throws Exception {
+        assertPersistedEquals(
+                JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null));
+    }
+
+    private void assertPersistedEquals(JobStatus original) throws Exception {
         mTaskStoreUnderTest.clear();
-        JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null);
-        mTaskStoreUnderTest.add(first);
+        mTaskStoreUnderTest.add(original);
         waitForPendingIo();
 
         final JobSet jobStatusSet = new JobSet();
         mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
         final JobStatus second = jobStatusSet.getAllJobs().iterator().next();
-        assertJobsEqual(first, second);
+        assertJobsEqual(original, second);
     }
 
     /**
@@ -693,6 +753,8 @@
         assertEquals("Calling UID not equal", expected.getUid(), actual.getUid());
         assertEquals("Calling user not equal", expected.getUserId(), actual.getUserId());
 
+        assertEquals(expected.getNamespace(), actual.getNamespace());
+
         assertEquals("Internal flags not equal",
                 expected.getInternalFlags(), actual.getInternalFlags());
 
@@ -701,6 +763,59 @@
                 expected.getEarliestRunTime(), actual.getEarliestRunTime());
         compareTimestampsSubjectToIoLatency("Late run-times not the same after read.",
                 expected.getLatestRunTimeElapsed(), actual.getLatestRunTimeElapsed());
+
+        assertEquals(expected.hasWorkLocked(), actual.hasWorkLocked());
+        if (expected.hasWorkLocked()) {
+            List<JobWorkItem> allWork = new ArrayList<>();
+            if (expected.executingWork != null) {
+                allWork.addAll(expected.executingWork);
+            }
+            if (expected.pendingWork != null) {
+                allWork.addAll(expected.pendingWork);
+            }
+            // All work for freshly loaded Job will be pending.
+            assertNotNull(actual.pendingWork);
+            assertTrue(ArrayUtils.isEmpty(actual.executingWork));
+            assertEquals(allWork.size(), actual.pendingWork.size());
+            for (int i = 0; i < allWork.size(); ++i) {
+                JobWorkItem expectedItem = allWork.get(i);
+                JobWorkItem actualItem = actual.pendingWork.get(i);
+                assertJobWorkItemsEqual(expectedItem, actualItem);
+            }
+        }
+    }
+
+    private void assertJobWorkItemsEqual(JobWorkItem expected, JobWorkItem actual) {
+        if (expected == null) {
+            assertNull(actual);
+            return;
+        }
+        assertNotNull(actual);
+        assertEquals(expected.getDeliveryCount(), actual.getDeliveryCount());
+        assertEquals(expected.getEstimatedNetworkDownloadBytes(),
+                actual.getEstimatedNetworkDownloadBytes());
+        assertEquals(expected.getEstimatedNetworkUploadBytes(),
+                actual.getEstimatedNetworkUploadBytes());
+        assertEquals(expected.getMinimumNetworkChunkBytes(), actual.getMinimumNetworkChunkBytes());
+        if (expected.getIntent() == null) {
+            assertNull(actual.getIntent());
+        } else {
+            // filterEquals() just so happens to check almost everything that is persisted to disk.
+            assertTrue(expected.getIntent().filterEquals(actual.getIntent()));
+            assertEquals(expected.getIntent().getFlags(), actual.getIntent().getFlags());
+        }
+        assertEquals(expected.getGrants(), actual.getGrants());
+        PersistableBundle expectedExtras = expected.getExtras();
+        PersistableBundle actualExtras = actual.getExtras();
+        if (expectedExtras == null) {
+            assertNull(actualExtras);
+        } else {
+            assertEquals(expectedExtras.size(), actualExtras.size());
+            Set<String> keys = expectedExtras.keySet();
+            for (String key : keys) {
+                assertTrue(Objects.equals(expectedExtras.get(key), actualExtras.get(key)));
+            }
+        }
     }
 
     /**
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index b7faf22..3268df2 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -28,7 +28,7 @@
 import android.platform.test.annotations.LargeTest;
 import android.util.ArraySet;
 import android.util.Log;
-import android.util.SparseArray;
+import android.util.SparseArrayMap;
 import android.util.SparseBooleanArray;
 import android.util.SparseLongArray;
 
@@ -54,8 +54,13 @@
 
     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
             int callingUid) {
+        return createJobStatus(testTag, jobInfoBuilder, callingUid, "PJQTest");
+    }
+
+    private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
+            int callingUid, String namespace) {
         return JobStatus.createFromJobInfo(
-                jobInfoBuilder.build(), callingUid, "com.android.test", 0, testTag);
+                jobInfoBuilder.build(), callingUid, "com.android.test", 0, namespace, testTag);
     }
 
     @Test
@@ -373,12 +378,12 @@
         jobQueue.add(rC10);
         jobQueue.add(eC11);
 
-        checkPendingJobInvariants(jobQueue);
         JobStatus job;
         final JobStatus[] expectedPureOrder = new JobStatus[]{
                 eC3, rD4, eE5, eB6, rB2, eA7, rA1, rH8, eF9, rF8, eC11, rC10, rG12, rG13, eE14};
         int idx = 0;
         jobQueue.setOptimizeIteration(false);
+        checkPendingJobInvariants(jobQueue);
         jobQueue.resetIterator();
         while ((job = jobQueue.next()) != null) {
             assertEquals("List wasn't correctly sorted @ index " + idx,
@@ -390,6 +395,93 @@
                 eC3, eC11, rD4, eE5, eE14, eB6, rB2, eA7, rA1, rH8, eF9, rF8, rC10, rG12, rG13};
         idx = 0;
         jobQueue.setOptimizeIteration(true);
+        checkPendingJobInvariants(jobQueue);
+        jobQueue.resetIterator();
+        while ((job = jobQueue.next()) != null) {
+            assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
+                    expectedOptimizedOrder[idx].getJobId(), job.getJobId());
+            idx++;
+        }
+    }
+
+    @Test
+    public void testPendingJobSorting_namespacing() {
+        PendingJobQueue jobQueue = new PendingJobQueue();
+
+        // First letter in job variable name indicate regular (r) or expedited (e).
+        // Capital letters in job variable name indicate the app/UID.
+        // Third letter (x, y, z) indicates the namespace.
+        // Numbers in job variable name indicate the enqueue time.
+        // Expected sort order:
+        //   eCx3 > rDx4 > eBy6 > rBy2 > eAy7 > rAx1 > eCy8 > rEz9 > rEz5
+        // Intentions:
+        //   * A jobs test expedited is before regular, regardless of namespace
+        //   * B jobs test expedited is before regular, in the same namespace
+        //   * C jobs test sorting by priority with different namespaces
+        //   * E jobs test sorting by priority in the same namespace
+        final String namespaceX = null;
+        final String namespaceY = "y";
+        final String namespaceZ = "z";
+        JobStatus rAx1 = createJobStatus("testPendingJobSorting",
+                createJobInfo(1), 1, namespaceX);
+        JobStatus rBy2 = createJobStatus("testPendingJobSorting",
+                createJobInfo(2), 2, namespaceY);
+        JobStatus eCx3 = createJobStatus("testPendingJobSorting",
+                createJobInfo(3).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH),
+                3, namespaceX);
+        JobStatus rDx4 = createJobStatus("testPendingJobSorting",
+                createJobInfo(4), 4, namespaceX);
+        JobStatus rEz5 = createJobStatus("testPendingJobSorting",
+                createJobInfo(5).setPriority(JobInfo.PRIORITY_LOW), 5, namespaceZ);
+        JobStatus eBy6 = createJobStatus("testPendingJobSorting",
+                createJobInfo(6).setExpedited(true), 2, namespaceY);
+        JobStatus eAy7 = createJobStatus("testPendingJobSorting",
+                createJobInfo(7).setExpedited(true), 1, namespaceY);
+        JobStatus eCy8 = createJobStatus("testPendingJobSorting",
+                createJobInfo(8).setExpedited(true).setPriority(JobInfo.PRIORITY_MAX),
+                3, namespaceY);
+        JobStatus rEz9 = createJobStatus("testPendingJobSorting",
+                createJobInfo(9).setPriority(JobInfo.PRIORITY_HIGH), 5, namespaceZ);
+
+        rAx1.enqueueTime = 10;
+        rBy2.enqueueTime = 20;
+        eCx3.enqueueTime = 30;
+        rDx4.enqueueTime = 40;
+        rEz5.enqueueTime = 50;
+        eBy6.enqueueTime = 60;
+        eAy7.enqueueTime = 70;
+        eCy8.enqueueTime = 80;
+        rEz9.enqueueTime = 90;
+
+        // Add in random order so sorting is apparent.
+        jobQueue.add(rEz9);
+        jobQueue.add(eCy8);
+        jobQueue.add(rDx4);
+        jobQueue.add(rEz5);
+        jobQueue.add(rBy2);
+        jobQueue.add(rAx1);
+        jobQueue.add(eCx3);
+        jobQueue.add(eBy6);
+        jobQueue.add(eAy7);
+
+        JobStatus job;
+        final JobStatus[] expectedPureOrder = new JobStatus[]{
+                eCx3, rDx4, eBy6, rBy2, eAy7, rAx1, eCy8, rEz9, rEz5};
+        int idx = 0;
+        jobQueue.setOptimizeIteration(false);
+        checkPendingJobInvariants(jobQueue);
+        jobQueue.resetIterator();
+        while ((job = jobQueue.next()) != null) {
+            assertEquals("List wasn't correctly sorted @ index " + idx,
+                    expectedPureOrder[idx].getJobId(), job.getJobId());
+            idx++;
+        }
+
+        final JobStatus[] expectedOptimizedOrder = new JobStatus[]{
+                eCx3, eCy8, rDx4, eBy6, rBy2, eAy7, rAx1, rEz9, rEz5};
+        idx = 0;
+        jobQueue.setOptimizeIteration(true);
+        checkPendingJobInvariants(jobQueue);
         jobQueue.resetIterator();
         while ((job = jobQueue.next()) != null) {
             assertEquals("Optimized list wasn't correctly sorted @ index " + idx,
@@ -414,6 +506,22 @@
     }
 
     @Test
+    public void testPendingJobSorting_Random_namespacing() {
+        PendingJobQueue jobQueue = new PendingJobQueue();
+        Random random = new Random(1); // Always use the same series of pseudo random values.
+
+        for (int i = 0; i < 5000; ++i) {
+            JobStatus job = createJobStatus("testPendingJobSorting_Random",
+                    createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250),
+                    "namespace" + random.nextInt(5));
+            job.enqueueTime = random.nextInt(1_000_000);
+            jobQueue.add(job);
+        }
+
+        checkPendingJobInvariants(jobQueue);
+    }
+
+    @Test
     public void testPendingJobSortingTransitivity() {
         PendingJobQueue jobQueue = new PendingJobQueue();
         // Always use the same series of pseudo random values.
@@ -546,10 +654,11 @@
 
     private void checkPendingJobInvariants(PendingJobQueue jobQueue) {
         final SparseBooleanArray regJobSeen = new SparseBooleanArray();
-        // Latest priority enqueue times seen for each priority for each app.
-        final SparseArray<SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
-                new SparseArray<>();
-        final SparseArray<SparseLongArray> latestPriorityEjEnqueueTimesPerUid = new SparseArray<>();
+        // Latest priority enqueue times seen for each priority+namespace for each app.
+        final SparseArrayMap<String, SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
+                new SparseArrayMap();
+        final SparseArrayMap<String, SparseLongArray> latestPriorityEjEnqueueTimesPerUid =
+                new SparseArrayMap<>();
         final int noEntry = -1;
         int prevOverrideState = noEntry;
 
@@ -579,11 +688,12 @@
             }
 
             final int priority = job.getEffectivePriority();
-            final SparseArray<SparseLongArray> latestPriorityEnqueueTimesPerUid =
+            final SparseArrayMap<String, SparseLongArray> latestPriorityEnqueueTimesPerUid =
                     job.isRequestedExpeditedJob()
                             ? latestPriorityEjEnqueueTimesPerUid
                             : latestPriorityRegEnqueueTimesPerUid;
-            SparseLongArray latestPriorityEnqueueTimes = latestPriorityEnqueueTimesPerUid.get(uid);
+            SparseLongArray latestPriorityEnqueueTimes =
+                    latestPriorityEnqueueTimesPerUid.get(uid, job.getNamespace());
             if (latestPriorityEnqueueTimes != null) {
                 // Invariant 2
                 for (int p = priority - 1; p >= JobInfo.PRIORITY_MIN; --p) {
@@ -603,7 +713,8 @@
                 }
             } else {
                 latestPriorityEnqueueTimes = new SparseLongArray();
-                latestPriorityEnqueueTimesPerUid.put(uid, latestPriorityEnqueueTimes);
+                latestPriorityEnqueueTimesPerUid.add(
+                        uid, job.getNamespace(), latestPriorityEnqueueTimes);
             }
             latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
 
@@ -618,7 +729,7 @@
     }
 
     private static String testJobToString(JobStatus job) {
-        return "testJob " + job.getSourceUid() + "/" + job.getJobId()
+        return "testJob " + job.getSourceUid() + "/" + job.getNamespace() + "/" + job.getJobId()
                 + "/o" + job.overrideState
                 + "/p" + job.getEffectivePriority()
                 + "/b" + job.lastEvaluatedBias
diff --git a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
index 6cdae53..56df9d9 100644
--- a/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
+++ b/services/tests/servicestests/src/com/android/server/locales/FakePackageConfigurationUpdater.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import android.annotation.Nullable;
 import android.os.LocaleList;
 
@@ -29,6 +31,8 @@
 
     FakePackageConfigurationUpdater() {}
 
+    private int mGender = GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
     LocaleList mLocales = null;
 
     @Override
@@ -43,6 +47,12 @@
     }
 
     @Override
+    public PackageConfigurationUpdater setGrammaticalGender(int gender) {
+        mGender = gender;
+        return this;
+    }
+
+    @Override
     public boolean commit() {
         return mLocales != null;
     }
@@ -56,4 +66,10 @@
         return mLocales;
     }
 
+    /**
+     * Returns the gender that were stored during the test run.
+     */
+    int getGender() {
+        return mGender;
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index 79ed7d1..065aec5 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -234,7 +236,8 @@
             throws Exception {
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
         String imPkgName = getCurrentInputMethodPackageName();
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
@@ -274,7 +277,8 @@
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
-        doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null))
+        doReturn(new PackageConfig(/* nightMode = */ 0, /* locales = */ null,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(any(), anyInt());
 
         LocaleList locales = mLocaleManagerService.getApplicationLocales(
@@ -288,7 +292,8 @@
             throws Exception {
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -303,7 +308,8 @@
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
         setUpPassingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -319,7 +325,8 @@
                 .getPackageUidAsUser(eq(DEFAULT_PACKAGE_NAME), any(), anyInt());
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
                 .getPackageUidAsUser(eq(DEFAULT_INSTALLER_PACKAGE_NAME), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
 
         LocaleList locales =
@@ -334,7 +341,8 @@
             throws Exception {
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
-        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES,
+                GRAMMATICAL_GENDER_NOT_SPECIFIED))
                 .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
         String imPkgName = getCurrentInputMethodPackageName();
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
index cbf555e..494796e 100644
--- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.locales;
 
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
+
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
@@ -168,7 +170,8 @@
             /* isUpdatedSystemApp = */ true))
             .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
         doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
-                DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+                        DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+                .when(mMockActivityTaskManager)
                 .getApplicationConfig(anyString(), anyInt());
 
         mPackageMonitor.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
@@ -186,7 +189,8 @@
             /* isUpdatedSystemApp = */ true))
             .when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
         doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
-                DEFAULT_LOCALES)).when(mMockActivityTaskManager)
+                        DEFAULT_LOCALES, GRAMMATICAL_GENDER_NOT_SPECIFIED))
+                .when(mMockActivityTaskManager)
                 .getApplicationConfig(anyString(), anyInt());
 
         // first update
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 d3b647d..f0f0632 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -154,7 +154,7 @@
                 storageManager, spManager, gsiService, recoverableKeyStoreManager,
                 userManagerInternal, deviceStateCache));
         mGateKeeperService = gatekeeper;
-        mAuthSecretServiceAidl = authSecretService;
+        mAuthSecretService = authSecretService;
     }
 
     @Override
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
index 95d0e15..03d5b17 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsStorageTests.java
@@ -389,11 +389,11 @@
 
     @Test
     public void testPersistentData_serializeUnserialize() {
-        byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP, SOME_USER_ID,
+        byte[] serialized = PersistentData.toBytes(PersistentData.TYPE_SP_GATEKEEPER, SOME_USER_ID,
                 DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, PAYLOAD);
         PersistentData deserialized = PersistentData.fromBytes(serialized);
 
-        assertEquals(PersistentData.TYPE_SP, deserialized.type);
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, deserialized.type);
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_COMPLEX, deserialized.qualityForUi);
         assertArrayEquals(PAYLOAD, deserialized.payload);
     }
@@ -424,13 +424,13 @@
         // the wire format in the future.
         byte[] serializedVersion1 = new byte[] {
                 1, /* PersistentData.VERSION_1 */
-                1, /* PersistentData.TYPE_SP */
+                1, /* PersistentData.TYPE_SP_GATEKEEPER */
                 0x00, 0x00, 0x04, 0x0A,  /* SOME_USER_ID */
                 0x00, 0x03, 0x00, 0x00,  /* PASSWORD_NUMERIC_COMPLEX */
                 1, 2, -1, -2, 33, /* PAYLOAD */
         };
         PersistentData deserialized = PersistentData.fromBytes(serializedVersion1);
-        assertEquals(PersistentData.TYPE_SP, deserialized.type);
+        assertEquals(PersistentData.TYPE_SP_GATEKEEPER, deserialized.type);
         assertEquals(SOME_USER_ID, deserialized.userId);
         assertEquals(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX,
                 deserialized.qualityForUi);
@@ -438,7 +438,7 @@
 
         // Make sure the constants we use on the wire do not change.
         assertEquals(0, PersistentData.TYPE_NONE);
-        assertEquals(1, PersistentData.TYPE_SP);
+        assertEquals(1, PersistentData.TYPE_SP_GATEKEEPER);
         assertEquals(2, PersistentData.TYPE_SP_WEAVER);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
index 8f6dd5d..10f27ca 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayActorEnforcerTests.kt
@@ -18,12 +18,11 @@
 
 import android.content.om.OverlayInfo
 import android.content.om.OverlayableInfo
-import android.content.pm.ApplicationInfo
-import android.content.pm.PackageInfo
 import android.os.Process
 import android.util.ArrayMap
 import com.android.server.om.OverlayActorEnforcer.ActorState
 import com.android.server.pm.pkg.AndroidPackage
+import com.android.server.pm.pkg.PackageState
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
@@ -31,7 +30,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
-import org.mockito.Mockito
 import org.mockito.Mockito.spy
 import java.io.IOException
 
@@ -126,13 +124,11 @@
          */
         private val CASES = listOf(
                 ActorState.TARGET_NOT_FOUND withCases {
-                    failure("nullPkgInfo") { targetPkgInfo = null }
+                    failure("nullPkgInfo") { targetPkgState = null }
                     allowed("debuggable") {
-                        targetPkgInfo = androidPackage(TARGET_PKG).apply {
-                            whenever(this.isDebuggable).thenReturn(true)
-                        }
+                        targetPkgState = packageState(TARGET_PKG, debuggable = true)
                     }
-                    skip { targetPkgInfo = androidPackage(TARGET_PKG) }
+                    skip { targetPkgState = packageState(TARGET_PKG) }
                 },
                 ActorState.NO_PACKAGES_FOR_UID withCases {
                     failure("empty") { callingUid = EMPTY_UID }
@@ -240,21 +236,15 @@
                     }
                 },
                 ActorState.ACTOR_NOT_FOUND withCases {
-                    failure("nullActorPkgInfo") { actorPkgInfo = null }
+                    failure("nullActorPkgInfo") { actorPkgState = null }
                     failure("nullActorAppInfo") {
-                        actorPkgInfo = null
+                        actorPkgState = null
                     }
-                    skip { actorPkgInfo = androidPackage(VALID_ACTOR_PKG) }
+                    skip { actorPkgState = packageState(VALID_ACTOR_PKG) }
                 },
                 ActorState.ACTOR_NOT_PREINSTALLED withCases {
-                    failure("notSystem") {
-                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG)
-                    }
-                    skip {
-                        actorPkgInfo = androidPackage(VALID_ACTOR_PKG).apply {
-                            whenever(this.isSystem).thenReturn(true)
-                        }
-                    }
+                    failure("notSystem") { actorPkgState = packageState(VALID_ACTOR_PKG) }
+                    skip { actorPkgState = packageState(VALID_ACTOR_PKG, isSystem = true) }
                 },
                 ActorState.INVALID_ACTOR withCases {
                     failure("invalidUid") { callingUid = INVALID_ACTOR_UID }
@@ -285,10 +275,18 @@
         private infix fun ActorState.withCases(block: TestCase.() -> Unit) =
                 TestCase(this).apply(block)
 
-        private fun androidPackage(pkgName: String): AndroidPackage = mockThrowOnUnmocked {
+        private fun packageState(
+            pkgName: String,
+            debuggable: Boolean = false,
+            isSystem: Boolean = false
+        ) = mockThrowOnUnmocked<PackageState> {
             whenever(this.packageName).thenReturn(pkgName)
-            whenever(this.isDebuggable).thenReturn(false)
-            whenever(this.isSystem).thenReturn(false)
+            whenever(this.isSystem).thenReturn(isSystem)
+            val androidPackage = mockThrowOnUnmocked<AndroidPackage> {
+                whenever(this.packageName).thenReturn(pkgName)
+                whenever(this.isDebuggable).thenReturn(debuggable)
+            }
+            whenever(this.androidPackage).thenReturn(androidPackage)
         }
 
         private fun makeTestName(testCase: TestCase, caseName: String, type: Params.Type): String {
@@ -364,8 +362,8 @@
         var namedActorsMap: Map<String, Map<String, String>> = emptyMap(),
         var hasPermission: Boolean = false,
         var targetOverlayableInfo: OverlayableInfo? = null,
-        var targetPkgInfo: AndroidPackage? = null,
-        var actorPkgInfo: AndroidPackage? = null,
+        var targetPkgState: PackageState? = null,
+        var actorPkgState: PackageState? = null,
         vararg val packageNames: String = arrayOf("com.test.actor.one")
     ) : PackageManagerHelper {
 
@@ -380,7 +378,7 @@
             throw UnsupportedOperationException()
         }
 
-        override fun initializeForUser(userId: Int): ArrayMap<String, AndroidPackage> {
+        override fun initializeForUser(userId: Int): ArrayMap<String, PackageState> {
             throw UnsupportedOperationException()
         }
 
@@ -391,7 +389,7 @@
             userId: Int
         ) = targetOverlayableInfo?.takeIf {
             // Protect against this method being called with the wrong package name
-            targetPkgInfo == null || targetPkgInfo?.packageName == packageName
+            targetPkgState == null || targetPkgState?.packageName == packageName
         }
 
         override fun getPackagesForUid(uid: Int) = when (uid) {
@@ -407,7 +405,7 @@
         override fun doesTargetDefineOverlayable(targetPackageName: String?, userId: Int): Boolean {
             return targetOverlayableInfo?.takeIf {
                 // Protect against this method being called with the wrong package name
-                targetPkgInfo == null || targetPkgInfo?.packageName == targetPackageName
+                targetPkgState == null || targetPkgState?.packageName == targetPackageName
             } != null
         }
 
@@ -417,8 +415,8 @@
             }
         }
 
-        override fun getPackageForUser(packageName: String, userId: Int) =
-            listOfNotNull(targetPkgInfo, actorPkgInfo).find { it.packageName == packageName }
+        override fun getPackageStateForUser(packageName: String, userId: Int) =
+            listOfNotNull(targetPkgState, actorPkgState).find { it.packageName == packageName }
 
         override fun getConfigSignaturePackage(): String {
             throw UnsupportedOperationException()
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index dab4335..3e82d45 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -40,6 +40,8 @@
 
 import com.android.internal.content.om.OverlayConfig;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.AndroidPackageSplit;
+import com.android.server.pm.pkg.PackageState;
 
 import org.junit.Assert;
 import org.junit.Before;
@@ -357,18 +359,24 @@
             }
 
             @Nullable
-            private AndroidPackage getPackageForUser(int user) {
+            private PackageState getPackageForUser(int user) {
                 if (!installedUserIds.contains(user)) {
                     return null;
                 }
                 final AndroidPackage pkg = Mockito.mock(AndroidPackage.class);
                 when(pkg.getPackageName()).thenReturn(packageName);
-                when(pkg.getBaseApkPath()).thenReturn(apkPath);
                 when(pkg.getLongVersionCode()).thenReturn((long) versionCode);
                 when(pkg.getOverlayTarget()).thenReturn(targetPackageName);
                 when(pkg.getOverlayTargetOverlayableName()).thenReturn(targetOverlayableName);
                 when(pkg.getOverlayCategory()).thenReturn("Fake-category-" + targetPackageName);
-                return pkg;
+                var baseSplit = mock(AndroidPackageSplit.class);
+                when(baseSplit.getPath()).thenReturn(apkPath);
+                when(pkg.getSplits()).thenReturn(List.of(baseSplit));
+
+                var pkgState = Mockito.mock(PackageState.class);
+                when(pkgState.getPackageName()).thenReturn(packageName);
+                when(pkgState.getAndroidPackage()).thenReturn(pkg);
+                return pkgState;
             }
         }
     }
@@ -382,10 +390,10 @@
 
         @NonNull
         @Override
-        public ArrayMap<String, AndroidPackage> initializeForUser(int userId) {
-            final ArrayMap<String, AndroidPackage> packages = new ArrayMap<>();
+        public ArrayMap<String, PackageState> initializeForUser(int userId) {
+            final ArrayMap<String, PackageState> packages = new ArrayMap<>();
             mState.mPackages.forEach((key, value) -> {
-                final AndroidPackage pkg = value.getPackageForUser(userId);
+                final PackageState pkg = value.getPackageForUser(userId);
                 if (pkg != null) {
                     packages.put(key, pkg);
                 }
@@ -395,7 +403,7 @@
 
         @Nullable
         @Override
-        public AndroidPackage getPackageForUser(@NonNull String packageName, int userId) {
+        public PackageState getPackageStateForUser(@NonNull String packageName, int userId) {
             final FakeDeviceState.Package pkgState = mState.select(packageName, userId);
             return pkgState == null ? null : pkgState.getPackageForUser(userId);
         }
@@ -466,7 +474,7 @@
 
         private int getCrc(@NonNull final String path) {
             final FakeDeviceState.Package pkg = mState.selectFromPath(path);
-            Assert.assertNotNull(pkg);
+            Assert.assertNotNull("path = " + path, pkg);
             return pkg.versionCode;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index eb91671..77bdf19 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -28,11 +28,12 @@
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.junit.MockitoJUnitRunner;
 
 
@@ -52,78 +53,90 @@
 
     @Test
     public void getSeInfoOptInToLatest() {
-        AndroidPackage pkg = makePackage(Build.VERSION_CODES.P);
+        var packageState = makePackageState(Build.VERSION_CODES.P);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + LATEST_OPT_IN_VERSION));
     }
 
     @Test
     public void getSeInfoOptInToR() {
-        AndroidPackage pkg = makePackage(Build.VERSION_CODES.P);
+        var packageState = makePackageState(Build.VERSION_CODES.P);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + R_OPT_IN_VERSION));
     }
 
     @Test
     public void getSeInfoNoOptIn() {
-        AndroidPackage pkg = makePackage(Build.VERSION_CODES.P);
+        var packageState = makePackageState(Build.VERSION_CODES.P);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=28"));
     }
 
     @Test
     public void getSeInfoNoOptInButAlreadyLatest() {
-        AndroidPackage pkg = makePackage(LATEST_OPT_IN_VERSION);
+        var packageState = makePackageState(LATEST_OPT_IN_VERSION);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + LATEST_OPT_IN_VERSION));
     }
 
     @Test
     public void getSeInfoTargetingCurDevelopment() {
-        AndroidPackage pkg = makePackage(Build.VERSION_CODES.CUR_DEVELOPMENT);
+        var packageState = makePackageState(Build.VERSION_CODES.CUR_DEVELOPMENT);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + Build.VERSION_CODES.CUR_DEVELOPMENT));
     }
 
     @Test
     public void getSeInfoNoOptInButAlreadyR() {
-        AndroidPackage pkg = makePackage(R_OPT_IN_VERSION);
+        var packageState = makePackageState(R_OPT_IN_VERSION);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(false);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + R_OPT_IN_VERSION));
     }
 
     @Test
     public void getSeInfoOptInRButLater() {
-        AndroidPackage pkg = makePackage(R_OPT_IN_VERSION + 1);
+        var packageState = makePackageState(R_OPT_IN_VERSION + 1);
         when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES),
-                argThat(argument -> argument.packageName.equals(pkg.getPackageName()))))
+                argThat(argument -> argument.packageName.equals(packageState.getPackageName()))))
                 .thenReturn(true);
-        assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility),
+        assertThat(SELinuxMMAC.getSeInfo(packageState, packageState.getAndroidPackage(), null,
+                        mMockCompatibility),
                 is("default:targetSdkVersion=" + (R_OPT_IN_VERSION + 1)));
     }
 
-    private AndroidPackage makePackage(int targetSdkVersion) {
-        return ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
-                .setTargetSdkVersion(targetSdkVersion)
-                .hideAsParsed())
-                .hideAsFinal();
+    private PackageState makePackageState(int targetSdkVersion) {
+        var packageState = Mockito.mock(PackageState.class);
+        when(packageState.getPackageName()).thenReturn(PACKAGE_NAME);
+        when(packageState.getAndroidPackage()).thenReturn(
+                ((ParsedPackage) PackageImpl.forTesting(PACKAGE_NAME)
+                        .setTargetSdkVersion(targetSdkVersion)
+                        .hideAsParsed())
+                        .hideAsFinal()
+        );
+        return packageState;
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
index 94a8ed3..bbe8907 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserLifecycleStressTest.java
@@ -41,6 +41,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -108,8 +109,8 @@
             switchUser(USER_SYSTEM);
         }
 
-        final UserInfo foundGuest = mUserManager.findCurrentGuestUser();
-        int nextGuestId = foundGuest == null ? USER_NULL : foundGuest.id;
+        final List<UserInfo> guestUsers = mUserManager.getGuestUsers();
+        int nextGuestId = guestUsers.isEmpty() ? USER_NULL : guestUsers.get(0).id;
 
         for (int i = 0; i < NUM_ITERATIONS; i++) {
             final int currentGuestId = nextGuestId;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 810b294..5059ef3 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -619,6 +619,30 @@
 
     @MediumTest
     @Test
+    public void testRevokeUserAdmin() throws Exception {
+        UserInfo userInfo = createUser("Admin", /*flags=*/ UserInfo.FLAG_ADMIN);
+        assertThat(userInfo.isAdmin()).isTrue();
+
+        mUserManager.revokeUserAdmin(userInfo.id);
+
+        userInfo = mUserManager.getUserInfo(userInfo.id);
+        assertThat(userInfo.isAdmin()).isFalse();
+    }
+
+    @MediumTest
+    @Test
+    public void testRevokeUserAdminFromNonAdmin() throws Exception {
+        UserInfo userInfo = createUser("NonAdmin", /*flags=*/ 0);
+        assertThat(userInfo.isAdmin()).isFalse();
+
+        mUserManager.revokeUserAdmin(userInfo.id);
+
+        userInfo = mUserManager.getUserInfo(userInfo.id);
+        assertThat(userInfo.isAdmin()).isFalse();
+    }
+
+    @MediumTest
+    @Test
     public void testGetProfileParent() throws Exception {
         assumeManagedUsersSupported();
         final int primaryUserId = mUserManager.getPrimaryUser().id;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
index 7e7a434..319a280 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
@@ -32,6 +32,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class AmbientDisplayPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final long MINUTE_IN_MS = 60 * 1000;
@@ -46,19 +47,19 @@
         mStatsRule.initMeasuredEnergyStatsLocked();
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
 
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000},
                 new int[]{Display.STATE_ON}, 0);
 
         stats.noteScreenStateLocked(0, Display.STATE_DOZE, 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
                 30 * MINUTE_IN_MS);
 
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200_000_000},
                 new int[]{Display.STATE_DOZE}, 30 * MINUTE_IN_MS);
 
         stats.noteScreenStateLocked(0, Display.STATE_OFF, 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
                 120 * MINUTE_IN_MS);
 
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000},
                 new int[]{Display.STATE_OFF}, 120 * MINUTE_IN_MS);
 
         AmbientDisplayPowerCalculator calculator =
@@ -73,7 +74,7 @@
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(27.777778);
         assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -88,13 +89,13 @@
 
         stats.noteScreenStateLocked(0, screenStates[0], 0, 0, 0);
         stats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0);
 
         // Switch display0 to doze
         screenStates[0] = Display.STATE_DOZE;
         stats.noteScreenStateLocked(0, screenStates[0], 30 * MINUTE_IN_MS, 30 * MINUTE_IN_MS,
                 30 * MINUTE_IN_MS);
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200, 300},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{200, 300},
                 screenStates, 30 * MINUTE_IN_MS);
 
         // Switch display1 to doze
@@ -102,7 +103,7 @@
         stats.noteScreenStateLocked(1, Display.STATE_DOZE, 90 * MINUTE_IN_MS, 90 * MINUTE_IN_MS,
                 90 * MINUTE_IN_MS);
         // 100,000,000 uC should be attributed to display 0 doze here.
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000, 700_000_000},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000, 700_000_000},
                 screenStates, 90 * MINUTE_IN_MS);
 
         // Switch display0 to off
@@ -110,14 +111,14 @@
         stats.noteScreenStateLocked(0, screenStates[0], 120 * MINUTE_IN_MS, 120 * MINUTE_IN_MS,
                 120 * MINUTE_IN_MS);
         // 40,000,000 and 70,000,000 uC should be attributed to display 0 and 1 doze here.
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{40_000_000, 70_000_000},
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{40_000_000, 70_000_000},
                 screenStates, 120 * MINUTE_IN_MS);
 
         // Switch display1 to off
         screenStates[1] = Display.STATE_OFF;
         stats.noteScreenStateLocked(1, screenStates[1], 150 * MINUTE_IN_MS, 150 * MINUTE_IN_MS,
                 150 * MINUTE_IN_MS);
-        stats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100, 90_000_000}, screenStates,
+        stats.updateDisplayEnergyConsumerStatsLocked(new long[]{100, 90_000_000}, screenStates,
                 150 * MINUTE_IN_MS);
         // 90,000,000 uC should be attributed to display 1 doze here.
 
@@ -134,7 +135,7 @@
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
                 .isWithin(PRECISION).of(83.33333);
         assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_AMBIENT_DISPLAY))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
index c83610d..2ebe215 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
@@ -55,6 +55,7 @@
  * Build/Install/Run:
  * atest FrameworksServicesTests:BatteryExternalStatsWorkerTest
  */
+@SuppressWarnings("GuardedBy")
 public class BatteryExternalStatsWorkerTest {
     private BatteryExternalStatsWorker mBatteryExternalStatsWorker;
     private TestBatteryStatsImpl mBatteryStatsImpl;
@@ -133,7 +134,7 @@
         mBatteryExternalStatsWorker.systemServicesReady();
 
         final EnergyConsumerResult[] displayResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_DISPLAY).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_DISPLAY).getNow(null);
         // Results should only have the cpu cluster energy consumers
         final int[] receivedDisplayIds = new int[displayResults.length];
         for (int i = 0; i < displayResults.length; i++) {
@@ -143,25 +144,25 @@
         assertArrayEquals(displayIds, receivedDisplayIds);
 
         final EnergyConsumerResult[] wifiResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_WIFI).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_WIFI).getNow(null);
         // Results should only have the wifi energy consumer
         assertEquals(1, wifiResults.length);
         assertEquals(wifiId, wifiResults[0].id);
 
         final EnergyConsumerResult[] bluetoothResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_BT).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_BT).getNow(null);
         // Results should only have the bluetooth energy consumer
         assertEquals(1, bluetoothResults.length);
         assertEquals(btId, bluetoothResults[0].id);
 
         final EnergyConsumerResult[] mobileRadioResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_RADIO).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_RADIO).getNow(null);
         // Results should only have the mobile radio energy consumer
         assertEquals(1, mobileRadioResults.length);
         assertEquals(mobileRadioId, mobileRadioResults[0].id);
 
         final EnergyConsumerResult[] cpuResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_CPU).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_CPU).getNow(null);
         // Results should only have the cpu cluster energy consumers
         final int[] receivedCpuIds = new int[cpuResults.length];
         for (int i = 0; i < cpuResults.length; i++) {
@@ -171,7 +172,7 @@
         assertArrayEquals(cpuClusterIds, receivedCpuIds);
 
         final EnergyConsumerResult[] allResults =
-                mBatteryExternalStatsWorker.getMeasuredEnergyLocked(UPDATE_ALL).getNow(null);
+                mBatteryExternalStatsWorker.getEnergyConsumersLocked(UPDATE_ALL).getNow(null);
         // All energy consumer results should be available
         final int[] receivedAllIds = new int[allResults.length];
         for (int i = 0; i < allResults.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
index 22a7e8d..e65229f 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
@@ -27,8 +27,8 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.CpuUsageDetails;
+import android.os.BatteryStats.EnergyConsumerDetails;
 import android.os.BatteryStats.HistoryItem;
-import android.os.BatteryStats.MeasuredEnergyDetails;
 import android.os.Parcel;
 import android.util.Log;
 
@@ -272,33 +272,33 @@
         mHistory.setBatteryState(true /* charging */, BatteryManager.BATTERY_STATUS_CHARGING, 80,
                 1234);
 
-        MeasuredEnergyDetails details = new MeasuredEnergyDetails();
-        MeasuredEnergyDetails.EnergyConsumer consumer1 =
-                new MeasuredEnergyDetails.EnergyConsumer();
+        EnergyConsumerDetails details = new EnergyConsumerDetails();
+        EnergyConsumerDetails.EnergyConsumer consumer1 =
+                new EnergyConsumerDetails.EnergyConsumer();
         consumer1.type = 42;
         consumer1.ordinal = 0;
         consumer1.name = "A";
 
-        MeasuredEnergyDetails.EnergyConsumer consumer2 =
-                new MeasuredEnergyDetails.EnergyConsumer();
+        EnergyConsumerDetails.EnergyConsumer consumer2 =
+                new EnergyConsumerDetails.EnergyConsumer();
         consumer2.type = 777;
         consumer2.ordinal = 0;
         consumer2.name = "B/0";
 
-        MeasuredEnergyDetails.EnergyConsumer consumer3 =
-                new MeasuredEnergyDetails.EnergyConsumer();
+        EnergyConsumerDetails.EnergyConsumer consumer3 =
+                new EnergyConsumerDetails.EnergyConsumer();
         consumer3.type = 777;
         consumer3.ordinal = 1;
         consumer3.name = "B/1";
 
-        MeasuredEnergyDetails.EnergyConsumer consumer4 =
-                new MeasuredEnergyDetails.EnergyConsumer();
+        EnergyConsumerDetails.EnergyConsumer consumer4 =
+                new EnergyConsumerDetails.EnergyConsumer();
         consumer4.type = 314;
         consumer4.ordinal = 1;
         consumer4.name = "C";
 
         details.consumers =
-                new MeasuredEnergyDetails.EnergyConsumer[]{consumer1, consumer2, consumer3,
+                new EnergyConsumerDetails.EnergyConsumer[]{consumer1, consumer2, consumer3,
                         consumer4};
         details.chargeUC = new long[details.consumers.length];
         for (int i = 0; i < details.chargeUC.length; i++) {
@@ -306,7 +306,7 @@
         }
         details.chargeUC[3] = BatteryStats.POWER_DATA_UNAVAILABLE;
 
-        mHistory.recordMeasuredEnergyDetails(200, 200, details);
+        mHistory.recordEnergyConsumerDetails(200, 200, details);
 
         BatteryStatsHistoryIterator iterator = mHistory.iterate();
         BatteryStats.HistoryItem item;
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 773a2dc..998d22e 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
@@ -57,7 +57,7 @@
 
 import com.android.internal.os.BatteryStatsHistoryIterator;
 import com.android.internal.os.PowerProfile;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 import com.android.server.power.stats.BatteryStatsImpl.DualTimer;
 
 import junit.framework.TestCase;
@@ -73,15 +73,6 @@
 
 /**
  * Test various BatteryStatsImpl noteStart methods.
- *
- * Build/Install/Run: bit FrameworksCoreTests:BatteryStatsNoteTest
- *
- * Alternatively,
- * Build: m FrameworksCoreTests
- * Install: adb install -r \
- *      ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk
- * Run: adb shell am instrument -e class BatteryStatsNoteTest -w \
- *      com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
  */
 @SuppressWarnings("GuardedBy")
 public class BatteryStatsNoteTest extends TestCase {
@@ -1153,7 +1144,7 @@
         // Case A: uid1 off, uid2 off, battery off, screen off
         bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
         bi.setOnBatteryInternal(battery);
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{500_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{500_000}, screen, clocks.realtime);
         checkMeasuredCharge("A", uid1, blame1, uid2, blame2, globalDoze, bi);
 
         // Case B: uid1 off, uid2 off, battery ON,  screen off
@@ -1162,24 +1153,24 @@
         bi.updateTimeBasesLocked(battery, screen[0], clocks.realtime * 1000, 0);
         bi.setOnBatteryInternal(battery);
         clocks.realtime += 19;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{510_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{510_000}, screen, clocks.realtime);
         checkMeasuredCharge("B", uid1, blame1, uid2, blame2, globalDoze, bi);
 
         // Case C: uid1 ON,  uid2 off, battery on,  screen off
         clocks.realtime += 18;
         setFgState(uid1, true, bi);
         clocks.realtime += 18;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{520_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{520_000}, screen, clocks.realtime);
         checkMeasuredCharge("C", uid1, blame1, uid2, blame2, globalDoze, bi);
 
         // Case D: uid1 on,  uid2 off, battery on,  screen ON
         clocks.realtime += 17;
         screen[0] = Display.STATE_ON;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{521_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{521_000}, screen, clocks.realtime);
         blame1 += 0; // Screen had been off during the measurement period
         checkMeasuredCharge("D.1", uid1, blame1, uid2, blame2, globalDoze, bi);
         clocks.realtime += 101;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{530_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{530_000}, screen, clocks.realtime);
         blame1 += 530_000;
         checkMeasuredCharge("D.2", uid1, blame1, uid2, blame2, globalDoze, bi);
 
@@ -1187,7 +1178,7 @@
         clocks.realtime += 20;
         setFgState(uid2, true, bi);
         clocks.realtime += 40;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{540_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{540_000}, screen, clocks.realtime);
         // In the past 60ms, sum of fg is 20+40+40=100ms. uid1 is blamed for 60/100; uid2 for 40/100
         blame1 += 540_000 * (20 + 40) / (20 + 40 + 40);
         blame2 += 540_000 * (0 + 40) / (20 + 40 + 40);
@@ -1197,7 +1188,7 @@
         clocks.realtime += 40;
         setFgState(uid2, false, bi);
         clocks.realtime += 120;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{550_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{550_000}, screen, clocks.realtime);
         // In the past 160ms, sum f fg is 200ms. uid1 is blamed for 40+120 of it; uid2 for 40 of it.
         blame1 += 550_000 * (40 + 120) / (40 + 40 + 120);
         blame2 += 550_000 * (40 + 0) / (40 + 40 + 120);
@@ -1206,14 +1197,14 @@
         // Case G: uid1 on,  uid2 off,  battery on, screen DOZE
         clocks.realtime += 5;
         screen[0] = Display.STATE_DOZE;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{570_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{570_000}, screen, clocks.realtime);
         blame1 += 570_000; // All of this pre-doze time is blamed on uid1.
         checkMeasuredCharge("G", uid1, blame1, uid2, blame2, globalDoze, bi);
 
         // Case H: uid1 on,  uid2 off,  battery on, screen ON
         clocks.realtime += 6;
         screen[0] = Display.STATE_ON;
-        bi.updateDisplayMeasuredEnergyStatsLocked(new long[]{580_000}, screen, clocks.realtime);
+        bi.updateDisplayEnergyConsumerStatsLocked(new long[]{580_000}, screen, clocks.realtime);
         blame1 += 0; // The screen had been doze during the energy period
         globalDoze += 580_000;
         checkMeasuredCharge("H", uid1, blame1, uid2, blame2, globalDoze, bi);
@@ -1262,11 +1253,11 @@
 
         newChargesA.put(uid1, 20_000);
         // Implicit newChargesA.put(uid2, 0);
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketA, 500_000, newChargesA);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketA, 500_000, newChargesA);
 
         newChargesB.put(uid1, 60_000);
         // Implicit newChargesB.put(uid2, 0);
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketB, 700_000, newChargesB);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketB, 700_000, newChargesB);
 
         checkCustomBatteryConsumption(
                 "A", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
@@ -1277,12 +1268,12 @@
 
         newChargesA.put(uid1, 7_000); blame1A += 7_000;
         // Implicit newChargesA.put(uid2, 0); blame2A += 0;
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketA, 310_000, newChargesA);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketA, 310_000, newChargesA);
         totalBlameA += 310_000;
 
         newChargesB.put(uid1, 63_000); blame1B += 63_000;
         newChargesB.put(uid2, 15_000); blame2B += 15_000;
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketB, 790_000, newChargesB);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketB, 790_000, newChargesB);
         totalBlameB += 790_000;
 
         checkCustomBatteryConsumption(
@@ -1292,10 +1283,10 @@
         // ----- Case C: battery still on
         newChargesA.delete(uid1); blame1A += 0;
         newChargesA.put(uid2, 16_000); blame2A += 16_000;
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketA, 560_000, newChargesA);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketA, 560_000, newChargesA);
         totalBlameA += 560_000;
 
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketB, 10_000, null);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketB, 10_000, null);
         totalBlameB += 10_000;
 
         checkCustomBatteryConsumption(
@@ -1303,8 +1294,8 @@
 
 
         // ----- Case D: battery still on
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketA, 0, newChargesA);
-        bi.updateCustomMeasuredEnergyStatsLocked(bucketB, 15_000, new SparseLongArray(1));
+        bi.updateCustomEnergyConsumerStatsLocked(bucketA, 0, newChargesA);
+        bi.updateCustomEnergyConsumerStatsLocked(bucketB, 15_000, new SparseLongArray(1));
         totalBlameB += 15_000;
         checkCustomBatteryConsumption(
                 "D", totalBlameA, totalBlameB, uid1, blame1A, blame1B, uid2, blame2A, blame2B, bi);
@@ -2156,19 +2147,19 @@
 
     private void checkMeasuredCharge(String caseName, int uid1, long blame1, int uid2, long blame2,
             long globalDoze, MockBatteryStatsImpl bi) {
-        final int bucket = MeasuredEnergyStats.POWER_BUCKET_SCREEN_ON;
+        final int bucket = EnergyConsumerStats.POWER_BUCKET_SCREEN_ON;
 
         assertEquals("Wrong uid1 blame for Case " + caseName, blame1,
-                bi.getUidStatsLocked(uid1).getMeasuredBatteryConsumptionUC(bucket));
+                bi.getUidStatsLocked(uid1).getEnergyConsumptionUC(bucket));
 
         assertEquals("Wrong uid2 blame for Case " + caseName, blame2,
-                bi.getUidStatsLocked(uid2).getMeasuredBatteryConsumptionUC(bucket));
+                bi.getUidStatsLocked(uid2).getEnergyConsumptionUC(bucket));
 
         assertEquals("Wrong total blame for Case " + caseName, blame1 + blame2,
-                bi.getScreenOnMeasuredBatteryConsumptionUC());
+                bi.getScreenOnEnergyConsumptionUC());
 
         assertEquals("Wrong doze for Case " + caseName, globalDoze,
-                bi.getScreenDozeMeasuredBatteryConsumptionUC());
+                bi.getScreenDozeEnergyConsumptionUC());
     }
 
     private void checkCustomBatteryConsumption(String caseName,
@@ -2177,11 +2168,11 @@
             int uid2, long blame2A, long blame2B,
             MockBatteryStatsImpl bi) {
 
-        final long[] actualTotal = bi.getCustomConsumerMeasuredBatteryConsumptionUC();
+        final long[] actualTotal = bi.getCustomEnergyConsumerBatteryConsumptionUC();
         final long[] actualUid1 =
-                bi.getUidStatsLocked(uid1).getCustomConsumerMeasuredBatteryConsumptionUC();
+                bi.getUidStatsLocked(uid1).getCustomEnergyConsumerBatteryConsumptionUC();
         final long[] actualUid2 =
-                bi.getUidStatsLocked(uid2).getCustomConsumerMeasuredBatteryConsumptionUC();
+                bi.getUidStatsLocked(uid2).getCustomEnergyConsumerBatteryConsumptionUC();
 
         assertNotNull(actualTotal);
         assertNotNull(actualUid1);
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
index 807df47..48290e5 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
@@ -50,14 +50,14 @@
         BstatsCpuTimesValidationTest.class,
         CameraPowerCalculatorTest.class,
         CpuPowerCalculatorTest.class,
-        CustomMeasuredPowerCalculatorTest.class,
+        CustomEnergyConsumerPowerCalculatorTest.class,
         FlashlightPowerCalculatorTest.class,
         GnssPowerCalculatorTest.class,
         IdlePowerCalculatorTest.class,
         KernelWakelockReaderTest.class,
         LongSamplingCounterTest.class,
         LongSamplingCounterArrayTest.class,
-        MeasuredEnergySnapshotTest.class,
+        EnergyConsumerSnapshotTest.class,
         MobileRadioPowerCalculatorTest.class,
         ScreenPowerCalculatorTest.class,
         SensorPowerCalculatorTest.class,
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 7c1962c..bf2faac 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
@@ -35,7 +35,7 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.os.PowerProfile;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
@@ -146,11 +146,13 @@
     public BatteryUsageStatsRule initMeasuredEnergyStatsLocked(
             String[] customPowerComponentNames) {
         final boolean[] supportedStandardBuckets =
-                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
         Arrays.fill(supportedStandardBuckets, true);
-        mBatteryStats.initMeasuredEnergyStatsLocked(supportedStandardBuckets,
-                customPowerComponentNames);
-        mBatteryStats.informThatAllExternalStatsAreFlushed();
+        synchronized (mBatteryStats) {
+            mBatteryStats.initEnergyConsumerStatsLocked(supportedStandardBuckets,
+                    customPowerComponentNames);
+            mBatteryStats.informThatAllExternalStatsAreFlushed();
+        }
         return this;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
index e603ea5..266a226 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
@@ -17,7 +17,7 @@
 package com.android.server.power.stats;
 
 import static android.os.BatteryConsumer.POWER_COMPONENT_ANY;
-import static android.os.BatteryConsumer.POWER_MODEL_MEASURED_ENERGY;
+import static android.os.BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION;
 import static android.os.BatteryConsumer.POWER_MODEL_UNDEFINED;
 import static android.os.BatteryConsumer.PROCESS_STATE_BACKGROUND;
 import static android.os.BatteryConsumer.PROCESS_STATE_CACHED;
@@ -313,7 +313,7 @@
 
         addUidBatteryConsumer(builder, batteryStats, APP_UID1, null,
                 4321, 5432,
-                123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_MEASURED_ENERGY,
+                123, BatteryConsumer.POWER_MODEL_POWER_PROFILE, 345, POWER_MODEL_ENERGY_CONSUMPTION,
                 456, 567, 678,
                 1777, 7771, 1888, 8881, 1999, 9991, 321, 654);
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
index 76dd0fd..4d4337c1 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
@@ -193,16 +193,16 @@
 
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(Process.BLUETOOTH_UID),
-                0.10378, 3583, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                0.10378, 3583, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getUidBatteryConsumer(APP_UID),
-                0.22950, 8416, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                0.22950, 8416, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getDeviceBatteryConsumer(),
-                0.33333, 12000, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                0.33333, 12000, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertBluetoothPowerAndDuration(
                 mStatsRule.getAppsBatteryConsumer(),
-                0.33329, 11999, BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                0.33329, 11999, BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -256,7 +256,7 @@
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
                 .isWithin(PRECISION).of(0.8220561);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_BLUETOOTH))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_BLUETOOTH,
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
index 9ad6a7c..ced996f3 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
@@ -42,7 +42,7 @@
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.LongArrayMultiStateCounter;
 import com.android.internal.os.PowerProfile;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -100,8 +100,8 @@
         MockitoAnnotations.initMocks(this);
 
         final boolean[] supportedPowerBuckets =
-                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
-        supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
+        supportedPowerBuckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
 
         when(mMockCpuUidFreqTimeReader.isFastCpuTimesReader()).thenReturn(true);
 
@@ -114,7 +114,7 @@
                 .setKernelCpuUidActiveTimeReader(mMockKerneCpuUidActiveTimeReader)
                 .setKernelSingleUidTimeReader(mMockKernelSingleUidTimeReader)
                 .setSystemServerCpuThreadReader(mMockSystemServerCpuThreadReader)
-                .initMeasuredEnergyStatsLocked(supportedPowerBuckets, new String[0]);
+                .initEnergyConsumerStatsLocked(supportedPowerBuckets, new String[0]);
     }
 
     @Test
@@ -257,7 +257,7 @@
         assertThat(uidConsumer1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(3.18877);
         assertThat(uidConsumer1.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(uidConsumer1.getPackageWithHighestDrain()).isEqualTo("bar");
 
         UidBatteryConsumer uidConsumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
@@ -266,20 +266,20 @@
         assertThat(uidConsumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(7.44072);
         assertThat(uidConsumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
         assertThat(uidConsumer2.getPackageWithHighestDrain()).isNull();
 
         final BatteryConsumer deviceBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(10.62949);
         assertThat(deviceBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer appsBatteryConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(appsBatteryConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_CPU))
                 .isWithin(PRECISION).of(10.62949);
         assertThat(appsBatteryConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_CPU))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CustomMeasuredPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
similarity index 90%
rename from services/tests/servicestests/src/com/android/server/power/stats/CustomMeasuredPowerCalculatorTest.java
rename to services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
index 552b4f7..245faaf 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CustomMeasuredPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
@@ -32,7 +32,8 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class CustomMeasuredPowerCalculatorTest {
+@SuppressWarnings("GuardedBy")
+public class CustomEnergyConsumerPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
 
     private static final int APP_UID = Process.FIRST_APPLICATION_UID + 42;
@@ -50,13 +51,13 @@
 
         SparseLongArray uidEnergies = new SparseLongArray();
         uidEnergies.put(APP_UID, 30_000_000);
-        batteryStats.updateCustomMeasuredEnergyStatsLocked(0, 100_000_000, uidEnergies);
+        batteryStats.updateCustomEnergyConsumerStatsLocked(0, 100_000_000, uidEnergies);
 
         uidEnergies.put(APP_UID, 120_000_000);
-        batteryStats.updateCustomMeasuredEnergyStatsLocked(1, 200_000_000, uidEnergies);
+        batteryStats.updateCustomEnergyConsumerStatsLocked(1, 200_000_000, uidEnergies);
 
-        CustomMeasuredPowerCalculator calculator =
-                new CustomMeasuredPowerCalculator(mStatsRule.getPowerProfile());
+        CustomEnergyConsumerPowerCalculator calculator =
+                new CustomEnergyConsumerPowerCalculator(mStatsRule.getPowerProfile());
 
         mStatsRule.apply(calculator);
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
similarity index 92%
rename from services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java
rename to services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
index 122f7eb..558f396 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MeasuredEnergySnapshotTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
@@ -32,18 +32,18 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.server.power.stats.MeasuredEnergySnapshot.MeasuredEnergyDeltaData;
+import com.android.server.power.stats.EnergyConsumerSnapshot.EnergyConsumerDeltaData;
 
 import org.junit.Test;
 
 /**
- * Test class for {@link MeasuredEnergySnapshot}.
+ * Test class for {@link EnergyConsumerSnapshot}.
  *
  * To run the tests, use
  * atest FrameworksServicesTests:com.android.server.power.stats.MeasuredEnergySnapshotTest
  */
 @SmallTest
-public final class MeasuredEnergySnapshotTest {
+public final class EnergyConsumerSnapshotTest {
     private static final EnergyConsumer CONSUMER_DISPLAY = createEnergyConsumer(
             0, 0, EnergyConsumerType.DISPLAY, "Display");
     private static final  EnergyConsumer CONSUMER_OTHER_0 = createEnergyConsumer(
@@ -107,17 +107,17 @@
 
     @Test
     public void testUpdateAndGetDelta_empty() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
         assertNull(snapshot.updateAndGetDelta(null, VOLTAGE_0));
         assertNull(snapshot.updateAndGetDelta(new EnergyConsumerResult[0], VOLTAGE_0));
     }
 
     @Test
     public void testUpdateAndGetDelta() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
 
         // results0
-        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
+        EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
         if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
             assertNull(delta.displayChargeUC);
             assertNull(delta.otherTotalChargeUC);
@@ -204,10 +204,10 @@
     /** Test updateAndGetDelta() when the results have consumers absent from idToConsumerMap. */
     @Test
     public void testUpdateAndGetDelta_some() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(SOME_ID_CONSUMER_MAP);
 
         // results0
-        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
+        EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
         if (delta != null) { // null is fine here. If non-null, it better be uninteresting though.
             assertNull(delta.displayChargeUC);
             assertNull(delta.otherTotalChargeUC);
@@ -226,23 +226,23 @@
 
     @Test
     public void testGetOtherOrdinalNames() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
         assertThat(snapshot.getOtherOrdinalNames()).asList()
                 .containsExactly("GPU", "HPU", "IPU &_");
     }
 
     @Test
     public void testGetOtherOrdinalNames_none() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(SOME_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(SOME_ID_CONSUMER_MAP);
         assertEquals(0, snapshot.getOtherOrdinalNames().length);
     }
 
     @Test
     public void getMeasuredEnergyDetails() {
-        final MeasuredEnergySnapshot snapshot = new MeasuredEnergySnapshot(ALL_ID_CONSUMER_MAP);
+        final EnergyConsumerSnapshot snapshot = new EnergyConsumerSnapshot(ALL_ID_CONSUMER_MAP);
         snapshot.updateAndGetDelta(RESULTS_0, VOLTAGE_0);
-        MeasuredEnergyDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1);
-        BatteryStats.MeasuredEnergyDetails details = snapshot.getMeasuredEnergyDetails(delta);
+        EnergyConsumerDeltaData delta = snapshot.updateAndGetDelta(RESULTS_1, VOLTAGE_1);
+        BatteryStats.EnergyConsumerDetails details = snapshot.getEnergyConsumerDetails(delta);
         assertThat(details.consumers).hasLength(4);
         assertThat(details.chargeUC).isEqualTo(new long[]{2667, 3200000, 0, 0});
         assertThat(details.toString()).isEqualTo("DISPLAY=2667 HPU=3200000 GPU=0 IPU &_=0");
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
index 5bc4311..3f2a6d0 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
@@ -33,6 +33,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class GnssPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
 
@@ -89,7 +90,7 @@
         uidStats2.noteStopGps(5000);
 
         BatteryStatsImpl stats = mStatsRule.getBatteryStats();
-        stats.updateGnssMeasuredEnergyStatsLocked(30_000_000, 6000);
+        stats.updateGnssEnergyConsumerStatsLocked(30_000_000, 6000);
 
         GnssPowerCalculator calculator =
                 new GnssPowerCalculator(mStatsRule.getPowerProfile());
@@ -102,7 +103,7 @@
         assertThat(consumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(2.77777);
         assertThat(consumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer consumer2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(consumer2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_GNSS))
@@ -110,18 +111,18 @@
         assertThat(consumer2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(5.55555);
         assertThat(consumer2.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(8.333333);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_GNSS))
                 .isWithin(PRECISION).of(8.333333);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_GNSS))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
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 5bc73bd..65e6486 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
@@ -527,7 +527,7 @@
         stats.noteModemControllerActivity(null, POWER_DATA_UNAVAILABLE, 12000, 12000,
                 mNetworkStatsManager);
 
-        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC()).isAtMost(0);
+        assertThat(uid.getMobileRadioEnergyConsumptionUC()).isAtMost(0);
         // 12000-8000 = 4000 ms == 4_000_000 us
         assertThat(uid.getMobileRadioActiveTimeInProcessState(BatteryConsumer.PROCESS_STATE_ANY))
                 .isEqualTo(4_000_000);
@@ -622,19 +622,19 @@
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(2.77778);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
                 .isWithin(PRECISION).of(1.53934);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         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);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -687,15 +687,15 @@
 
         mStatsRule.setTime(20000, 20000);
 
-        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC())
+        assertThat(uid.getMobileRadioEnergyConsumptionUC())
                 .isIn(Range.open(20_000_000L, 21_000_000L));
-        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+        assertThat(uid.getMobileRadioEnergyConsumptionUC(
                 BatteryConsumer.PROCESS_STATE_FOREGROUND))
                 .isIn(Range.open(13_000_000L, 14_000_000L));
-        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+        assertThat(uid.getMobileRadioEnergyConsumptionUC(
                 BatteryConsumer.PROCESS_STATE_BACKGROUND))
                 .isIn(Range.open(7_000_000L, 8_000_000L));
-        assertThat(uid.getMobileRadioMeasuredBatteryConsumptionUC(
+        assertThat(uid.getMobileRadioEnergyConsumptionUC(
                 BatteryConsumer.PROCESS_STATE_FOREGROUND_SERVICE))
                 .isEqualTo(0);
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
index df4b896..19d2639 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
@@ -35,7 +35,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.PowerProfile;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -78,11 +78,13 @@
 
     public void initMeasuredEnergyStats(String[] customBucketNames) {
         final boolean[] supportedStandardBuckets =
-                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
+                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
         Arrays.fill(supportedStandardBuckets, true);
-        mMeasuredEnergyStatsConfig = new MeasuredEnergyStats.Config(supportedStandardBuckets,
-                customBucketNames, new int[0], new String[]{""});
-        mGlobalMeasuredEnergyStats = new MeasuredEnergyStats(mMeasuredEnergyStatsConfig);
+        synchronized (this) {
+            mEnergyConsumerStatsConfig = new EnergyConsumerStats.Config(supportedStandardBuckets,
+                    customBucketNames, new int[0], new String[]{""});
+            mGlobalEnergyConsumerStats = new EnergyConsumerStats(mEnergyConsumerStatsConfig);
+        }
     }
 
     public TimeBase getOnBatteryTimeBase() {
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
index 36faae4..3723079 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
@@ -36,6 +36,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@SuppressWarnings("GuardedBy")
 public class ScreenPowerCalculatorTest {
     private static final double PRECISION = 0.00001;
     private static final int APP_UID1 = Process.FIRST_APPLICATION_UID + 42;
@@ -56,12 +57,12 @@
         BatteryStatsImpl batteryStats = mStatsRule.getBatteryStats();
 
         batteryStats.noteScreenStateLocked(0, Display.STATE_ON, 0, 0, 0);
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{0},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{0},
                 new int[]{Display.STATE_ON}, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true,
                 0, 0);
 
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{200_000_000},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{200_000_000},
                 new int[]{Display.STATE_ON}, 15 * MINUTE_IN_MS);
 
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_CACHED_EMPTY, false,
@@ -70,7 +71,7 @@
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP, true,
                 20 * MINUTE_IN_MS, 20 * MINUTE_IN_MS);
 
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300_000_000},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300_000_000},
                 new int[]{Display.STATE_ON}, 60 * MINUTE_IN_MS);
 
         batteryStats.noteScreenStateLocked(0, Display.STATE_OFF,
@@ -78,7 +79,7 @@
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
 
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{100_000_000},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{100_000_000},
                 new int[]{Display.STATE_DOZE}, 120 * MINUTE_IN_MS);
 
         mStatsRule.setTime(120 * MINUTE_IN_US, 120 * MINUTE_IN_US);
@@ -98,7 +99,7 @@
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(64.81481);
         assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -110,7 +111,7 @@
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(101.85185);
         assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -120,7 +121,7 @@
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(166.66666);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -129,7 +130,7 @@
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(166.66666);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
 
@@ -148,7 +149,7 @@
         batteryStats.noteScreenStateLocked(1, screenStates[1], 0, 0, 0);
         batteryStats.noteScreenBrightnessLocked(0, 255, 0, 0);
         setProcState(APP_UID1, ActivityManager.PROCESS_STATE_TOP, true, 0, 0);
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{300, 400}, screenStates, 0);
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{300, 400}, screenStates, 0);
 
         batteryStats.noteScreenBrightnessLocked(0, 100, 5 * MINUTE_IN_MS, 5 * MINUTE_IN_MS);
         batteryStats.noteScreenBrightnessLocked(0, 200, 10 * MINUTE_IN_MS, 10 * MINUTE_IN_MS);
@@ -164,7 +165,7 @@
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
         batteryStats.noteScreenStateLocked(1, screenStates[1], 80 * MINUTE_IN_MS,
                 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{600_000_000, 500},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{600_000_000, 500},
                 screenStates, 80 * MINUTE_IN_MS);
 
         batteryStats.noteScreenBrightnessLocked(1, 25, 80 * MINUTE_IN_MS, 80 * MINUTE_IN_MS);
@@ -174,7 +175,7 @@
         screenStates[1] = Display.STATE_OFF;
         batteryStats.noteScreenStateLocked(1, screenStates[1], 110 * MINUTE_IN_MS,
                 110 * MINUTE_IN_MS, 110 * MINUTE_IN_MS);
-        batteryStats.updateDisplayMeasuredEnergyStatsLocked(new long[]{700, 800_000_000},
+        batteryStats.updateDisplayEnergyConsumerStatsLocked(new long[]{700, 800_000_000},
                 screenStates, 110 * MINUTE_IN_MS);
 
         setProcState(APP_UID2, ActivityManager.PROCESS_STATE_TOP_SLEEPING, false,
@@ -194,7 +195,7 @@
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(388.88888);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid1 = mStatsRule.getUidBatteryConsumer(APP_UID1);
         assertThat(uid1.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -206,7 +207,7 @@
         assertThat(uid1.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(41.66666);
         assertThat(uid1.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         UidBatteryConsumer uid2 = mStatsRule.getUidBatteryConsumer(APP_UID2);
         assertThat(uid2.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -218,7 +219,7 @@
         assertThat(uid2.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(347.22222);
         assertThat(uid2.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getAppsBatteryConsumer();
         assertThat(appsConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_SCREEN))
@@ -226,7 +227,7 @@
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_SCREEN))
                 .isWithin(PRECISION).of(388.88888);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_SCREEN))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
index 037ac78..3c5c0a3 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
@@ -37,7 +37,7 @@
 import com.android.internal.os.KernelCpuUidTimeReader;
 import com.android.internal.os.KernelSingleUidTimeReader;
 import com.android.internal.os.PowerProfile;
-import com.android.internal.power.MeasuredEnergyStats;
+import com.android.internal.power.EnergyConsumerStats;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -137,10 +137,10 @@
     @Test
     public void testMeasuredEnergyBasedModel() {
         final boolean[] supportedPowerBuckets =
-                new boolean[MeasuredEnergyStats.NUMBER_STANDARD_POWER_BUCKETS];
-        supportedPowerBuckets[MeasuredEnergyStats.POWER_BUCKET_CPU] = true;
+                new boolean[EnergyConsumerStats.NUMBER_STANDARD_POWER_BUCKETS];
+        supportedPowerBuckets[EnergyConsumerStats.POWER_BUCKET_CPU] = true;
         mStatsRule.getBatteryStats()
-                .initMeasuredEnergyStatsLocked(supportedPowerBuckets, new String[0]);
+                .initEnergyConsumerStatsLocked(supportedPowerBuckets, new String[0]);
 
         prepareBatteryStats(new long[]{50000000, 100000000});
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
index b6f3036..113be8b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
@@ -204,7 +204,7 @@
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.2214666 / (0.2214666 + 0.645200) * 1_000_000 / 3600000);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer deviceConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(deviceConsumer.getUsageDurationMillis(BatteryConsumer.POWER_COMPONENT_WIFI))
@@ -212,13 +212,13 @@
         assertThat(deviceConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.27777);
         assertThat(deviceConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         BatteryConsumer appsConsumer = mStatsRule.getDeviceBatteryConsumer();
         assertThat(appsConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.277777);
         assertThat(appsConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 
     @Test
@@ -258,7 +258,7 @@
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(1.0325211);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
 
         final BatteryConsumer.Key foreground = uidConsumer.getKey(
                 BatteryConsumer.POWER_COMPONENT_WIFI,
@@ -327,6 +327,6 @@
         assertThat(uidConsumer.getConsumedPower(BatteryConsumer.POWER_COMPONENT_WIFI))
                 .isWithin(PRECISION).of(0.8231573 / (0.8231573 + 0.8759216) * 1_000_000 / 3600000);
         assertThat(uidConsumer.getPowerModel(BatteryConsumer.POWER_COMPONENT_WIFI))
-                .isEqualTo(BatteryConsumer.POWER_MODEL_MEASURED_ENERGY);
+                .isEqualTo(BatteryConsumer.POWER_MODEL_ENERGY_CONSUMPTION);
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
index b16ca8b..b4a294d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenPolicyTest.java
@@ -19,6 +19,7 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import android.os.Parcel;
 import android.service.notification.ZenPolicy;
 import android.service.notification.nano.DNDPolicyProto;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -32,9 +33,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class ZenPolicyTest extends UiServiceTestCase {
+    private static final String CLASS = "android.service.notification.ZenPolicy";
 
     @Test
     public void testZenPolicyApplyAllowedToDisallowed() {
@@ -524,6 +529,66 @@
         assertProtoMatches(policy, policy.toProto());
     }
 
+    @Test
+    public void testTooLongLists_fromParcel() {
+        ArrayList<Integer> longList = new ArrayList<Integer>(50);
+        for (int i = 0; i < 50; i++) {
+            longList.add(ZenPolicy.STATE_UNSET);
+        }
+
+        ZenPolicy.Builder builder = new ZenPolicy.Builder();
+        ZenPolicy policy = builder.build();
+
+        try {
+            Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+                    "mPriorityCategories");
+            priorityCategories.setAccessible(true);
+            priorityCategories.set(policy, longList);
+
+            Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+            visualEffects.setAccessible(true);
+            visualEffects.set(policy, longList);
+        } catch (NoSuchFieldException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } catch (IllegalAccessException e) {
+            fail(e.toString());
+        }
+
+        Parcel parcel = Parcel.obtain();
+        policy.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        ZenPolicy fromParcel = ZenPolicy.CREATOR.createFromParcel(parcel);
+
+        // Confirm that all the fields are accessible and UNSET
+        assertAllPriorityCategoriesUnsetExcept(fromParcel, -1);
+        assertAllVisualEffectsUnsetExcept(fromParcel, -1);
+
+        // Because we don't access the lists directly, we also need to use reflection to make sure
+        // the lists are the right length.
+        try {
+            Field priorityCategories = Class.forName(CLASS).getDeclaredField(
+                    "mPriorityCategories");
+            priorityCategories.setAccessible(true);
+            ArrayList<Integer> pcList = (ArrayList<Integer>) priorityCategories.get(fromParcel);
+            assertEquals(ZenPolicy.NUM_PRIORITY_CATEGORIES, pcList.size());
+
+
+            Field visualEffects = Class.forName(CLASS).getDeclaredField("mVisualEffects");
+            visualEffects.setAccessible(true);
+            ArrayList<Integer> veList = (ArrayList<Integer>) visualEffects.get(fromParcel);
+            assertEquals(ZenPolicy.NUM_VISUAL_EFFECTS, veList.size());
+        } catch (NoSuchFieldException e) {
+            fail(e.toString());
+        } catch (ClassNotFoundException e) {
+            fail(e.toString());
+        } catch (IllegalAccessException e) {
+            fail(e.toString());
+        }
+    }
+
     private void assertAllPriorityCategoriesUnsetExcept(ZenPolicy policy, int except) {
         if (except != ZenPolicy.PRIORITY_CATEGORY_REMINDERS) {
             assertEquals(ZenPolicy.STATE_UNSET, policy.getPriorityCategoryReminders());
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java
new file mode 100644
index 0000000..3646f1a
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.MediumTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+
+/**
+ * Tests for the {@link ActivityInterceptorCallbackRegistry} class.
+ */
+@Presubmit
+@MediumTest
+@RunWith(WindowTestRunner.class)
+public final class ActivityInterceptorCallbackRegistryTest extends WindowTestsBase {
+
+    private ActivityInterceptorCallbackRegistry mRegistry;
+
+    @Before
+    public void setUp() {
+        mRegistry = spy(ActivityInterceptorCallbackRegistry.getInstance());
+        Mockito.doReturn(Process.SYSTEM_UID).when(mRegistry).getCallingUid();
+    }
+
+    @Test
+    public void registerActivityInterceptorCallbackFailIfNotSystemId() {
+        // default registry with test app uid
+        ActivityInterceptorCallbackRegistry registry = spy(
+                ActivityInterceptorCallbackRegistry.getInstance());
+        assertThrows(
+                SecurityException.class,
+                () ->  registry.registerActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1,
+                        info -> null)
+        );
+    }
+
+    @Test
+    public void registerActivityInterceptorCallbackFailIfIdNotInRange() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mRegistry.registerActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1,
+                        info -> null)
+        );
+
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mRegistry.registerActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID - 1,
+                        info -> null)
+        );
+    }
+
+    @Test
+    public void registerActivityInterceptorCallbackFailIfCallbackIsNull() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mRegistry.registerActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID,
+                        null)
+        );
+    }
+
+    @Test
+    public void registerActivityInterceptorCallbackSuccessfully() {
+        int size = mAtm.getActivityInterceptorCallbacks().size();
+        int orderId = MAINLINE_FIRST_ORDERED_ID;
+        mRegistry.registerActivityInterceptorCallback(orderId,
+                info -> null);
+        assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size());
+        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId));
+    }
+
+    @Test
+    public void unregisterActivityInterceptorCallbackFailIfNotSystemId() {
+        // default registry with test app uid
+        ActivityInterceptorCallbackRegistry registry = spy(
+                ActivityInterceptorCallbackRegistry.getInstance());
+        assertThrows(
+                SecurityException.class,
+                () ->  registry.unregisterActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1)
+        );
+    }
+
+    @Test
+    public void unRegisterActivityInterceptorCallbackFailIfIdNotInRange() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mRegistry.unregisterActivityInterceptorCallback(
+                        MAINLINE_LAST_ORDERED_ID + 1));
+    }
+
+    @Test
+    public void unregisterActivityInterceptorCallbackFailIfNotRegistered() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mRegistry.unregisterActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID)
+        );
+    }
+
+    @Test
+    public void unregisterActivityInterceptorCallbackSuccessfully() {
+        int size = mAtm.getActivityInterceptorCallbacks().size();
+        int orderId = MAINLINE_FIRST_ORDERED_ID;
+        mRegistry.registerActivityInterceptorCallback(orderId,
+                info -> null);
+        assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size());
+        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId));
+
+        mRegistry.unregisterActivityInterceptorCallback(orderId);
+        assertEquals(size, mAtm.getActivityInterceptorCallbacks().size());
+        assertFalse(mAtm.getActivityInterceptorCallbacks().contains(orderId));
+
+    }
+}
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 b0489da..4ee87d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3288,7 +3288,7 @@
         assertFalse(app2.mActivityRecord.mImeInsetsFrozenUntilStartInput);
         verify(app2.mClient, atLeastOnce()).resized(any(), anyBoolean(), any(),
                 insetsStateCaptor.capture(), anyBoolean(), anyBoolean(), anyInt(), anyInt(),
-                anyInt());
+                anyBoolean());
         assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 8a15c30..7d16fb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
 import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
 import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
 
@@ -25,15 +26,19 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
@@ -68,6 +73,7 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 /**
@@ -353,16 +359,24 @@
 
     public void addMockInterceptorCallback(
             @Nullable Intent intent, @Nullable ActivityOptions activityOptions) {
+        addMockInterceptorCallback(intent, activityOptions, false);
+    }
+
+    public void addMockInterceptorCallback(
+            @Nullable Intent intent, @Nullable ActivityOptions activityOptions,
+            boolean skipResolving) {
         int size = mActivityInterceptorCallbacks.size();
         mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() {
             @Override
-            public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+            public ActivityInterceptResult onInterceptActivityLaunch(@NonNull
+                    ActivityInterceptorInfo info) {
                 if (intent == null && activityOptions == null) {
                     return null;
                 }
                 return new ActivityInterceptResult(
-                        intent != null ? intent : info.intent,
-                        activityOptions != null ? activityOptions : info.checkedOptions);
+                        intent != null ? intent : info.getIntent(),
+                        activityOptions != null ? activityOptions : info.getCheckedOptions(),
+                        skipResolving);
             }
         });
     }
@@ -395,6 +409,30 @@
     }
 
     @Test
+    public void testInterceptionCallback_skipResolving() {
+        addMockInterceptorCallback(
+                new Intent("android.test.foo"),
+                ActivityOptions.makeBasic().setLaunchDisplayId(3), true);
+        ActivityInfo aInfo = mAInfo;
+        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+        assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
+        assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
+        assertEquals(aInfo, mInterceptor.mAInfo); // mAInfo should not be resolved
+    }
+
+    @Test
+    public void testInterceptionCallback_NoSkipResolving() throws InterruptedException {
+        addMockInterceptorCallback(
+                new Intent("android.test.foo"),
+                ActivityOptions.makeBasic().setLaunchDisplayId(3));
+        ActivityInfo aInfo = mAInfo;
+        assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null));
+        assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
+        assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
+        assertNotEquals(aInfo, mInterceptor.mAInfo); // mAInfo should be resolved after intercept
+    }
+
+    @Test
     public void testActivityLaunchedCallback_singleCallback() {
         addMockInterceptorCallback(null, null);
 
@@ -405,4 +443,30 @@
 
         verify(callback, times(1)).onActivityLaunched(any(), any(), any());
     }
+
+    @Test
+    public void testSandboxServiceInterceptionHappensToSandboxedActivityAction()
+            throws InterruptedException {
+
+        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
+        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
+
+        Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+
+        verify(spyCallback, times(1)).onInterceptActivityLaunch(
+                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
+    }
+
+    @Test
+    public void testSandboxServiceInterceptionNotCalledForNotSandboxedActivityAction() {
+        ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
+        mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
+
+        Intent intent = new Intent();
+        mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null);
+
+        verify(spyCallback, never()).onInterceptActivityLaunch(
+                any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
+    }
 }
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 43627f4..5eecb0a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -384,7 +384,7 @@
         doReturn(null).when(mMockPackageManager).resolveIntent(any(), any(), anyLong(), anyLong(),
                 anyInt(), anyBoolean(), anyInt());
         doReturn(null).when(mMockPackageManager).resolveIntentExported(any(), any(),
-                anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt());
+                anyLong(), anyLong(), anyInt(), anyBoolean(), anyInt(), anyInt());
         doReturn(new ComponentName("", "")).when(mMockPackageManager).getSystemUiServiceComponent();
 
         // Never review permissions
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 368b750..3dcae91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -25,8 +25,9 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
-import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
-import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORDERED_ID;
+import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID;
 import static com.android.server.wm.ActivityRecord.State.PAUSED;
 import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -45,6 +46,7 @@
 import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -957,11 +959,12 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testRegisterActivityStartInterceptor_IndexTooSmall() {
-        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1,
+        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID - 1,
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult onInterceptActivityLaunch(
+                            @NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -969,11 +972,12 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testRegisterActivityStartInterceptor_IndexTooLarge() {
-        mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1,
+        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_LAST_ORDERED_ID + 1,
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult onInterceptActivityLaunch(
+                            @NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -981,19 +985,21 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testRegisterActivityStartInterceptor_DuplicateId() {
-        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult onInterceptActivityLaunch(
+                            @NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
-        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult onInterceptActivityLaunch(
+                            @NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
@@ -1003,16 +1009,43 @@
     public void testRegisterActivityStartInterceptor() {
         assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
 
-        mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
+        mAtm.mInternal.registerActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID,
                 new ActivityInterceptorCallback() {
                     @Nullable
                     @Override
-                    public ActivityInterceptResult intercept(ActivityInterceptorInfo info) {
+                    public ActivityInterceptResult onInterceptActivityLaunch(
+                            @NonNull ActivityInterceptorInfo info) {
                         return null;
                     }
                 });
 
         assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
-        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID));
+        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(SYSTEM_FIRST_ORDERED_ID));
+    }
+
+    @Test
+    public void testSystemAndMainlineOrderIdsNotOverlapping() {
+        assertTrue(MAINLINE_FIRST_ORDERED_ID - SYSTEM_LAST_ORDERED_ID > 1);
+    }
+
+    @Test
+    public void testUnregisterActivityStartInterceptor() {
+        int size = mAtm.getActivityInterceptorCallbacks().size();
+        int orderId = SYSTEM_FIRST_ORDERED_ID;
+
+        mAtm.mInternal.registerActivityStartInterceptor(orderId,
+                (ActivityInterceptorCallback) info -> null);
+        assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size());
+        assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId));
+
+        mAtm.mInternal.unregisterActivityStartInterceptor(orderId);
+        assertEquals(size, mAtm.getActivityInterceptorCallbacks().size());
+        assertFalse(mAtm.getActivityInterceptorCallbacks().contains(orderId));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testUnregisterActivityStartInterceptor_IdNotExist() {
+        assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
+        mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 92dd047..4ad8516 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
 import static android.view.Display.INVALID_DISPLAY;
@@ -281,6 +280,64 @@
     }
 
     @Test
+    public void testStartRecording_notifiesCallback() {
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper)
+                .notifyActiveProjectionCapturedContentVisibilityChanged(true);
+    }
+
+    @Test
+    public void testOnVisibleRequestedChanged_notifiesCallback() {
+        // WHEN a recording is ongoing.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // WHEN the child requests a visibility change.
+        boolean isVisibleRequested = true;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper, atLeastOnce())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+        // WHEN the child requests a visibility change.
+        isVisibleRequested = false;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is notified.
+        verify(mMediaProjectionManagerWrapper)
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+    }
+
+    @Test
+    public void testOnVisibleRequestedChanged_noRecording_doesNotNotifyCallback() {
+        // WHEN a recording is not ongoing.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+        // WHEN the child requests a visibility change.
+        boolean isVisibleRequested = true;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is not notified.
+        verify(mMediaProjectionManagerWrapper, never())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+
+        // WHEN the child requests a visibility change.
+        isVisibleRequested = false;
+        mContentRecorder.onVisibleRequestedChanged(isVisibleRequested);
+
+        // THEN the visibility change callback is not notified.
+        verify(mMediaProjectionManagerWrapper, never())
+                .notifyActiveProjectionCapturedContentVisibilityChanged(isVisibleRequested);
+    }
+
+    @Test
     public void testPauseRecording_pausesRecording() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
new file mode 100644
index 0000000..d1234e3
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -0,0 +1,428 @@
+/*
+ * 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.wm;
+
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_90;
+
+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.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+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.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.RefreshCallbackItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.content.ComponentName;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.res.Configuration;
+import android.content.res.Configuration.Orientation;
+import android.hardware.camera2.CameraManager;
+import android.os.Handler;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+import android.view.Surface.Rotation;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Tests for {@link DisplayRotationCompatPolicy}.
+ *
+ * Build/Install/Run:
+ *  atest WmTests:DisplayRotationCompatPolicyTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public final class DisplayRotationCompatPolicyTests extends WindowTestsBase {
+
+    private static final String TEST_PACKAGE_1 = "com.test.package.one";
+    private static final String TEST_PACKAGE_2 = "com.test.package.two";
+    private static final String CAMERA_ID_1 = "camera-1";
+    private static final String CAMERA_ID_2 = "camera-2";
+
+    private CameraManager mMockCameraManager;
+    private Handler mMockHandler;
+    private LetterboxConfiguration mLetterboxConfiguration;
+
+    private DisplayRotationCompatPolicy mDisplayRotationCompatPolicy;
+    private CameraManager.AvailabilityCallback mCameraAvailabilityCallback;
+
+    private ActivityRecord mActivity;
+    private Task mTask;
+
+    @Before
+    public void setUp() throws Exception {
+        mLetterboxConfiguration = mDisplayContent.mWmService.mLetterboxConfiguration;
+        spyOn(mLetterboxConfiguration);
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ anyBoolean()))
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled())
+                .thenReturn(true);
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(true);
+
+        mMockCameraManager = mock(CameraManager.class);
+        doAnswer(invocation -> {
+            mCameraAvailabilityCallback = invocation.getArgument(1);
+            return null;
+        }).when(mMockCameraManager).registerAvailabilityCallback(
+                any(Executor.class), any(CameraManager.AvailabilityCallback.class));
+
+        spyOn(mContext);
+        when(mContext.getSystemService(CameraManager.class)).thenReturn(mMockCameraManager);
+
+        spyOn(mDisplayContent);
+
+        mDisplayContent.setIgnoreOrientationRequest(true);
+
+        mMockHandler = mock(Handler.class);
+
+        when(mMockHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+                invocation -> {
+                    ((Runnable) invocation.getArgument(0)).run();
+                    return null;
+                });
+        mDisplayRotationCompatPolicy = new DisplayRotationCompatPolicy(
+                mDisplayContent, mMockHandler);
+    }
+
+    @Test
+    public void testTreatmentNotEnabled_noForceRotationOrRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ anyBoolean()))
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testTreatmentDisabledViaDeviceConfig_noForceRotationOrRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
+                    /* checkDeviceConfig */ true))
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, mDisplayContent);
+        mActivity.getTask().reparent(organizer.mPrimary, WindowContainer.POSITION_TOP,
+                false /* moveParents */, "test" /* reason */);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertTrue(mActivity.inMultiWindowMode());
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationUnspecified_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationLocked_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_LOCKED);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testOrientationNoSensor_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_NOSENSOR);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testIgnoreOrientationRequestIsFalse_noForceRotationOrRefresh() throws Exception {
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testDisplayNotInternal_noForceRotationOrRefresh() throws Exception {
+        Display display = mDisplayContent.getDisplay();
+        spyOn(display);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        when(display.getType()).thenReturn(Display.TYPE_EXTERNAL);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_WIFI);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_OVERLAY);
+        assertNoForceRotationOrRefresh();
+
+        when(display.getType()).thenReturn(Display.TYPE_VIRTUAL);
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testNoCameraConnection_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testCameraReconnected_forceRotationAndRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+    }
+
+    @Test
+    public void testReconnectedToDifferentCamera_forceRotationAndRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_2, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+        assertActivityRefreshRequested(/* refreshRequested */ true);
+    }
+
+    @Test
+    public void testGetOrientation_cameraConnectionClosed_returnUnspecified() {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraClosed(CAMERA_ID_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+    }
+
+    @Test
+    public void testCameraOpenedForDifferentPackage_noForceRotationOrRefresh() throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_2);
+
+        assertNoForceRotationOrRefresh();
+    }
+
+    @Test
+    public void testGetOrientation_portraitActivity_portraitNaturalOrientation_returnPortrait() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+                /* naturalOrientation */ ORIENTATION_PORTRAIT,
+                /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    @Test
+    public void testGetOrientation_portraitActivity_landscapeNaturalOrientation_returnLandscape() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_PORTRAIT,
+                /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+                /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    @Test
+    public void testGetOrientation_landscapeActivity_portraitNaturalOrientation_returnLandscape() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+                /* naturalOrientation */ ORIENTATION_PORTRAIT,
+                /* expectedOrientation */ SCREEN_ORIENTATION_LANDSCAPE);
+    }
+
+    @Test
+    public void testGetOrientation_landscapeActivity_landscapeNaturalOrientation_returnPortrait() {
+        testGetOrientationForActivityAndNaturalOrientations(
+                /* activityOrientation */ SCREEN_ORIENTATION_LANDSCAPE,
+                /* naturalOrientation */ ORIENTATION_LANDSCAPE,
+                /* expectedOrientation */ SCREEN_ORIENTATION_PORTRAIT);
+    }
+
+    private void testGetOrientationForActivityAndNaturalOrientations(
+            @ScreenOrientation int activityOrientation,
+            @Orientation int naturalOrientation,
+            @ScreenOrientation int expectedOrientation) {
+        configureActivityAndDisplay(activityOrientation, naturalOrientation);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                expectedOrientation);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_displayRotationNotChanging_noRefresh()
+            throws Exception {
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ false);
+
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    @Test
+    public void testOnActivityConfigurationChanging_cycleThroughStopDisabled() throws Exception {
+        when(mLetterboxConfiguration.isCameraCompatRefreshCycleThroughStopEnabled())
+                .thenReturn(false);
+
+        configureActivity(SCREEN_ORIENTATION_PORTRAIT);
+
+        mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1);
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false);
+    }
+
+    private void configureActivity(@ScreenOrientation int activityOrientation) {
+        configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT);
+    }
+
+    private void configureActivityAndDisplay(@ScreenOrientation int activityOrientation,
+            @Orientation int naturalOrientation) {
+
+        mTask = new TaskBuilder(mSupervisor)
+                .setDisplay(mDisplayContent)
+                .build();
+
+        mActivity = new ActivityBuilder(mAtm)
+                .setComponent(new ComponentName(TEST_PACKAGE_1, ".TestActivity"))
+                .setScreenOrientation(activityOrientation)
+                .setTask(mTask)
+                .build();
+
+        spyOn(mActivity.mAtmService.getLifecycleManager());
+        spyOn(mActivity.mLetterboxUiController);
+
+        doReturn(mActivity).when(mDisplayContent).topRunningActivity(anyBoolean());
+        doReturn(naturalOrientation).when(mDisplayContent).getNaturalOrientation();
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested) throws Exception {
+        assertActivityRefreshRequested(refreshRequested, /* cycleThroughStop*/ true);
+    }
+
+    private void assertActivityRefreshRequested(boolean refreshRequested,
+                boolean cycleThroughStop) throws Exception {
+        verify(mActivity.mLetterboxUiController, times(refreshRequested ? 1 : 0))
+                .setIsRefreshAfterRotationRequested(true);
+
+        final ClientTransaction transaction = ClientTransaction.obtain(
+                mActivity.app.getThread(), mActivity.token);
+        transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
+        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false));
+
+        verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
+                .scheduleTransaction(eq(transaction));
+    }
+
+    private void assertNoForceRotationOrRefresh() throws Exception {
+        callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true);
+
+        assertEquals(mDisplayRotationCompatPolicy.getOrientation(),
+                SCREEN_ORIENTATION_UNSPECIFIED);
+        assertActivityRefreshRequested(/* refreshRequested */ false);
+    }
+
+    private void callOnActivityConfigurationChanging(
+            ActivityRecord activity, boolean isDisplayRotationChanging) {
+        mDisplayRotationCompatPolicy.onActivityConfigurationChanging(activity,
+                /* newConfig */ createConfigurationWithDisplayRotation(ROTATION_0),
+                /* newConfig */ createConfigurationWithDisplayRotation(
+                        isDisplayRotationChanging ? ROTATION_90 : ROTATION_0));
+    }
+
+    private static Configuration createConfigurationWithDisplayRotation(@Rotation int rotation) {
+        final Configuration config = new Configuration();
+        config.windowConfiguration.setDisplayRotation(rotation);
+        return config;
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index b45c37f..491f876d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -60,6 +60,7 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
+import android.view.DisplayAddress;
 import android.view.Surface;
 import android.view.WindowManager;
 
@@ -101,6 +102,7 @@
     private static WindowManagerService sMockWm;
     private DisplayContent mMockDisplayContent;
     private DisplayPolicy mMockDisplayPolicy;
+    private DisplayAddress mMockDisplayAddress;
     private Context mMockContext;
     private Resources mMockRes;
     private SensorManager mMockSensorManager;
@@ -1091,9 +1093,11 @@
             when(mMockResolver.acquireProvider(Settings.AUTHORITY))
                     .thenReturn(mFakeSettingsProvider.getIContentProvider());
 
+            mMockDisplayAddress = mock(DisplayAddress.class);
+
             mMockDisplayWindowSettings = mock(DisplayWindowSettings.class);
-            mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayPolicy,
-                    mMockDisplayWindowSettings, mMockContext, new Object());
+            mTarget = new DisplayRotation(sMockWm, mMockDisplayContent, mMockDisplayAddress,
+                    mMockDisplayPolicy, mMockDisplayWindowSettings, mMockContext, new Object());
             reset(sMockWm);
 
             captureObservers();
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
index 1be9de7..51a7e74 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationPersisterTest.java
@@ -67,6 +67,11 @@
                         R.integer.config_letterboxDefaultPositionForHorizontalReachability),
                 () -> mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForVerticalReachability),
+                () -> mContext.getResources().getInteger(
+                        R.integer.config_letterboxDefaultPositionForBookModeReachability),
+                () -> mContext.getResources().getInteger(
+                        R.integer.config_letterboxDefaultPositionForTabletopModeReachability
+                ),
                 mConfigFolder, mPersisterQueue, mQueueState);
         mQueueListener = queueEmpty -> mQueueState.onItemAdded();
         mPersisterQueue.addListener(mQueueListener);
@@ -84,14 +89,15 @@
     @Test
     public void test_whenStoreIsCreated_valuesAreDefaults() {
         final int positionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        false);
         final int defaultPositionForHorizontalReachability =
                 mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForHorizontalReachability);
         Assert.assertEquals(defaultPositionForHorizontalReachability,
                 positionForHorizontalReachability);
         final int positionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
         final int defaultPositionForVerticalReachability =
                 mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForVerticalReachability);
@@ -101,15 +107,16 @@
 
     @Test
     public void test_whenUpdatedWithNewValues_valuesAreWritten() {
-        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
-        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
         waitForCompletion(mPersisterQueue);
         final int newPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        false);
         final int newPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
         Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 newPositionForHorizontalReachability);
         Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -120,24 +127,24 @@
     public void test_whenUpdatedWithNewValues_valuesAreReadAfterRestart() {
         final PersisterQueue firstPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister firstPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, mContext.getFilesDir(), firstPersisterQueue,
-                mQueueState);
+                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
+                firstPersisterQueue, mQueueState);
         firstPersister.start();
-        firstPersister.setLetterboxPositionForHorizontalReachability(
+        firstPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
-        firstPersister.setLetterboxPositionForVerticalReachability(
+        firstPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
         waitForCompletion(firstPersisterQueue);
         stopPersisterSafe(firstPersisterQueue);
         final PersisterQueue secondPersisterQueue = new PersisterQueue();
         final LetterboxConfigurationPersister secondPersister = new LetterboxConfigurationPersister(
-                mContext, () -> -1, () -> -1, mContext.getFilesDir(), secondPersisterQueue,
-                mQueueState);
+                mContext, () -> -1, () -> -1, () -> -1, () -> -1, mContext.getFilesDir(),
+                secondPersisterQueue, mQueueState);
         secondPersister.start();
         final int newPositionForHorizontalReachability =
-                secondPersister.getLetterboxPositionForHorizontalReachability();
+                secondPersister.getLetterboxPositionForHorizontalReachability(false);
         final int newPositionForVerticalReachability =
-                secondPersister.getLetterboxPositionForVerticalReachability();
+                secondPersister.getLetterboxPositionForVerticalReachability(false);
         Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 newPositionForHorizontalReachability);
         Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -149,15 +156,16 @@
 
     @Test
     public void test_whenUpdatedWithNewValuesAndDeleted_valuesAreDefaults() {
-        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(
+        mLetterboxConfigurationPersister.setLetterboxPositionForHorizontalReachability(false,
                 LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT);
-        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(
+        mLetterboxConfigurationPersister.setLetterboxPositionForVerticalReachability(false,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
         waitForCompletion(mPersisterQueue);
         final int newPositionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        false);
         final int newPositionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
         Assert.assertEquals(LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 newPositionForHorizontalReachability);
         Assert.assertEquals(LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
@@ -165,14 +173,15 @@
         deleteConfiguration(mLetterboxConfigurationPersister, mPersisterQueue);
         waitForCompletion(mPersisterQueue);
         final int positionForHorizontalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForHorizontalReachability(
+                        false);
         final int defaultPositionForHorizontalReachability =
                 mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForHorizontalReachability);
         Assert.assertEquals(defaultPositionForHorizontalReachability,
                 positionForHorizontalReachability);
         final int positionForVerticalReachability =
-                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability();
+                mLetterboxConfigurationPersister.getLetterboxPositionForVerticalReachability(false);
         final int defaultPositionForVerticalReachability =
                 mContext.getResources().getInteger(
                         R.integer.config_letterboxDefaultPositionForVerticalReachability);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index c927f9e..e196704 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -26,6 +26,7 @@
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
 
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -39,7 +40,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.function.Consumer;
+import java.util.Arrays;
+import java.util.function.BiConsumer;
 
 @SmallTest
 @Presubmit
@@ -58,20 +60,28 @@
 
     @Test
     public void test_whenReadingValues_storeIsInvoked() {
-        mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability();
-        verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability();
-        mLetterboxConfiguration.getLetterboxPositionForVerticalReachability();
-        verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability();
+        for (boolean halfFoldPose : Arrays.asList(false, true)) {
+            mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose);
+            verify(mLetterboxConfigurationPersister).getLetterboxPositionForHorizontalReachability(
+                    halfFoldPose);
+            mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose);
+            verify(mLetterboxConfigurationPersister).getLetterboxPositionForVerticalReachability(
+                    halfFoldPose);
+        }
     }
 
     @Test
     public void test_whenSettingValues_updateConfigurationIsInvoked() {
-        mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
-        verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
-                anyInt());
-        mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
-        verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
-                anyInt());
+        for (boolean halfFoldPose : Arrays.asList(false, true)) {
+            mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop(
+                    halfFoldPose);
+            verify(mLetterboxConfigurationPersister).setLetterboxPositionForHorizontalReachability(
+                    eq(halfFoldPose), anyInt());
+            mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop(
+                    halfFoldPose);
+            verify(mLetterboxConfigurationPersister).setLetterboxPositionForVerticalReachability(
+                    eq(halfFoldPose), anyInt());
+        }
     }
 
     @Test
@@ -81,33 +91,65 @@
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
         assertForHorizontalMove(
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
         // Starting from left
         assertForHorizontalMove(
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
         assertForHorizontalMove(
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
         // Starting from right
         assertForHorizontalMove(
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
         assertForHorizontalMove(
                 /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
                 /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
+                LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+        // Starting from left - book mode
+        assertForHorizontalMove(
+                /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+                /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+                /* expectedTime */ 1,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
+        assertForHorizontalMove(
+                /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+                /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+                /* expectedTime */ 1,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+        // Starting from right - book mode
+        assertForHorizontalMove(
+                /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+                /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+                /* expectedTime */ 2,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForHorizontalReachabilityToNextRightStop);
+        assertForHorizontalMove(
+                /* from */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT,
+                /* expected */ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+                /* expectedTime */ 2,
+                /* halfFoldPose */ true,
                 LetterboxConfiguration::movePositionForHorizontalReachabilityToNextLeftStop);
     }
 
@@ -118,55 +160,87 @@
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
         assertForVerticalMove(
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
         // Starting from top
         assertForVerticalMove(
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
                 /* expectedTime */ 1,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
         assertForVerticalMove(
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
         // Starting from bottom
         assertForVerticalMove(
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
         assertForVerticalMove(
                 /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
                 /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
                 /* expectedTime */ 2,
+                /* halfFoldPose */ false,
+                LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+        // Starting from top - tabletop mode
+        assertForVerticalMove(
+                /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+                /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+                /* expectedTime */ 1,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
+        assertForVerticalMove(
+                /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+                /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+                /* expectedTime */ 1,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+        // Starting from bottom - tabletop mode
+        assertForVerticalMove(
+                /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+                /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+                /* expectedTime */ 2,
+                /* halfFoldPose */ true,
+                LetterboxConfiguration::movePositionForVerticalReachabilityToNextTopStop);
+        assertForVerticalMove(
+                /* from */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+                /* expected */ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM,
+                /* expectedTime */ 2,
+                /* halfFoldPose */ true,
                 LetterboxConfiguration::movePositionForVerticalReachabilityToNextBottomStop);
     }
 
     private void assertForHorizontalMove(int from, int expected, int expectedTime,
-            Consumer<LetterboxConfiguration> move) {
+            boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
         // We are in the current position
-        when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability())
+        when(mLetterboxConfiguration.getLetterboxPositionForHorizontalReachability(halfFoldPose))
                 .thenReturn(from);
-        move.accept(mLetterboxConfiguration);
+        move.accept(mLetterboxConfiguration, halfFoldPose);
         verify(mLetterboxConfigurationPersister,
-                times(expectedTime)).setLetterboxPositionForHorizontalReachability(
+                times(expectedTime)).setLetterboxPositionForHorizontalReachability(halfFoldPose,
                 expected);
     }
 
     private void assertForVerticalMove(int from, int expected, int expectedTime,
-            Consumer<LetterboxConfiguration> move) {
+            boolean halfFoldPose, BiConsumer<LetterboxConfiguration, Boolean> move) {
         // We are in the current position
-        when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability())
+        when(mLetterboxConfiguration.getLetterboxPositionForVerticalReachability(halfFoldPose))
                 .thenReturn(from);
-        move.accept(mLetterboxConfiguration);
+        move.accept(mLetterboxConfiguration, halfFoldPose);
         verify(mLetterboxConfigurationPersister,
-                times(expectedTime)).setLetterboxPositionForVerticalReachability(
+                times(expectedTime)).setLetterboxPositionForVerticalReachability(halfFoldPose,
                 expected);
     }
 }
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 1484cb7..850bed6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -98,6 +99,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.statusbar.LetterboxDetails;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.DeviceStateController.FoldState;
 
 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
@@ -2821,6 +2823,70 @@
     }
 
     @Test
+    public void testUpdateResolvedBoundsVerticalPosition_tabletop() {
+
+        // Set up a display in portrait with a fixed-orientation LANDSCAPE app
+        setUpDisplaySizeWithApp(1400, 2800);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(
+                1.0f /*letterboxVerticalPositionMultiplier*/);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+        Rect letterboxNoFold = new Rect(0, 2100, 1400, 2800);
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+        // Make the activity full-screen
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);
+
+        Rect letterboxHalfFold = new Rect(0, 0, 1400, 700);
+        assertEquals(letterboxHalfFold, mActivity.getBounds());
+
+        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+    }
+
+    @Test
+    public void testUpdateResolvedBoundsHorizontalPosition_book() {
+
+        // Set up a display in landscape with a fixed-orientation PORTRAIT app
+        setUpDisplaySizeWithApp(2800, 1400);
+        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
+                1.0f /*letterboxVerticalPositionMultiplier*/);
+        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+        Rect letterboxNoFold = new Rect(2100, 0, 2800, 1400);
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+        // Make the activity full-screen
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);
+
+        Rect letterboxHalfFold = new Rect(0, 0, 700, 1400);
+        assertEquals(letterboxHalfFold, mActivity.getBounds());
+
+        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
+
+        assertEquals(letterboxNoFold, mActivity.getBounds());
+
+    }
+
+    private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
+        final DisplayRotation r = mActivity.mDisplayContent.getDisplayRotation();
+        doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
+        doReturn(false).when(r).isDeviceInPosture(any(FoldState.class), anyBoolean());
+        if (isHalfFolded) {
+            doReturn(true).when(r).isDeviceInPosture(FoldState.HALF_FOLDED, isTabletop);
+        }
+        mActivity.recomputeConfiguration();
+    }
+
+    @Test
     public void testUpdateResolvedBoundsPosition_alignToTop() {
         final int notchHeight = 100;
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
index 6e72bf3..3f8acc6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java
@@ -46,7 +46,7 @@
     @Override
     public void resized(ClientWindowFrames frames, boolean reportDraw,
             MergedConfiguration mergedConfig, InsetsState insetsState, boolean forceLayout,
-            boolean alwaysConsumeSystemBars, int displayId, int seqId, int resizeMode)
+            boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)
             throws RemoteException {
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index d583e89..1a1ca54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -56,6 +56,8 @@
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
+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;
@@ -660,6 +662,111 @@
     }
 
     @Test
+    public void testSetVisibleRequested() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        assertThat(root.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(root.isVisibleRequested()).isFalse();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(root.isVisibleRequested()).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+    }
+
+    @Test
+    public void testSetVisibleRequested_childRequestsVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        // Hidden root and child request hidden.
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(listener.mIsVisibleRequested).isFalse();
+        assertThat(child1.isVisibleRequested()).isFalse();
+
+        // Child requests to be visible, so child and root request visible.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(root.isVisibleRequested()).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+        assertThat(child1.isVisibleRequested()).isTrue();
+        // Visible request didn't change.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isFalse();
+        verify(root, times(2)).onChildVisibleRequestedChanged(child1);
+    }
+
+    @Test
+    public void testSetVisibleRequested_childRequestsHidden() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        final TestWindowContainerListener listener = new TestWindowContainerListener();
+        root.registerWindowContainerListener(listener);
+
+        // Root and child requests visible.
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(listener.mIsVisibleRequested).isTrue();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(child1.isVisibleRequested()).isTrue();
+
+        // Child requests hidden, so child and root request hidden.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isTrue();
+        assertThat(root.isVisibleRequested()).isFalse();
+        assertThat(listener.mIsVisibleRequested).isFalse();
+        assertThat(child1.isVisibleRequested()).isFalse();
+        // Visible request didn't change.
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        verify(root, times(3)).onChildVisibleRequestedChanged(child1);
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_bothVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        // Child and root request visible.
+        assertThat(root.setVisibleRequested(/* isVisible= */ true)).isTrue();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+        // Visible request already updated on root when child requested.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_childVisible() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ true)).isTrue();
+
+        // Visible request already updated on root when child requested.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
+    public void testOnChildVisibleRequestedChanged_childHidden() {
+        final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).setLayer(
+                0).build());
+        final TestWindowContainer child1 = root.addChildWindow();
+
+        assertThat(root.setVisibleRequested(/* isVisible= */ false)).isFalse();
+        assertThat(child1.setVisibleRequested(/* isVisible= */ false)).isFalse();
+
+        // Visible request did not change.
+        assertThat(root.onChildVisibleRequestedChanged(child1)).isFalse();
+    }
+
+    @Test
     public void testSetOrientation() {
         final TestWindowContainer root = spy(new TestWindowContainerBuilder(mWm).build());
         final TestWindowContainer child = spy(root.addChildWindow());
@@ -1656,6 +1763,7 @@
     private static class TestWindowContainerListener implements WindowContainerListener {
         private Configuration mConfiguration = new Configuration();
         private DisplayContent mDisplayContent;
+        private boolean mIsVisibleRequested;
 
         @Override
         public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
@@ -1666,5 +1774,10 @@
         public void onDisplayChanged(DisplayContent dc) {
             mDisplayContent = dc;
         }
+
+        @Override
+        public void onVisibleRequestedChanged(boolean isVisibleRequested) {
+            mIsVisibleRequested = isVisibleRequested;
+        }
     }
 }
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 8bd4148..d6cfd00 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.content.res.Configuration.GRAMMATICAL_GENDER_NOT_SPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
@@ -414,9 +415,10 @@
     public void testTopActivityUiModeChangeScheduleConfigChange() {
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.setVisibleRequested(true);
-        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any());
+        doReturn(true).when(activity).applyAppSpecificConfig(anyInt(), any(), anyInt());
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage(DEFAULT_COMPONENT_PACKAGE_NAME,
-                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
+                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+                GRAMMATICAL_GENDER_NOT_SPECIFIED);
         verify(activity).ensureActivityConfiguration(anyInt(), anyBoolean());
     }
 
@@ -425,8 +427,9 @@
         final ActivityRecord activity = createActivityRecord(mWpc);
         activity.setVisibleRequested(true);
         mWpc.updateAppSpecificSettingsForAllActivitiesInPackage("com.different.package",
-                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"));
-        verify(activity, never()).applyAppSpecificConfig(anyInt(), any());
+                Configuration.UI_MODE_NIGHT_YES, LocaleList.forLanguageTags("en-XA"),
+                GRAMMATICAL_GENDER_NOT_SPECIFIED);
+        verify(activity, never()).applyAppSpecificConfig(anyInt(), any(), anyInt());
         verify(activity, never()).ensureActivityConfiguration(anyInt(), anyBoolean());
     }
 
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 99d1772..5e0e209 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -277,12 +277,9 @@
         assertFalse(imeWindow.canBeImeTarget());
 
         // Simulate the window is in split screen root task.
-        final DockedTaskDividerController controller =
-                mDisplayContent.getDockedDividerController();
         final Task rootTask = createTask(mDisplayContent,
                 WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
         spyOn(appWindow);
-        spyOn(controller);
         spyOn(rootTask);
         rootTask.setFocusable(false);
         doReturn(rootTask).when(appWindow).getRootTask();
@@ -776,7 +773,7 @@
                     anyBoolean() /* reportDraw */, any() /* mergedConfig */,
                     any() /* insetsState */, anyBoolean() /* forceLayout */,
                     anyBoolean() /* alwaysConsumeSystemBars */, anyInt() /* displayId */,
-                    anyInt() /* seqId */, anyInt() /* resizeMode */);
+                    anyInt() /* seqId */, anyBoolean() /* dragResizing */);
         } catch (RemoteException ignored) {
         }
         win.reportResized();
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
index e82a7c2..fb0ce56 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/StateMachineTest.java
@@ -215,6 +215,20 @@
     }
 
     @Test
+    public void testStateMachineTriggerStateActionDelegateRoot() {
+        final StringBuffer log = new StringBuffer();
+
+        StateMachine stateMachine = new StateMachine(0x2);
+        stateMachine.addStateHandler(0x0, new LoggingHandler(0x0, log));
+        stateMachine.addStateHandler(0x2,
+                new LoggingHandler(0x2, log, false /* handleSelf */));
+
+        // state 0x2 delegate the message handling to its parent state
+        stateMachine.handle(0, null);
+        assertEquals("h0;", log.toString());
+    }
+
+    @Test
     public void testStateMachineNestedTransition() {
         final StringBuffer log = new StringBuffer();
 
diff --git a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
index ca11629..ff4268f 100644
--- a/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
+++ b/services/usb/java/com/android/server/usb/hal/port/UsbPortAidl.java
@@ -80,38 +80,46 @@
     /**
      * USB data status is not known.
      */
-    public static final int USB_DATA_STATUS_UNKNOWN = 0;
+    public static final int AIDL_USB_DATA_STATUS_UNKNOWN = 0;
 
     /**
      * USB data is enabled.
      */
-    public static final int USB_DATA_STATUS_ENABLED = 1;
+    public static final int AIDL_USB_DATA_STATUS_ENABLED = 1;
 
     /**
      * USB data is disabled as the port is too hot.
      */
-    public static final int USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_OVERHEAT = 2;
 
     /**
      * USB data is disabled due to contaminated port.
      */
-    public static final int USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_CONTAMINANT = 3;
 
     /**
-     * USB data is disabled due to docking event.
+     * USB data(both host mode and device mode) is disabled due to docking event.
      */
-    public static final int USB_DATA_STATUS_DISABLED_DOCK = 4;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK = 4;
 
     /**
      * USB data is disabled by
      * {@link UsbPort#enableUsbData UsbPort.enableUsbData}.
      */
-    public static final int USB_DATA_STATUS_DISABLED_FORCE = 5;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_FORCE = 5;
 
     /**
      * USB data is disabled for debug.
      */
-    public static final int USB_DATA_STATUS_DISABLED_DEBUG = 6;
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DEBUG = 6;
+    /**
+     * USB host mode disabled due to docking event.
+     */
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK_HOST_MODE = 7;
+    /**
+     * USB device mode disabled due to docking event.
+     */
+    public static final int AIDL_USB_DATA_STATUS_DISABLED_DOCK_DEVICE_MODE = 8;
 
     public @UsbHalVersion int getUsbHalVersion() throws RemoteException {
         synchronized (mLock) {
@@ -529,24 +537,43 @@
             int usbDataStatus = UsbPortStatus.DATA_STATUS_UNKNOWN;
             for (int i = 0; i < usbDataStatusHal.length; i++) {
                 switch (usbDataStatusHal[i]) {
-                    case USB_DATA_STATUS_ENABLED:
+                    case AIDL_USB_DATA_STATUS_ENABLED:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_ENABLED;
                         break;
-                    case USB_DATA_STATUS_DISABLED_OVERHEAT:
+                    case AIDL_USB_DATA_STATUS_DISABLED_OVERHEAT:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_OVERHEAT;
                         break;
-                    case USB_DATA_STATUS_DISABLED_CONTAMINANT:
+                    case AIDL_USB_DATA_STATUS_DISABLED_CONTAMINANT:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_CONTAMINANT;
                         break;
-                    case USB_DATA_STATUS_DISABLED_DOCK:
+                    /* Indicates both host and gadget mode being disabled. */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
                         break;
-                    case USB_DATA_STATUS_DISABLED_FORCE:
+                    case AIDL_USB_DATA_STATUS_DISABLED_FORCE:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_FORCE;
                         break;
-                    case USB_DATA_STATUS_DISABLED_DEBUG:
+                    case AIDL_USB_DATA_STATUS_DISABLED_DEBUG:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DEBUG;
                         break;
+                    /*
+                     * Set DATA_STATUS_DISABLED_DOCK when DATA_STATUS_DISABLED_DOCK_HOST_MODE
+                     * is set.
+                     */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK_HOST_MODE:
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_HOST_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        break;
+                    /*
+                     * Set DATA_STATUS_DISABLED_DOCK when DATA_STATUS_DISABLED_DEVICE_DOCK
+                     * is set.
+                     */
+                    case AIDL_USB_DATA_STATUS_DISABLED_DOCK_DEVICE_MODE:
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK_DEVICE_MODE;
+                        usbDataStatus |= UsbPortStatus.DATA_STATUS_DISABLED_DOCK;
+                        break;
                     default:
                         usbDataStatus |= UsbPortStatus.DATA_STATUS_UNKNOWN;
                 }
diff --git a/telecomm/java/android/telecom/CallAttributes.aidl b/telecomm/java/android/telecom/CallAttributes.aidl
new file mode 100644
index 0000000..19bada7
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallAttributes;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallAttributes.java b/telecomm/java/android/telecom/CallAttributes.java
new file mode 100644
index 0000000..6d87981
--- /dev/null
+++ b/telecomm/java/android/telecom/CallAttributes.java
@@ -0,0 +1,344 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * CallAttributes represents a set of properties that define a new Call.  Apps should build an
+ * instance of this class and use {@link TelecomManager#addCall} to start a new call with Telecom.
+ *
+ * <p>
+ * Apps should first register a {@link PhoneAccount} via {@link TelecomManager#registerPhoneAccount}
+ * and use the same {@link PhoneAccountHandle} registered with Telecom when creating an
+ * instance of CallAttributes.
+ */
+public final class CallAttributes implements Parcelable {
+
+    /** PhoneAccountHandle associated with the App managing calls **/
+    private final PhoneAccountHandle mPhoneAccountHandle;
+
+    /** Display name of the person on the other end of the call **/
+    private final CharSequence mDisplayName;
+
+    /** Address of the call. Note, this can be extended to a meeting link **/
+    private final Uri mAddress;
+
+    /** The direction (Outgoing/Incoming) of the new Call **/
+    @Direction private final int mDirection;
+
+    /** Information related to data being transmitted (voice, video, etc. ) **/
+    @CallType private final int mCallType;
+
+    /** Allows a package to opt into capabilities on the telecom side, on a per-call basis **/
+    @CallCapability private final int mCallCapabilities;
+
+    /** @hide **/
+    public static final String CALL_CAPABILITIES_KEY = "TelecomCapabilities";
+
+    private CallAttributes(@NonNull PhoneAccountHandle phoneAccountHandle,
+            @NonNull CharSequence displayName,
+            @NonNull Uri address,
+            int direction,
+            int callType,
+            int callCapabilities) {
+        mPhoneAccountHandle = phoneAccountHandle;
+        mDisplayName = displayName;
+        mAddress = address;
+        mDirection = direction;
+        mCallType = callType;
+        mCallCapabilities = callCapabilities;
+    }
+
+    /** @hide */
+    @IntDef(value = {DIRECTION_INCOMING, DIRECTION_OUTGOING})
+    public @interface Direction {
+    }
+    /**
+     * Indicates that the call is an incoming call.
+     */
+    public static final int DIRECTION_INCOMING = 1;
+    /**
+     * Indicates that the call is an outgoing call.
+     */
+    public static final int DIRECTION_OUTGOING = 2;
+
+    /** @hide */
+    @IntDef(value = {AUDIO_CALL, VIDEO_CALL})
+    public @interface CallType {
+    }
+    /**
+     * Used when answering or dialing a call to indicate that the call does not have a video
+     * component
+     */
+    public static final int AUDIO_CALL = 1;
+    /**
+     * Indicates video transmission is supported
+     */
+    public static final int VIDEO_CALL = 2;
+
+    /** @hide */
+    @IntDef(value = {SUPPORTS_SET_INACTIVE, SUPPORTS_STREAM, SUPPORTS_TRANSFER}, flag = true)
+    public @interface CallCapability {
+    }
+    /**
+     * The call being created can be set to inactive (traditionally referred to as hold).  This
+     * means that once a new call goes active, if the active call needs to be held in order to
+     * place or receive an incoming call, the active call will be placed on hold.  otherwise, the
+     * active call may be disconnected.
+     */
+    public static final int SUPPORTS_SET_INACTIVE = 1 << 1;
+    /**
+     * The call can be streamed from a root device to another device to continue the call without
+     * completely transferring it.
+     */
+    public static final int SUPPORTS_STREAM = 1 << 2;
+    /**
+     * The call can be completely transferred from one endpoint to another
+     */
+    public static final int SUPPORTS_TRANSFER = 1 << 3;
+
+    /**
+     * Build an instance of {@link CallAttributes}. In order to build a valid instance, a
+     * {@link PhoneAccountHandle}, call {@link Direction}, display name, and {@link Uri} address
+     * are required.
+     *
+     * <p>
+     * Note: Pass in the same {@link PhoneAccountHandle} that was used to register a
+     * {@link PhoneAccount} with Telecom. see {@link TelecomManager#registerPhoneAccount}
+     */
+    public static final class Builder {
+        // required and final fields
+        private final PhoneAccountHandle mPhoneAccountHandle;
+        @Direction private final int mDirection;
+        private final CharSequence mDisplayName;
+        private final Uri mAddress;
+        // optional fields
+        @CallType private int mCallType = CallAttributes.AUDIO_CALL;
+        @CallCapability private int mCallCapabilities = SUPPORTS_SET_INACTIVE;
+
+        /**
+         * Constructor for the CallAttributes.Builder class
+         *
+         * @param phoneAccountHandle that belongs to package registered with Telecom
+         * @param callDirection of the new call that will be added to Telecom
+         * @param displayName of the caller for incoming calls or initiating user for outgoing calls
+         * @param address of the caller for incoming calls or destination for outgoing calls
+         */
+        public Builder(@NonNull PhoneAccountHandle phoneAccountHandle,
+                @Direction int callDirection, @NonNull CharSequence displayName,
+                @NonNull Uri address) {
+            if (!isInRange(DIRECTION_INCOMING, DIRECTION_OUTGOING, callDirection)) {
+                throw new IllegalArgumentException(TextUtils.formatSimple("CallDirection=[%d] is"
+                                + " invalid. CallDirections value should be within [%d, %d]",
+                        callDirection, DIRECTION_INCOMING, DIRECTION_OUTGOING));
+            }
+            Objects.requireNonNull(phoneAccountHandle);
+            Objects.requireNonNull(displayName);
+            Objects.requireNonNull(address);
+            mPhoneAccountHandle = phoneAccountHandle;
+            mDirection = callDirection;
+            mDisplayName = displayName;
+            mAddress = address;
+        }
+
+        /**
+         * @param callType see {@link CallType} for valid arguments
+         * @return Builder
+         */
+        @NonNull
+        public Builder setCallType(@CallType int callType) {
+            if (!isInRange(AUDIO_CALL, VIDEO_CALL, callType)) {
+                throw new IllegalArgumentException(TextUtils.formatSimple("CallType=[%d] is"
+                                + " invalid. CallTypes value should be within [%d, %d]",
+                        callType, AUDIO_CALL, VIDEO_CALL));
+            }
+            mCallType = callType;
+            return this;
+        }
+
+        /**
+         * @param callCapabilities see {@link CallCapability} for valid arguments
+         * @return Builder
+         */
+        @NonNull
+        public Builder setCallCapabilities(@CallCapability int callCapabilities) {
+            mCallCapabilities = callCapabilities;
+            return this;
+        }
+
+        /**
+         * Build an instance of {@link CallAttributes} based on the last values passed to the
+         * setters or default values.
+         *
+         * @return an instance of {@link CallAttributes}
+         */
+        @NonNull
+        public CallAttributes build() {
+            return new CallAttributes(mPhoneAccountHandle, mDisplayName, mAddress, mDirection,
+                    mCallType, mCallCapabilities);
+        }
+
+        /** @hide */
+        private boolean isInRange(int floor, int ceiling, int value) {
+            return value >= floor && value <= ceiling;
+        }
+    }
+
+    /**
+     * The {@link PhoneAccountHandle} that should be registered to Telecom to allow calls.  The
+     * {@link PhoneAccountHandle} should be registered before creating a CallAttributes instance.
+     *
+     * @return the {@link PhoneAccountHandle} for this package that allows this call to be created
+     */
+    @NonNull public PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccountHandle;
+    }
+
+    /**
+     * @return display name of the incoming caller or the person being called for an outgoing call
+     */
+    @NonNull public CharSequence getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
+     * @return address of the incoming caller
+     *           or the address of the person being called for an outgoing call
+     */
+    @NonNull public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return the direction of the new call.
+     */
+    public @Direction int getDirection() {
+        return mDirection;
+    }
+
+    /**
+     * @return Information related to data being transmitted (voice, video, etc. )
+     */
+    public @CallType int getCallType() {
+        return mCallType;
+    }
+
+    /**
+     * @return The allowed capabilities of the new call
+     */
+    public @CallCapability int getCallCapabilities() {
+        return mCallCapabilities;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@Nullable Parcel dest, int flags) {
+        dest.writeParcelable(mPhoneAccountHandle, flags);
+        dest.writeCharSequence(mDisplayName);
+        dest.writeParcelable(mAddress, flags);
+        dest.writeInt(mDirection);
+        dest.writeInt(mCallType);
+        dest.writeInt(mCallCapabilities);
+    }
+
+    /**
+     * Responsible for creating CallAttribute objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<CallAttributes> CREATOR =
+            new Parcelable.Creator<>() {
+                @Override
+                public CallAttributes createFromParcel(Parcel source) {
+                    return new CallAttributes(source.readParcelable(getClass().getClassLoader(),
+                            android.telecom.PhoneAccountHandle.class),
+                            source.readCharSequence(),
+                            source.readParcelable(getClass().getClassLoader(),
+                                    android.net.Uri.class),
+                            source.readInt(),
+                            source.readInt(),
+                            source.readInt());
+                }
+
+                @Override
+                public CallAttributes[] newArray(int size) {
+                    return new CallAttributes[size];
+                }
+            };
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("{ CallAttributes: [phoneAccountHandle: ")
+                .append(mPhoneAccountHandle)  /* PhoneAccountHandle#toString handles PII */
+                .append("], [contactName: ")
+                .append(Log.pii(mDisplayName))
+                .append("], [address=")
+                .append(Log.pii(mAddress))
+                .append("], [direction=")
+                .append(mDirection)
+                .append("], [callType=")
+                .append(mCallType)
+                .append("], [mCallCapabilities=")
+                .append(mCallCapabilities)
+                .append("]  }");
+
+        return sb.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || obj.getClass() != this.getClass()) {
+            return false;
+        }
+        CallAttributes that = (CallAttributes) obj;
+        return this.mDirection == that.mDirection
+                && this.mCallType == that.mCallType
+                && this.mCallCapabilities == that.mCallCapabilities
+                && Objects.equals(this.mPhoneAccountHandle, that.mPhoneAccountHandle)
+                && Objects.equals(this.mAddress, that.mAddress)
+                && Objects.equals(this.mDisplayName, that.mDisplayName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mPhoneAccountHandle, mAddress, mDisplayName,
+                mDirection, mCallType, mCallCapabilities);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallAudioState.java b/telecomm/java/android/telecom/CallAudioState.java
index fccdf76..c7cc1bd 100644
--- a/telecomm/java/android/telecom/CallAudioState.java
+++ b/telecomm/java/android/telecom/CallAudioState.java
@@ -27,7 +27,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -58,6 +57,9 @@
     /** Direct the audio stream through the device's speakerphone. */
     public static final int ROUTE_SPEAKER       = 0x00000008;
 
+    /** Direct the audio stream through another device. */
+    public static final int ROUTE_STREAMING     = 0x00000010;
+
     /**
      * Direct the audio stream through the device's earpiece or wired headset if one is
      * connected.
@@ -70,7 +72,7 @@
      * @hide
      **/
     public static final int ROUTE_ALL = ROUTE_EARPIECE | ROUTE_BLUETOOTH | ROUTE_WIRED_HEADSET |
-            ROUTE_SPEAKER;
+            ROUTE_SPEAKER | ROUTE_STREAMING;
 
     private final boolean isMuted;
     private final int route;
@@ -189,7 +191,11 @@
      */
     @CallAudioRoute
     public int getSupportedRouteMask() {
-        return supportedRouteMask;
+        if (route == ROUTE_STREAMING) {
+            return ROUTE_STREAMING;
+        } else {
+            return supportedRouteMask;
+        }
     }
 
     /**
@@ -232,6 +238,9 @@
         if ((route & ROUTE_SPEAKER) == ROUTE_SPEAKER) {
             listAppend(buffer, "SPEAKER");
         }
+        if ((route & ROUTE_STREAMING) == ROUTE_STREAMING) {
+            listAppend(buffer, "STREAMING");
+        }
 
         return buffer.toString();
     }
diff --git a/telecomm/java/android/telecom/CallControl.aidl b/telecomm/java/android/telecom/CallControl.aidl
new file mode 100644
index 0000000..0f780e6
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallControl;
diff --git a/telecomm/java/android/telecom/CallControl.java b/telecomm/java/android/telecom/CallControl.java
new file mode 100644
index 0000000..867bcc7
--- /dev/null
+++ b/telecomm/java/android/telecom/CallControl.java
@@ -0,0 +1,282 @@
+/*
+ * 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.telecom;
+
+import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ICallControl;
+
+import java.util.concurrent.Executor;
+
+/**
+ * CallControl provides client side control of a call.  Each Call will get an individual CallControl
+ * instance in which the client can alter the state of the associated call.
+ *
+ * <p>
+ * Each method is Transactional meaning that it can succeed or fail. If a transaction succeeds,
+ * the {@link OutcomeReceiver#onResult} will be called by Telecom.  Otherwise, the
+ * {@link OutcomeReceiver#onError} is called and provides a {@link CallException} that details why
+ * the operation failed.
+ */
+public final class CallControl implements AutoCloseable {
+    private static final String TAG = CallControl.class.getSimpleName();
+    private static final String INTERFACE_ERROR_MSG = "Call Control is not available";
+    private final String mCallId;
+    private final ICallControl mServerInterface;
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    private final ClientTransactionalServiceRepository mRepository;
+
+    /** @hide */
+    public CallControl(@NonNull String callId, @Nullable ICallControl serverInterface,
+            @NonNull ClientTransactionalServiceRepository repository,
+            @NonNull PhoneAccountHandle pah) {
+        mCallId = callId;
+        mServerInterface = serverInterface;
+        mRepository = repository;
+        mPhoneAccountHandle = pah;
+    }
+
+    /**
+     * @return the callId Telecom assigned to this CallControl object which should be attached to
+     *  an individual call.
+     */
+    @NonNull
+    public ParcelUuid getCallId() {
+        return ParcelUuid.fromString(mCallId);
+    }
+
+    /**
+     * Request Telecom set the call state to active.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 switched the call state to active
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                 the call state to active.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void setActive(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.setActive(mCallId,
+                        new CallControlResultReceiver("setActive", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom set the call state to inactive. This the same as hold for two call endpoints
+     * but can be extended to setting a meeting to inactive.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 switched the call state to inactive
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to set
+     *                 the call state to inactive.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void setInactive(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.setInactive(mCallId,
+                        new CallControlResultReceiver("setInactive", executor, callback));
+
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom set the call state to disconnect.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 disconnected the call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 disconnect the call.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void disconnect(@NonNull DisconnectCause disconnectCause,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.disconnect(mCallId, disconnectCause,
+                        new CallControlResultReceiver("disconnect", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request Telecom reject the incoming call.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 rejected the incoming call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 reject the incoming call.  A {@link CallException} will be passed
+     *                 that details why the operation failed.
+     */
+    public void rejectCall(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.rejectCall(mCallId,
+                        new CallControlResultReceiver("rejectCall", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * Request start a call streaming session. On receiving valid request, telecom will bind to
+     * the {@link CallStreamingService} implemented by a general call streaming sender. So that the
+     * call streaming sender can perform streaming local device audio to another remote device and
+     * control the call during streaming.
+     *
+     * @param executor The {@link Executor} on which the {@link OutcomeReceiver} callback
+     *                 will be called on.
+     * @param callback that will be completed on the Telecom side that details success or failure
+     *                 of the requested operation.
+     *
+     *                 {@link OutcomeReceiver#onResult} will be called if Telecom has successfully
+     *                 rejected the incoming call.
+     *
+     *                 {@link OutcomeReceiver#onError} will be called if Telecom has failed to
+     *                 reject the incoming call.  A {@link CallException} will be passed that
+     *                 details why the operation failed.
+     */
+    public void startCallStreaming(@CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CallException> callback) {
+        if (mServerInterface != null) {
+            try {
+                mServerInterface.startCallStreaming(mCallId,
+                        new CallControlResultReceiver("startCallStreaming", executor, callback));
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        } else {
+            throw new IllegalStateException(INTERFACE_ERROR_MSG);
+        }
+    }
+
+    /**
+     * This method should be called after
+     * {@link CallControl#disconnect(DisconnectCause, Executor, OutcomeReceiver)} or
+     * {@link CallControl#rejectCall(Executor, OutcomeReceiver)}
+     * to destroy all references of this object and avoid memory leaks.
+     */
+    @Override
+    public void close() {
+        mRepository.removeCallFromServiceWrapper(mPhoneAccountHandle, mCallId);
+    }
+
+    /**
+     * Since {@link OutcomeReceiver}s cannot be passed via AIDL, a ResultReceiver (which can) must
+     * wrap the Clients {@link OutcomeReceiver} passed in and await for the Telecom Server side
+     * response in {@link ResultReceiver#onReceiveResult(int, Bundle)}.
+     * @hide */
+    private class CallControlResultReceiver extends ResultReceiver {
+        private final String mCallingMethod;
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CallException> mClientCallback;
+
+        CallControlResultReceiver(String method, Executor executor,
+                OutcomeReceiver<Void, CallException> clientCallback) {
+            super(null);
+            mCallingMethod = method;
+            mExecutor = executor;
+            mClientCallback = clientCallback;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            Log.d(CallControl.TAG, "%s: oRR: resultCode=[%s]", mCallingMethod, resultCode);
+            super.onReceiveResult(resultCode, resultData);
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                if (resultCode == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+                    mExecutor.execute(() -> mClientCallback.onResult(null));
+                } else {
+                    mExecutor.execute(() ->
+                            mClientCallback.onError(getTransactionException(resultData)));
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+    }
+
+    /** @hide */
+    private CallException getTransactionException(Bundle resultData) {
+        String message = "unknown error";
+        if (resultData != null && resultData.containsKey(TRANSACTION_EXCEPTION_KEY)) {
+            return resultData.getParcelable(TRANSACTION_EXCEPTION_KEY,
+                    CallException.class);
+        }
+        return new CallException(message, CallException.CODE_ERROR_UNKNOWN);
+    }
+}
diff --git a/telecomm/java/android/telecom/CallEventCallback.java b/telecomm/java/android/telecom/CallEventCallback.java
new file mode 100644
index 0000000..fd7e101
--- /dev/null
+++ b/telecomm/java/android/telecom/CallEventCallback.java
@@ -0,0 +1,121 @@
+/*
+ * 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.telecom;
+
+
+import android.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * CallEventCallback relays updates to a call from the Telecom framework.
+ * This can include operations which the app must implement on a Call due to the presence of other
+ * calls on the device, requests relayed from a Bluetooth device, or from another calling surface.
+ *
+ * <p>
+ * CallEventCallbacks with {@link Consumer}s are transactional, meaning that a client must
+ * complete the {@link Consumer} via {@link Consumer#accept(Object)} in order to complete the
+ * CallEventCallback. If a CallEventCallback can be completed, the
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#TRUE}. Otherwise,
+ * {@link Consumer#accept(Object)} should be called with {@link Boolean#FALSE} to represent the
+ * CallEventCallback cannot be completed on the client side.
+ *
+ * <p>
+ * Note: Each CallEventCallback has a timeout of 5000 milliseconds. Failing to complete the
+ * {@link Consumer} before the timeout will result in a failed transaction.
+ */
+public interface CallEventCallback {
+    /**
+     * Telecom is informing the client to set the call active
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+     *                     active on their end, the {@link Consumer#accept(Object)} should be
+     *                     called with {@link Boolean#TRUE}. Otherwise,
+     *                     {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#FALSE}.
+     */
+    void onSetActive(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to set the call inactive. This is the same as holding a call
+     * for two endpoints but can be extended to setting a meeting inactive.
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can set the call
+     *                     inactive on their end, the {@link Consumer#accept(Object)} should be
+     *                     called with {@link Boolean#TRUE}. Otherwise,
+     *                     {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#FALSE}.
+     */
+    void onSetInactive(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to answer an incoming call and set it to active.
+     *
+     * @param videoState   see {@link android.telecom.CallAttributes.CallType} for valid states
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can answer the call
+     *                     on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)} should
+     *                     be called with {@link Boolean#FALSE}.
+     */
+    void onAnswer(@android.telecom.CallAttributes.CallType int videoState,
+            @NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to reject the incoming call
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can reject the
+     *                     incoming call, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should  be called with {@link Boolean#FALSE}.
+     */
+    void onReject(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client to disconnect the call
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can disconnect the
+     *                     call on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should  be called with {@link Boolean#FALSE}.
+     */
+    void onDisconnect(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * update the client on the new {@link CallAudioState}
+     *
+     * @param callAudioState that is currently being used
+     */
+    void onCallAudioStateChanged(@NonNull CallAudioState callAudioState);
+
+    /**
+     * Telecom is informing the client to set the call in streaming.
+     *
+     * @param wasCompleted The {@link Consumer} to be completed. If the client can stream the
+     *                     call on their end, {@link Consumer#accept(Object)} should be called with
+     *                     {@link Boolean#TRUE}. Otherwise, {@link Consumer#accept(Object)}
+     *                     should be called with {@link Boolean#FALSE}.
+     */
+    void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted);
+
+    /**
+     * Telecom is informing the client user requested call streaming but the stream can't be
+     * started.
+     *
+     * @param reason Code to indicate the reason of this failure
+     */
+    void onCallStreamingFailed(@CallStreamingService.StreamingFailedReason int reason);
+}
diff --git a/telecomm/java/android/telecom/CallException.aidl b/telecomm/java/android/telecom/CallException.aidl
new file mode 100644
index 0000000..a16af12
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable CallException;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/CallException.java b/telecomm/java/android/telecom/CallException.java
new file mode 100644
index 0000000..0b0de6b
--- /dev/null
+++ b/telecomm/java/android/telecom/CallException.java
@@ -0,0 +1,160 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import androidx.annotation.NonNull;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class defines exceptions that can be thrown when using Telecom APIs with
+ * {@link android.os.OutcomeReceiver}s.  Most of these exceptions are thrown when changing a call
+ * state with {@link CallControl}s or {@link CallEventCallback}s.
+ */
+public final class CallException extends RuntimeException implements Parcelable {
+    /** @hide **/
+    public static final String TRANSACTION_EXCEPTION_KEY = "TelecomTransactionalExceptionKey";
+
+    /**
+     * The operation has failed due to an unknown or unspecified error.
+     */
+    public static final int CODE_ERROR_UNKNOWN = 1;
+
+    /**
+     * The operation has failed due to Telecom failing to hold the current active call for the
+     * call attempting to become the new active call.  The client should end the current active call
+     * and re-try the failed operation.
+     */
+    public static final int CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL = 2;
+
+    /**
+     * The operation has failed because Telecom has already removed the call from the server side
+     * and destroyed all the objects associated with it.  The client should re-add the call.
+     */
+    public static final int CODE_CALL_IS_NOT_BEING_TRACKED = 3;
+
+    /**
+     * The operation has failed because Telecom cannot set the requested call as the current active
+     * call.  The client should end the current active call and re-try the operation.
+     */
+    public static final int CODE_CALL_CANNOT_BE_SET_TO_ACTIVE = 4;
+
+    /**
+     * The operation has failed because there is either no PhoneAccount registered with Telecom
+     * for the given operation, or the limit of calls has been reached. The client should end the
+     * current active call and re-try the failed operation.
+     */
+    public static final int CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME = 5;
+
+    /**
+     * The operation has failed because the operation failed to complete before the timeout
+     */
+    public static final int CODE_OPERATION_TIMED_OUT = 6;
+
+    private int mCode = CODE_ERROR_UNKNOWN;
+    private final String mMessage;
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mMessage);
+        dest.writeInt(mCode);
+    }
+
+    /**
+     * Responsible for creating CallAttribute objects for deserialized Parcels.
+     */
+    public static final @android.annotation.NonNull
+            Parcelable.Creator<CallException> CREATOR = new Parcelable.Creator<>() {
+                    @Override
+                    public CallException createFromParcel(Parcel source) {
+                        return new CallException(source.readString8(), source.readInt());
+                    }
+
+                    @Override
+                    public CallException[] newArray(int size) {
+                        return new CallException[size];
+                    }
+            };
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "CODE_ERROR_", value = {
+            CODE_ERROR_UNKNOWN,
+            CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL,
+            CODE_CALL_IS_NOT_BEING_TRACKED,
+            CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+            CODE_OPERATION_TIMED_OUT
+    })
+    public @interface CallErrorCode {
+    }
+
+    /**
+     * Constructor for a new CallException when only message can be specified.
+     * {@code CODE_ERROR_UNKNOWN} will be default code returned when calling {@code getCode}
+     *
+     * @param message related to why the exception was created
+     */
+    public CallException(@Nullable String message) {
+        super(getMessage(message, CODE_ERROR_UNKNOWN));
+        mMessage = message;
+    }
+
+    /**
+     * Constructor for a new CallException that has a defined error code in this class
+     *
+     * @param message related to why the exception was created
+     * @param code defined above that caused this exception to be created
+     */
+    public CallException(@Nullable String message, @CallErrorCode int code) {
+        super(getMessage(message, code));
+        mCode = code;
+        mMessage = message;
+    }
+
+    /**
+     * @return one of the error codes defined in this class that was passed into the constructor
+     */
+    public @CallErrorCode int getCode() {
+        return mCode;
+    }
+
+    private static String getMessage(String message, int code) {
+        StringBuilder builder;
+        if (!TextUtils.isEmpty(message)) {
+            builder = new StringBuilder(message);
+            builder.append(" (code: ");
+            builder.append(code);
+            builder.append(")");
+            return builder.toString();
+        } else {
+            return "code: " + code;
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/CallStreamingService.java b/telecomm/java/android/telecom/CallStreamingService.java
new file mode 100644
index 0000000..affa6b6
--- /dev/null
+++ b/telecomm/java/android/telecom/CallStreamingService.java
@@ -0,0 +1,184 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This service is implemented by an app that wishes to provide functionality for a general call
+ * streaming sender for voip calls.
+ *
+ * TODO: add doc of how to be the general streaming sender
+ *
+ */
+public abstract class CallStreamingService extends Service {
+    /**
+     * The {@link android.content.Intent} that must be declared as handled by the service.
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+    public static final String SERVICE_INTERFACE = "android.telecom.CallStreamingService";
+
+    private static final int MSG_SET_STREAMING_CALL_ADAPTER = 1;
+    private static final int MSG_CALL_STREAMING_STARTED = 2;
+    private static final int MSG_CALL_STREAMING_STOPPED = 3;
+    private static final int MSG_CALL_STREAMING_CHANGED_CHANGED = 4;
+
+    /** Default Handler used to consolidate binder method calls onto a single thread. */
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mStreamingCallAdapter == null && msg.what != MSG_SET_STREAMING_CALL_ADAPTER) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_SET_STREAMING_CALL_ADAPTER:
+                    mStreamingCallAdapter = new StreamingCallAdapter(
+                            (IStreamingCallAdapter) msg.obj);
+                    break;
+                case MSG_CALL_STREAMING_STARTED:
+                    mCall = (StreamingCall) msg.obj;
+                    mCall.setAdapter(mStreamingCallAdapter);
+                    CallStreamingService.this.onCallStreamingStarted(mCall);
+                    break;
+                case MSG_CALL_STREAMING_STOPPED:
+                    mCall = null;
+                    mStreamingCallAdapter = null;
+                    CallStreamingService.this.onCallStreamingStopped();
+                    break;
+                case MSG_CALL_STREAMING_CHANGED_CHANGED:
+                    int state = (int) msg.obj;
+                    mCall.setStreamingState(state);
+                    CallStreamingService.this.onCallStreamingStateChanged(state);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Nullable
+    @Override
+    public IBinder onBind(@NonNull Intent intent) {
+        return new CallStreamingServiceBinder();
+    }
+
+    /** Manages the binder calls so that the implementor does not need to deal with it. */
+    private final class CallStreamingServiceBinder extends ICallStreamingService.Stub {
+        @Override
+        public void setStreamingCallAdapter(IStreamingCallAdapter streamingCallAdapter)
+                throws RemoteException {
+            mHandler.obtainMessage(MSG_SET_STREAMING_CALL_ADAPTER, mStreamingCallAdapter)
+                    .sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStarted(StreamingCall call) throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_STARTED, call).sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStopped() throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_STOPPED).sendToTarget();
+        }
+
+        @Override
+        public void onCallStreamingStateChanged(int state) throws RemoteException {
+            mHandler.obtainMessage(MSG_CALL_STREAMING_CHANGED_CHANGED, state).sendToTarget();
+        }
+    }
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because there's an ongoing streaming call on this device.
+     */
+    public static final int STREAMING_FAILED_ALREADY_STREAMING = 1;
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because telecom can't find existing general streaming sender on this
+     * device.
+     */
+    public static final int STREAMING_FAILED_NO_SENDER = 2;
+
+    /**
+     * Call streaming request reject reason used with
+     * {@link CallEventCallback#onCallStreamingFailed(int)} to indicate that telecom is rejecting a
+     * call streaming request because telecom can't bind to the general streaming sender app.
+     */
+    public static final int STREAMING_FAILED_SENDER_BINDING_ERROR = 3;
+
+    private StreamingCallAdapter mStreamingCallAdapter;
+    private StreamingCall mCall;
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = {"STREAMING_FAILED"},
+            value = {
+                    STREAMING_FAILED_ALREADY_STREAMING,
+                    STREAMING_FAILED_NO_SENDER,
+                    STREAMING_FAILED_SENDER_BINDING_ERROR
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StreamingFailedReason {};
+
+    /**
+     * Called when a {@code StreamingCall} has been added to this call streaming session. The call
+     * streaming sender should start to intercept the device audio using audio records and audio
+     * tracks from Audio frameworks.
+     *
+     * @param call a newly added {@code StreamingCall}.
+     */
+    public void onCallStreamingStarted(@NonNull StreamingCall call) {
+    }
+
+    /**
+     * Called when a current {@code StreamingCall} has been removed from this call streaming
+     * session. The call streaming sender should notify the streaming receiver that the call is
+     * stopped streaming and stop the device audio interception.
+     */
+    public void onCallStreamingStopped() {
+    }
+
+    /**
+     * Called when the streaming state of current {@code StreamingCall} changed. General streaming
+     * sender usually get notified of the holding/unholding from the original owner voip app of the
+     * call.
+     */
+    public void onCallStreamingStateChanged(@StreamingCall.StreamingCallState int state) {
+    }
+}
diff --git a/telecomm/java/android/telecom/PhoneAccount.java b/telecomm/java/android/telecom/PhoneAccount.java
index ec18c6a..b8c056e 100644
--- a/telecomm/java/android/telecom/PhoneAccount.java
+++ b/telecomm/java/android/telecom/PhoneAccount.java
@@ -418,7 +418,34 @@
      */
     public static final int CAPABILITY_VOICE_CALLING_AVAILABLE = 0x20000;
 
-    /* NEXT CAPABILITY: 0x40000 */
+
+    /**
+     * Flag indicating that this {@link PhoneAccount} supports the use TelecomManager APIs that
+     * utilize {@link android.os.OutcomeReceiver}s or {@link java.util.function.Consumer}s.
+     * Be aware, if this capability is set, {@link #CAPABILITY_SELF_MANAGED} will be amended by
+     * Telecom when this {@link PhoneAccount} is registered via
+     * {@link TelecomManager#registerPhoneAccount(PhoneAccount)}.
+     *
+     * <p>
+     * {@link android.os.OutcomeReceiver}s and {@link java.util.function.Consumer}s represent
+     * transactional operations because the operation can succeed or fail.  An app wishing to use
+     * transactional operations should define behavior for a successful and failed TelecomManager
+     * API call.
+     *
+     * @see #CAPABILITY_SELF_MANAGED
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS = 0x40000;
+
+    /**
+     * Flag indicating that this voip app {@link PhoneAccount} supports the call streaming session
+     * to stream call audio to another remote device via streaming app.
+     *
+     * @see #getCapabilities
+     */
+    public static final int CAPABILITY_SUPPORTS_CALL_STREAMING = 0x80000;
+
+    /* NEXT CAPABILITY: [0x100000, 0x200000, 0x400000] */
 
     /**
      * URI scheme for telephone number URIs.
diff --git a/telecomm/java/android/telecom/StreamingCall.aidl b/telecomm/java/android/telecom/StreamingCall.aidl
new file mode 100644
index 0000000..d286658
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.aidl
@@ -0,0 +1,22 @@
+/*
+ * 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.telecom;
+
+/**
+ * {@hide}
+ */
+parcelable StreamingCall;
\ No newline at end of file
diff --git a/telecomm/java/android/telecom/StreamingCall.java b/telecomm/java/android/telecom/StreamingCall.java
new file mode 100644
index 0000000..985cccc
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCall.java
@@ -0,0 +1,178 @@
+/*
+ * 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.telecom;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Represents a voip call requested to stream to another device that the general streaming sender
+ * app should present to the receiver.
+ */
+public final class StreamingCall implements Parcelable {
+    /**
+     * The state of a {@code StreamingCall} when newly created. General streaming sender should
+     * continuously stream call audio to the sender device as long as the {@code StreamingCall} is
+     * in this state.
+     */
+    public static final int STATE_STREAMING = 1;
+
+    /**
+     * The state of a {@code StreamingCall} when in a holding state.
+     */
+    public static final int STATE_HOLDING = 2;
+
+    /**
+     * The state of a {@code StreamingCall} when it's either disconnected or pulled back to the
+     * original device.
+     */
+    public static final int STATE_DISCONNECTED = 3;
+
+    private StreamingCall(@NonNull Parcel in) {
+        mComponentName = in.readParcelable(ComponentName.class.getClassLoader());
+        mDisplayName = in.readString16NoHelper();
+        mAddress = in.readParcelable(Uri.class.getClassLoader());
+        mExtras = in.readBundle();
+        mState = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<StreamingCall> CREATOR = new Creator<>() {
+        @Override
+        public StreamingCall createFromParcel(@NonNull Parcel in) {
+            return new StreamingCall(in);
+        }
+
+        @Override
+        public StreamingCall[] newArray(int size) {
+            return new StreamingCall[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@androidx.annotation.NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mComponentName, flags);
+        dest.writeString16NoHelper(mDisplayName);
+        dest.writeParcelable(mAddress, flags);
+        dest.writeBundle(mExtras);
+        dest.writeInt(mState);
+    }
+
+    /**
+     * @hide
+     */
+    @IntDef(prefix = { "STATE_" },
+            value = {
+                    STATE_STREAMING,
+                    STATE_HOLDING,
+                    STATE_DISCONNECTED
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface StreamingCallState {}
+
+    private final ComponentName mComponentName;
+    private final String mDisplayName;
+    private final Uri mAddress;
+    private final Bundle mExtras;
+    @StreamingCallState
+    private int mState;
+    private StreamingCallAdapter mAdapter = null;
+
+    public StreamingCall(@NonNull ComponentName componentName, @NonNull String displayName,
+            @NonNull Uri address, @NonNull Bundle extras) {
+        mComponentName = componentName;
+        mDisplayName = displayName;
+        mAddress = address;
+        mExtras = extras;
+        mState = STATE_STREAMING;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAdapter(StreamingCallAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * @return The {@link ComponentName} to identify the original voip app of this
+     * {@code StreamingCall}. General streaming sender app can use this to query necessary
+     * information (app icon etc.) in order to present notification of the streaming call on the
+     * receiver side.
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * @return The display name that the general streaming sender app can use this to present the
+     * {@code StreamingCall} to the receiver side.
+     */
+    @NonNull
+    public String getDisplayName() {
+        return mDisplayName;
+    }
+
+    /**
+     * @return The address (e.g., phone number) to which the {@code StreamingCall} is currently
+     * connected.
+     */
+    @NonNull
+    public Uri getAddress() {
+        return mAddress;
+    }
+
+    /**
+     * @return The state of this {@code StreamingCall}.
+     */
+    @StreamingCallState
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * @return The extra info the general streaming app need to stream the call from voip app or
+     * D2DI sdk.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Sets the state of this {@code StreamingCall}. The general streaming sender app can use this
+     * to request holding, unholding and disconnecting this {@code StreamingCall}.
+     * @param state The current streaming state of the call.
+     */
+    public void setStreamingState(@StreamingCallState int state) {
+        mAdapter.setStreamingState(state);
+    }
+}
diff --git a/telecomm/java/android/telecom/StreamingCallAdapter.java b/telecomm/java/android/telecom/StreamingCallAdapter.java
new file mode 100644
index 0000000..bd8727d
--- /dev/null
+++ b/telecomm/java/android/telecom/StreamingCallAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * 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.telecom;
+
+import android.os.RemoteException;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives commands from {@link CallStreamingService} implementations which should be executed by
+ * Telecom. When Telecom binds to a {@link CallStreamingService}, an instance of this class is given
+ * to the general streaming app through which it can manipulate the streaming calls. Whe the general
+ * streaming app is notified of new ongoing streaming calls, it can execute
+ * {@link StreamingCall#setStreamingState(int)} for the ongoing streaming calls the user on the
+ * receiver side would like to hold, unhold and disconnect.
+ *
+ * @hide
+ */
+public final class StreamingCallAdapter {
+    private final IStreamingCallAdapter mAdapter;
+
+    /**
+     * {@hide}
+     */
+    public StreamingCallAdapter(IStreamingCallAdapter adapter) {
+        mAdapter = adapter;
+    }
+
+    /**
+     * Instruct telecom to change the state of the streaming call.
+     *
+     * @param state The streaming state to set
+     */
+    public void setStreamingState(@StreamingCall.StreamingCallState int state) {
+        try {
+            mAdapter.setStreamingState(state);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af37ed5..7c86a75a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -18,6 +18,7 @@
 import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM;
 
 import android.Manifest;
+import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,7 @@
 import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
+import android.os.OutcomeReceiver;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -49,6 +51,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ClientTransactionalServiceWrapper;
 import com.android.internal.telecom.ITelecomService;
 
 import java.lang.annotation.Retention;
@@ -1056,6 +1060,14 @@
 
     private final ITelecomService mTelecomServiceOverride;
 
+    /** @hide **/
+    private final ClientTransactionalServiceRepository mTransactionalServiceRepository =
+            new ClientTransactionalServiceRepository();
+    /** @hide **/
+    public static final int TELECOM_TRANSACTION_SUCCESS = 0;
+    /** @hide **/
+    public static final String TRANSACTION_CALL_ID_KEY = "TelecomCallId";
+
     /**
      * @hide
      */
@@ -2640,6 +2652,92 @@
     }
 
     /**
+     * Adds a new call with the specified {@link CallAttributes} to the telecom service. This method
+     * can be used to add both incoming and outgoing calls.
+     *
+     * <p>
+     * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or
+     * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API
+     * will asynchronously provide an update on whether the new call was added successfully via
+     * an {@link OutcomeReceiver}.  Additionally, callbacks will run on the executor thread that was
+     * passed in.
+     *
+     * <p>
+     * Note: Only packages that register with
+     * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS}
+     * can utilize this API. {@link PhoneAccount}s that set the capabilities
+     * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION},
+     * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER},
+     * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER}
+     * are not supported and will cause an exception to be thrown.
+     *
+     * <p>
+     * Usage example:
+     * <pre>
+     *
+     *  // An app should first define their own construct of a Call that overrides all the
+     *  // {@link CallEventCallback}s
+     *  private class MyVoipCall implements CallEventCallback {
+     *    // override all the {@link CallEventCallback}s
+     *  }
+     *
+     * PhoneAccountHandle handle = new PhoneAccountHandle(
+     *                          new ComponentName("com.example.voip.app",
+     *                                            "com.example.voip.app.NewCallActivity"), "123");
+     *
+     * CallAttributes callAttributes = new CallAttributes.Builder(handle,
+     *                                             CallAttributes.DIRECTION_OUTGOING,
+     *                                            "John Smith", Uri.fromParts("tel", "123", null))
+     *                                            .build();
+     *
+     * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() {
+     *                              public void onResult(CallControl callControl) {
+     *                                 // The call has been added successfully
+     *                              }
+     *                           }, new MyVoipCall());
+     * </pre>
+     *
+     * @param callAttributes    attributes of the new call (incoming or outgoing, address, etc. )
+     * @param executor          thread to run background CallEventCallback updates on
+     * @param pendingControl    OutcomeReceiver that receives the result of addCall transaction
+     * @param callEventCallback object that overrides CallEventCallback
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS)
+    @SuppressLint("SamShouldBeLast")
+    public void addCall(@NonNull CallAttributes callAttributes,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<CallControl, CallException> pendingControl,
+            @NonNull CallEventCallback callEventCallback) {
+        Objects.requireNonNull(callAttributes);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(pendingControl);
+        Objects.requireNonNull(callEventCallback);
+
+        ITelecomService service = getTelecomService();
+        if (service != null) {
+            try {
+                // create or add the new call to a service wrapper w/ the same phoneAccountHandle
+                ClientTransactionalServiceWrapper transactionalServiceWrapper =
+                        mTransactionalServiceRepository.addNewCallForTransactionalServiceWrapper(
+                                callAttributes.getPhoneAccountHandle());
+
+                // couple all the args passed by the client
+                String newCallId = transactionalServiceWrapper.trackCall(callAttributes, executor,
+                        pendingControl, callEventCallback);
+
+                // send args to server to process new call
+                service.addCall(callAttributes, transactionalServiceWrapper.getCallEventCallback(),
+                        newCallId, mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException addCall: " + e);
+                e.rethrowFromSystemServer();
+            }
+        } else {
+            throw new IllegalStateException("Telecom service is not present");
+        }
+    }
+
+    /**
      * Handles {@link Intent#ACTION_CALL} intents trampolined from UserCallActivity.
      * @param intent The {@link Intent#ACTION_CALL} intent to handle.
      * @param callingPackageProxy The original package that called this before it was trampolined.
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
new file mode 100644
index 0000000..2eebbdb
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceRepository.java
@@ -0,0 +1,90 @@
+/*
+ * 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.telecom;
+
+import android.telecom.PhoneAccountHandle;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @hide
+ */
+public class ClientTransactionalServiceRepository {
+
+    private static final Map<PhoneAccountHandle, ClientTransactionalServiceWrapper> LOOKUP_TABLE =
+            new ConcurrentHashMap<>();
+
+    /**
+     * creates a new {@link ClientTransactionalServiceWrapper} if this is the first call being
+     * tracked for a particular package Or adds a new call for an existing
+     * {@link ClientTransactionalServiceWrapper}
+     *
+     * @param phoneAccountHandle for a particular package requesting to create a call
+     * @return the {@link ClientTransactionalServiceWrapper} that is tied tot the PhoneAccountHandle
+     */
+    public ClientTransactionalServiceWrapper addNewCallForTransactionalServiceWrapper(
+            PhoneAccountHandle phoneAccountHandle) {
+
+        ClientTransactionalServiceWrapper service = null;
+        if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+            service = new ClientTransactionalServiceWrapper(phoneAccountHandle, this);
+        } else {
+            service = getTransactionalServiceWrapper(phoneAccountHandle);
+        }
+
+        LOOKUP_TABLE.put(phoneAccountHandle, service);
+
+        return service;
+    }
+
+    private ClientTransactionalServiceWrapper getTransactionalServiceWrapper(
+            PhoneAccountHandle pah) {
+        return LOOKUP_TABLE.get(pah);
+    }
+
+    private boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
+        return LOOKUP_TABLE.containsKey(pah);
+    }
+
+    /**
+     * @param pah that is tied to a particular package with potential tracked calls
+     * @return if the {@link ClientTransactionalServiceWrapper} was successfully removed
+     */
+    public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        LOOKUP_TABLE.remove(pah);
+        return true;
+    }
+
+    /**
+     * @param pah    that is tied to a particular package with potential tracked calls
+     * @param callId of the TransactionalCall that you want to remove
+     * @return if the call was successfully removed from the service wrapper
+     */
+    public boolean removeCallFromServiceWrapper(PhoneAccountHandle pah, String callId) {
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        ClientTransactionalServiceWrapper service = LOOKUP_TABLE.get(pah);
+        service.untrackCall(callId);
+        return true;
+    }
+
+}
diff --git a/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
new file mode 100644
index 0000000..16816ff
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ClientTransactionalServiceWrapper.java
@@ -0,0 +1,283 @@
+/*
+ * 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.telecom;
+
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
+
+import android.os.Binder;
+import android.os.OutcomeReceiver;
+import android.os.ResultReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallAudioState;
+import android.telecom.CallControl;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * wraps {@link CallEventCallback} and {@link CallControl} on a
+ * per-{@link  android.telecom.PhoneAccountHandle} basis to track ongoing calls.
+ *
+ * @hide
+ */
+public class ClientTransactionalServiceWrapper {
+
+    private static final String TAG = ClientTransactionalServiceWrapper.class.getSimpleName();
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    private final ClientTransactionalServiceRepository mRepository;
+    private final ConcurrentHashMap<String, TransactionalCall> mCallIdToTransactionalCall =
+            new ConcurrentHashMap<>();
+    private static final String EXECUTOR_FAIL_MSG =
+            "Telecom hit an exception while handling a CallEventCallback on an executor: ";
+
+    public ClientTransactionalServiceWrapper(PhoneAccountHandle handle,
+            ClientTransactionalServiceRepository repo) {
+        mPhoneAccountHandle = handle;
+        mRepository = repo;
+    }
+
+    /**
+     * remove the given call from the class HashMap
+     *
+     * @param callId that is tied to TransactionalCall object
+     */
+    public void untrackCall(String callId) {
+        Log.i(TAG, TextUtils.formatSimple("removeCall: with id=[%s]", callId));
+        if (mCallIdToTransactionalCall.containsKey(callId)) {
+            // remove the call from the hashmap
+            TransactionalCall call = mCallIdToTransactionalCall.remove(callId);
+            // null out interface to avoid memory leaks
+            CallControl control = call.getCallControl();
+            if (control != null) {
+                call.setCallControl(null);
+            }
+        }
+        // possibly cleanup service wrapper if there are no more calls
+        if (mCallIdToTransactionalCall.size() == 0) {
+            mRepository.removeServiceWrapper(mPhoneAccountHandle);
+        }
+    }
+
+    /**
+     * start tracking a newly created call for a particular package
+     *
+     * @param callAttributes of the new call
+     * @param executor       to run callbacks on
+     * @param pendingControl that allows telecom to call into the client
+     * @param callback       that overrides the CallEventCallback
+     * @return the callId of the newly created call
+     */
+    public String trackCall(CallAttributes callAttributes, Executor executor,
+            OutcomeReceiver<CallControl, CallException> pendingControl,
+            CallEventCallback callback) {
+        // generate a new id for this new call
+        String newCallId = UUID.randomUUID().toString();
+
+        // couple the objects passed from the client side
+        mCallIdToTransactionalCall.put(newCallId, new TransactionalCall(newCallId, callAttributes,
+                executor, pendingControl, callback));
+
+        return newCallId;
+    }
+
+    public ICallEventCallback getCallEventCallback() {
+        return mCallEventCallback;
+    }
+
+    /**
+     * Consumers that is to be completed by the client and the result relayed back to telecom server
+     * side via a {@link ResultReceiver}. see com.android.server.telecom.TransactionalServiceWrapper
+     * for how the response is handled.
+     */
+    private class ReceiverWrapper implements Consumer<Boolean> {
+        private final ResultReceiver mRepeaterReceiver;
+
+        ReceiverWrapper(ResultReceiver resultReceiver) {
+            mRepeaterReceiver = resultReceiver;
+        }
+
+        @Override
+        public void accept(Boolean clientCompletedCallbackSuccessfully) {
+            if (clientCompletedCallbackSuccessfully) {
+                mRepeaterReceiver.send(TELECOM_TRANSACTION_SUCCESS, null);
+            } else {
+                mRepeaterReceiver.send(CallException.CODE_ERROR_UNKNOWN, null);
+            }
+        }
+
+        @Override
+        public Consumer<Boolean> andThen(Consumer<? super Boolean> after) {
+            return Consumer.super.andThen(after);
+        }
+    }
+
+    private final ICallEventCallback mCallEventCallback = new ICallEventCallback.Stub() {
+
+        private static final String ON_SET_ACTIVE = "onSetActive";
+        private static final String ON_SET_INACTIVE = "onSetInactive";
+        private static final String ON_ANSWER = "onAnswer";
+        private static final String ON_REJECT = "onReject";
+        private static final String ON_DISCONNECT = "onDisconnect";
+        private static final String ON_STREAMING_STARTED = "onStreamingStarted";
+
+        private void handleCallEventCallback(String action, String callId, int code,
+                ResultReceiver ackResultReceiver) {
+            Log.i(TAG, TextUtils.formatSimple("hCEC: id=[%s], action=[%s]", callId, action));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+            if (call != null) {
+                // Get the CallEventCallback interface
+                CallEventCallback callback = call.getCallEventCallback();
+                // Get Receiver to wait on client ack
+                ReceiverWrapper outcomeReceiverWrapper = new ReceiverWrapper(ackResultReceiver);
+
+                // wait for the client to complete the CallEventCallback
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    call.getExecutor().execute(() -> {
+                        switch (action) {
+                            case ON_SET_ACTIVE:
+                                callback.onSetActive(outcomeReceiverWrapper);
+                                break;
+                            case ON_SET_INACTIVE:
+                                callback.onSetInactive(outcomeReceiverWrapper);
+                                break;
+                            case ON_REJECT:
+                                callback.onReject(outcomeReceiverWrapper);
+                                untrackCall(callId);
+                                break;
+                            case ON_DISCONNECT:
+                                callback.onDisconnect(outcomeReceiverWrapper);
+                                untrackCall(callId);
+                                break;
+                            case ON_ANSWER:
+                                callback.onAnswer(code, outcomeReceiverWrapper);
+                                break;
+                            case ON_STREAMING_STARTED:
+                                callback.onCallStreamingStarted(outcomeReceiverWrapper);
+                                break;
+                        }
+                    });
+                } catch (Exception e) {
+                    Log.e(TAG, EXECUTOR_FAIL_MSG + e);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }
+        }
+
+        @Override
+        public void onAddCallControl(String callId, int resultCode, ICallControl callControl,
+                CallException transactionalException) {
+            Log.i(TAG, TextUtils.formatSimple("oACC: id=[%s], code=[%d]", callId, resultCode));
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+
+            if (call != null) {
+                OutcomeReceiver<CallControl, CallException> pendingControl =
+                        call.getPendingControl();
+
+                if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
+
+                    // create the interface object that the client will interact with
+                    CallControl control = new CallControl(callId, callControl, mRepository,
+                            mPhoneAccountHandle);
+                    // give the client the object via the OR that was passed into addCall
+                    pendingControl.onResult(control);
+
+                    // store for later reference
+                    call.setCallControl(control);
+                } else {
+                    pendingControl.onError(transactionalException);
+                    mCallIdToTransactionalCall.remove(callId);
+                }
+
+            } else {
+                untrackCall(callId);
+                Log.e(TAG, "oACC: TransactionalCall object not found for call w/ id=" + callId);
+            }
+        }
+
+        @Override
+        public void onSetActive(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_SET_ACTIVE, callId, 0, resultReceiver);
+        }
+
+
+        @Override
+        public void onSetInactive(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_SET_INACTIVE, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onAnswer(String callId, int videoState, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_ANSWER, callId, videoState, resultReceiver);
+        }
+
+        @Override
+        public void onReject(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_REJECT, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onDisconnect(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_DISCONNECT, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onCallAudioStateChanged(String callId, CallAudioState callAudioState) {
+            Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s]", callId));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+            if (call != null) {
+                CallEventCallback callback = call.getCallEventCallback();
+                Executor executor = call.getExecutor();
+                executor.execute(() -> callback.onCallAudioStateChanged(callAudioState));
+            }
+        }
+
+        @Override
+        public void removeCallFromTransactionalServiceWrapper(String callId) {
+            untrackCall(callId);
+        }
+
+        @Override
+        public void onCallStreamingStarted(String callId, ResultReceiver resultReceiver) {
+            handleCallEventCallback(ON_STREAMING_STARTED, callId, 0, resultReceiver);
+        }
+
+        @Override
+        public void onCallStreamingFailed(String callId, int reason) {
+            Log.i(TAG, TextUtils.formatSimple("onCallAudioStateChanged: callId=[%s], reason=[%s]",
+                    callId, reason));
+            // lookup the callEventCallback associated with the particular call
+            TransactionalCall call = mCallIdToTransactionalCall.get(callId);
+            if (call != null) {
+                CallEventCallback callback = call.getCallEventCallback();
+                Executor executor = call.getExecutor();
+                executor.execute(() -> callback.onCallStreamingFailed(reason));
+            }
+        }
+    };
+}
diff --git a/telecomm/java/com/android/internal/telecom/ICallControl.aidl b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
new file mode 100644
index 0000000..dc0aeac
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallControl.aidl
@@ -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.internal.telecom;
+
+import android.telecom.CallControl;
+import android.telecom.DisconnectCause;
+import android.os.ResultReceiver;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallControl {
+    void setActive(String callId, in ResultReceiver callback);
+    void setInactive(String callId, in ResultReceiver callback);
+    void disconnect(String callId, in DisconnectCause disconnectCause, in ResultReceiver callback);
+    void rejectCall(String callId, in ResultReceiver callback);
+    void startCallStreaming(String callId, in ResultReceiver callback);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
new file mode 100644
index 0000000..c45ef97
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallEventCallback.aidl
@@ -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.internal.telecom;
+
+import android.telecom.CallControl;
+import com.android.internal.telecom.ICallControl;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallException;
+
+/**
+ * {@hide}
+ */
+oneway interface ICallEventCallback {
+    // publicly exposed. Client should override
+    void onAddCallControl(String callId, int resultCode, in ICallControl callControl,
+     in CallException exception);
+    void onSetActive(String callId, in ResultReceiver callback);
+    void onSetInactive(String callId, in ResultReceiver callback);
+    void onAnswer(String callId, int videoState, in ResultReceiver callback);
+    void onReject(String callId, in ResultReceiver callback);
+    void onDisconnect(String callId, in ResultReceiver callback);
+    void onCallAudioStateChanged(String callId, in CallAudioState callAudioState);
+    // Streaming related. Client registered call streaming capabilities should override
+    void onCallStreamingStarted(String callId, in ResultReceiver callback);
+    void onCallStreamingFailed(String callId, int reason);
+    // hidden methods that help with cleanup
+    void removeCallFromTransactionalServiceWrapper(String callId);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
new file mode 100644
index 0000000..6d53fd2
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/ICallStreamingService.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.telecom;
+
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Internal remote interface for call streaming services.
+ *
+ * @see android.telecom.CallStreamingService
+ *
+ * {@hide}
+ */
+oneway interface ICallStreamingService {
+    void setStreamingCallAdapter(in IStreamingCallAdapter streamingCallAdapter);
+    void onCallStreamingStarted(in StreamingCall call);
+    void onCallStreamingStopped();
+    void onCallStreamingStateChanged(int state);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
new file mode 100644
index 0000000..51424a6
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/IStreamingCallAdapter.aidl
@@ -0,0 +1,28 @@
+/*
+ * 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.telecom;
+
+/**
+ * Internal remote callback interface for call streaming services.
+ *
+ * @see android.telecom.StreamingCallAdapter
+ *
+ * {@hide}
+ */
+oneway interface IStreamingCallAdapter {
+    void setStreamingState(int state);
+}
\ No newline at end of file
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index f1a6dd1..fdcb974 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -25,6 +25,8 @@
 import android.os.UserHandle;
 import android.telecom.PhoneAccount;
 import android.content.pm.ParceledListSlice;
+import android.telecom.CallAttributes;
+import com.android.internal.telecom.ICallEventCallback;
 
 /**
  * Interface used to interact with Telecom. Mostly this is used by TelephonyManager for passing
@@ -391,4 +393,10 @@
      */
     boolean isInSelfManagedCall(String packageName, in UserHandle userHandle,
         String callingPackage);
+
+    /**
+     * @see TelecomServiceImpl#addCall
+     */
+    void addCall(in CallAttributes callAttributes, in ICallEventCallback callback, String callId,
+        String callingPackage);
 }
diff --git a/telecomm/java/com/android/internal/telecom/TransactionalCall.java b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
new file mode 100644
index 0000000..d9c8210
--- /dev/null
+++ b/telecomm/java/com/android/internal/telecom/TransactionalCall.java
@@ -0,0 +1,76 @@
+/*
+ * 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.telecom;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallEventCallback;
+import android.telecom.CallException;
+
+import java.util.concurrent.Executor;
+
+/**
+ * @hide
+ */
+public class TransactionalCall {
+
+    private final String mCallId;
+    private final CallAttributes mCallAttributes;
+    private final Executor mExecutor;
+    private final OutcomeReceiver<CallControl, CallException> mPendingControl;
+    private final CallEventCallback mCallEventCallback;
+    private CallControl mCallControl;
+
+    public TransactionalCall(String callId, CallAttributes callAttributes,
+            Executor executor, OutcomeReceiver<CallControl, CallException>  pendingControl,
+            CallEventCallback callEventCallback) {
+        mCallId = callId;
+        mCallAttributes = callAttributes;
+        mExecutor = executor;
+        mPendingControl = pendingControl;
+        mCallEventCallback = callEventCallback;
+    }
+
+    public void setCallControl(CallControl callControl) {
+        mCallControl = callControl;
+    }
+
+    public CallControl getCallControl() {
+        return mCallControl;
+    }
+
+    public String getCallId() {
+        return mCallId;
+    }
+
+    public CallAttributes getCallAttributes() {
+        return mCallAttributes;
+    }
+
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    public OutcomeReceiver<CallControl, CallException> getPendingControl() {
+        return mPendingControl;
+    }
+
+    public CallEventCallback getCallEventCallback() {
+        return mCallEventCallback;
+    }
+}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index fdf69430..f90eabc 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -18,6 +18,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.Context;
@@ -822,4 +823,35 @@
         }
         return Integer.MAX_VALUE;
     }
+
+    /**
+     * Check if calling user is associated with the given subscription.
+     * @param context Context
+     * @param subId subscription ID
+     * @param callerUserHandle caller user handle
+     * @return  false if user is not associated with the subscription.
+     */
+    public static boolean checkSubscriptionAssociatedWithUser(@NonNull Context context, int subId,
+            @NonNull UserHandle callerUserHandle) {
+        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+            // No subscription on device, return true.
+            return true;
+        }
+
+        SubscriptionManager subManager = context.getSystemService(SubscriptionManager.class);
+        final long token = Binder.clearCallingIdentity();
+        try {
+            if ((subManager != null) &&
+                    (!subManager.isSubscriptionAssociatedWithUser(subId, callerUserHandle))) {
+                // If subId is not associated with calling user, return false.
+                Log.e(LOG_TAG,"User[User ID:" + callerUserHandle.getIdentifier()
+                        + "] is not associated with Subscription ID:" + subId);
+                return false;
+
+            }
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+        return true;
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a5203c4..6f462b1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -4468,6 +4468,18 @@
             "data_switch_validation_timeout_long";
 
     /**
+     * The minimum timeout of UDP port 4500 NAT / firewall entries on the Internet PDN of this
+     * carrier network. This will be used by Android platform VPNs to tune IPsec NAT keepalive
+     * interval. If this value is too low to provide uninterrupted inbound connectivity, then
+     * Android system VPNs may indicate to applications that the VPN cannot support long-lived
+     * TCP connections.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static final String KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT =
+            "min_udp_port_4500_nat_timeout_sec_int";
+
+    /**
      * Specifies whether the system should prefix the EAP method to the anonymous identity.
      * The following prefix will be added if this key is set to TRUE:
      *   EAP-AKA: "0"
@@ -7036,6 +7048,79 @@
         public static final String KEY_SMS_OVER_IMS_SUPPORTED_RATS_INT_ARRAY =
                 KEY_PREFIX + "sms_over_ims_supported_rats_int_array";
 
+        /**
+         * Maximum Retry Count for Failure, If the Retry Count exceeds this value,
+         * it must display to User Interface as sending failed
+         */
+        public static final String KEY_SMS_MAX_RETRY_COUNT_INT =
+                KEY_PREFIX + "sms_max_retry_count_int";
+
+        /**
+         * Maximum Retry Count for SMS over IMS on Failure, If the Retry Count exceeds this value,
+         * and if the retry count is less than KEY_SMS_MAX_RETRY_COUNT_INT
+         * sending SMS should fallback to CS
+         */
+        public static final String KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT =
+                KEY_PREFIX + "sms_max_retry_count_over_ims_int";
+
+        /**
+         * Delay Timer Value in milliseconds
+         * Retry SMS over IMS after this Timer expires
+         */
+        public static final String KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT =
+                KEY_PREFIX + "sms_rover_ims_send_retry_delay_millis_int";
+
+        /**
+         * TR1 Timer Value in milliseconds,
+         * Waits for RP-Ack from network for MO SMS.
+         */
+        public static final String KEY_SMS_TR1_TIMER_MILLIS_INT =
+                KEY_PREFIX + "sms_tr1_timer_millis_int";
+
+        /**
+         * TR2 Timer Value in milliseconds,
+         * Waits for RP-Ack from Transfer Layer for MT SMS.
+         */
+        public static final String KEY_SMS_TR2_TIMER_MILLIS_INT =
+                KEY_PREFIX + "sms_tr2_timer_millis_int";
+
+        /**
+         * SMS RP-Cause Values for which SMS should be retried over IMS
+         *
+         * <p>Possible values are,
+         * {@link SmsManager#SMS_RP_CAUSE_UNALLOCATED_NUMBER}
+         * {@link SmsManager#SMS_RP_CAUSE_OPERATOR_DETERMINED_BARRING}
+         * {@link SmsManager#SMS_RP_CAUSE_CALL_BARRING}
+         * {@link SmsManager#SMS_RP_CAUSE_RESERVED}
+         * {@link SmsManager#SMS_RP_CAUSE_SHORT_MESSAGE_TRANSFER_REJECTED}
+         * {@link SmsManager#SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER}
+         * {@link SmsManager#SMS_RP_CAUSE_UNIDENTIFIED_SUBSCRIBER}
+         * {@link SmsManager#SMS_RP_CAUSE_FACILITY_REJECTED}
+         * {@link SmsManager#SMS_RP_CAUSE_UNKNOWN_SUBSCRIBER}
+         * {@link SmsManager#SMS_RP_CAUSE_NETWORK_OUT_OF_ORDER}
+         * {@link SmsManager#SMS_RP_CAUSE_TEMPORARY_FAILURE}
+         * {@link SmsManager#SMS_RP_CAUSE_CONGESTION}
+         * {@link SmsManager#SMS_RP_CAUSE_RESOURCES_UNAVAILABLE}
+         * {@link SmsManager#SMS_RP_CAUSE_FACILITY_NOT_SUBSCRIBED}
+         * {@link SmsManager#SMS_RP_CAUSE_FACILITY_NOT_IMPLEMENTED}
+         * {@link SmsManager#SMS_RP_CAUSE_INVALID_MESSAGE_REFERENCE_VALUE}
+         * {@link SmsManager#SMS_RP_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE}
+         * {@link SmsManager#SMS_RP_CAUSE_INVALID_MANDATORY_INFORMATION}
+         * {@link SmsManager#SMS_RP_CAUSE_MESSAGE_TYPE_NON_EXISTENT}
+         * {@link SmsManager#SMS_RP_CAUSE_MESSAGE_INCOMPATIBLE_WITH_PROTOCOL_STATE}
+         * {@link SmsManager#SMS_RP_CAUSE_INFORMATION_ELEMENT_NON_EXISTENT}
+         * {@link SmsManager#SMS_RP_CAUSE_PROTOCOL_ERROR}
+         * {@link SmsManager#SMS_RP_CAUSE_INTERWORKING_UNSPECIFIED
+         */
+        public static final String KEY_SMS_RP_CAUSE_VALUES_TO_RETRY_OVER_IMS_INT_ARRAY =
+                KEY_PREFIX + "sms_rp_cause_values_to_retry_over_ims_int_array";
+
+        /**
+         * SMS RP-Cause Values for which Sending SMS should fallback
+         */
+        public static final String KEY_SMS_RP_CAUSE_VALUES_TO_FALLBACK_INT_ARRAY =
+                KEY_PREFIX + "sms_rp_cause_values_to_fallback_int_array";
+
         private static PersistableBundle getDefaults() {
             PersistableBundle defaults = new PersistableBundle();
             defaults.putBoolean(KEY_SMS_OVER_IMS_SUPPORTED_BOOL, true);
@@ -7043,6 +7128,45 @@
 
             defaults.putInt(KEY_SMS_OVER_IMS_FORMAT_INT, SMS_FORMAT_3GPP);
 
+            defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_INT, 3);
+            defaults.putInt(KEY_SMS_MAX_RETRY_COUNT_OVER_IMS_INT, 3);
+            defaults.putInt(KEY_SMS_OVER_IMS_SEND_RETRY_DELAY_MILLIS_INT,
+                    2000);
+            defaults.putInt(KEY_SMS_TR1_TIMER_MILLIS_INT, 130000);
+            defaults.putInt(KEY_SMS_TR2_TIMER_MILLIS_INT, 15000);
+
+            defaults.putIntArray(
+                    KEY_SMS_RP_CAUSE_VALUES_TO_RETRY_OVER_IMS_INT_ARRAY,
+                    new int[] {
+                        SmsManager.SMS_RP_CAUSE_TEMPORARY_FAILURE
+                    });
+            defaults.putIntArray(
+                    KEY_SMS_RP_CAUSE_VALUES_TO_FALLBACK_INT_ARRAY,
+                    new int[] {
+                        SmsManager.SMS_RP_CAUSE_UNALLOCATED_NUMBER,
+                        SmsManager.SMS_RP_CAUSE_OPERATOR_DETERMINED_BARRING,
+                        SmsManager.SMS_RP_CAUSE_CALL_BARRING,
+                        SmsManager.SMS_RP_CAUSE_RESERVED,
+                        SmsManager.SMS_RP_CAUSE_SHORT_MESSAGE_TRANSFER_REJECTED,
+                        SmsManager.SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER,
+                        SmsManager.SMS_RP_CAUSE_UNIDENTIFIED_SUBSCRIBER,
+                        SmsManager.SMS_RP_CAUSE_FACILITY_REJECTED,
+                        SmsManager.SMS_RP_CAUSE_UNKNOWN_SUBSCRIBER,
+                        SmsManager.SMS_RP_CAUSE_NETWORK_OUT_OF_ORDER,
+                        SmsManager.SMS_RP_CAUSE_CONGESTION,
+                        SmsManager.SMS_RP_CAUSE_RESOURCES_UNAVAILABLE,
+                        SmsManager.SMS_RP_CAUSE_FACILITY_NOT_SUBSCRIBED,
+                        SmsManager.SMS_RP_CAUSE_FACILITY_NOT_IMPLEMENTED,
+                        SmsManager.SMS_RP_CAUSE_INVALID_MESSAGE_REFERENCE_VALUE,
+                        SmsManager.SMS_RP_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE,
+                        SmsManager.SMS_RP_CAUSE_INVALID_MANDATORY_INFORMATION,
+                        SmsManager.SMS_RP_CAUSE_MESSAGE_TYPE_NON_EXISTENT,
+                        SmsManager.SMS_RP_CAUSE_MESSAGE_INCOMPATIBLE_WITH_PROTOCOL_STATE,
+                        SmsManager.SMS_RP_CAUSE_INFORMATION_ELEMENT_NON_EXISTENT,
+                        SmsManager.SMS_RP_CAUSE_PROTOCOL_ERROR,
+                        SmsManager.SMS_RP_CAUSE_INTERWORKING_UNSPECIFIED
+                    });
+
             defaults.putIntArray(
                     KEY_SMS_OVER_IMS_SUPPORTED_RATS_INT_ARRAY,
                     new int[] {
@@ -9986,6 +10110,7 @@
         sDefaults.putStringArray(KEY_MMI_TWO_DIGIT_NUMBER_PATTERN_STRING_ARRAY, new String[0]);
         sDefaults.putInt(KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT,
                 CellSignalStrengthLte.USE_RSRP);
+        sDefaults.putInt(KEY_MIN_UDP_PORT_4500_NAT_TIMEOUT_SEC_INT, 300);
         // Default wifi configurations.
         sDefaults.putAll(Wifi.getDefaults());
         sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 8106819..e6f1349 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -297,6 +297,106 @@
      */
     public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
 
+    // RP-Cause Values For MO SMS as per TS 124 011, table 8.4.
+
+    /** @hide */
+    @IntDef(prefix = { "SMS_RP_CAUSE" }, value = {
+        SmsManager.SMS_RP_CAUSE_UNALLOCATED_NUMBER,
+        SmsManager.SMS_RP_CAUSE_OPERATOR_DETERMINED_BARRING,
+        SmsManager.SMS_RP_CAUSE_CALL_BARRING,
+        SmsManager.SMS_RP_CAUSE_RESERVED,
+        SmsManager.SMS_RP_CAUSE_SHORT_MESSAGE_TRANSFER_REJECTED,
+        SmsManager.SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER,
+        SmsManager.SMS_RP_CAUSE_UNIDENTIFIED_SUBSCRIBER,
+        SmsManager.SMS_RP_CAUSE_FACILITY_REJECTED,
+        SmsManager.SMS_RP_CAUSE_UNKNOWN_SUBSCRIBER,
+        SmsManager.SMS_RP_CAUSE_NETWORK_OUT_OF_ORDER,
+        SmsManager.SMS_RP_CAUSE_TEMPORARY_FAILURE,
+        SmsManager.SMS_RP_CAUSE_CONGESTION,
+        SmsManager.SMS_RP_CAUSE_RESOURCES_UNAVAILABLE,
+        SmsManager.SMS_RP_CAUSE_FACILITY_NOT_SUBSCRIBED,
+        SmsManager.SMS_RP_CAUSE_FACILITY_NOT_IMPLEMENTED,
+        SmsManager.SMS_RP_CAUSE_INVALID_MESSAGE_REFERENCE_VALUE,
+        SmsManager.SMS_RP_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE,
+        SmsManager.SMS_RP_CAUSE_INVALID_MANDATORY_INFORMATION,
+        SmsManager.SMS_RP_CAUSE_MESSAGE_TYPE_NON_EXISTENT,
+        SmsManager.SMS_RP_CAUSE_MESSAGE_INCOMPATIBLE_WITH_PROTOCOL_STATE,
+        SmsManager.SMS_RP_CAUSE_INFORMATION_ELEMENT_NON_EXISTENT,
+        SmsManager.SMS_RP_CAUSE_PROTOCOL_ERROR,
+        SmsManager.SMS_RP_CAUSE_INTERWORKING_UNSPECIFIED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SMS_RP_CAUSE {}
+
+    /** Unallocated Number Cause */
+    public static final int SMS_RP_CAUSE_UNALLOCATED_NUMBER = 1;
+
+    /** RP-Cause for Operator Barring */
+    public static final int SMS_RP_CAUSE_OPERATOR_DETERMINED_BARRING = 8;
+
+    /** RP-Cause Value for Call Barring */
+    public static final int SMS_RP_CAUSE_CALL_BARRING = 10;
+
+    /** RP-Cause value for Reserved Number */
+    public static final int SMS_RP_CAUSE_RESERVED = 11;
+
+    /** RP-Cause Value for Message Transfer Rejected by Network */
+    public static final int SMS_RP_CAUSE_SHORT_MESSAGE_TRANSFER_REJECTED = 21;
+
+    /** RP-Cause Value for Destination is Out of Order */
+    public static final int SMS_RP_CAUSE_DESTINATION_OUT_OF_ORDER = 27;
+
+    /** RP-Cause Value when Subscriber is not Identified */
+    public static final int SMS_RP_CAUSE_UNIDENTIFIED_SUBSCRIBER = 28;
+
+    /** RP-Cause Value when SMS Facility if Rejected by Operator */
+    public static final int SMS_RP_CAUSE_FACILITY_REJECTED = 29;
+
+    /** RP-Cause Value when Subscriber is not Identified */
+    public static final int SMS_RP_CAUSE_UNKNOWN_SUBSCRIBER = 30;
+
+    /** RP-Cause Value when network is out of order*/
+    public static final int SMS_RP_CAUSE_NETWORK_OUT_OF_ORDER = 38;
+
+    /** RP-Cause Value For Temporary failure*/
+    public static final int SMS_RP_CAUSE_TEMPORARY_FAILURE = 41;
+
+    /** RP-Cause Value for SMS Failure due to Congestion in network*/
+    public static final int SMS_RP_CAUSE_CONGESTION = 42;
+
+    /** RP-Cause Value when Network Resources are unavailable */
+    public static final int SMS_RP_CAUSE_RESOURCES_UNAVAILABLE = 47;
+
+    /** RP-Cause Value when SMS Facilty is not subscribed by Reote device */
+    public static final int SMS_RP_CAUSE_FACILITY_NOT_SUBSCRIBED = 50;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_FACILITY_NOT_IMPLEMENTED = 69;
+
+    /** RP-Cause Value when RP-MessageRefere */
+    public static final int SMS_RP_CAUSE_INVALID_MESSAGE_REFERENCE_VALUE = 81;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_SEMANTICALLY_INCORRECT_MESSAGE = 95;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_INVALID_MANDATORY_INFORMATION = 96;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_MESSAGE_TYPE_NON_EXISTENT = 97;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_MESSAGE_INCOMPATIBLE_WITH_PROTOCOL_STATE = 98;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_INFORMATION_ELEMENT_NON_EXISTENT  = 99;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_PROTOCOL_ERROR = 111;
+
+    /** RP-Cause Value when network does not provide the received service */
+    public static final int SMS_RP_CAUSE_INTERWORKING_UNSPECIFIED = 127;
+
     /** @hide */
     @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = {
         SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN,
@@ -2249,6 +2349,7 @@
             RESULT_SMS_SEND_RETRY_FAILED,
             RESULT_REMOTE_EXCEPTION,
             RESULT_NO_DEFAULT_SMS_APP,
+            RESULT_USER_NOT_ALLOWED,
             RESULT_RIL_RADIO_NOT_AVAILABLE,
             RESULT_RIL_SMS_SEND_FAIL_RETRY,
             RESULT_RIL_NETWORK_REJECT,
@@ -2443,6 +2544,13 @@
      */
     public static final int RESULT_NO_DEFAULT_SMS_APP = 32;
 
+    /**
+     * User is not associated with the subscription.
+     * TODO(b/263279115): Make this error code public.
+     * @hide
+     */
+    public static final int RESULT_USER_NOT_ALLOWED = 33;
+
     // Radio Error results
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 4afc943..0638189 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4418,4 +4418,69 @@
         }
         return null;
     }
+
+    /**
+     * Check if subscription and user are associated with each other.
+     *
+     * @param subscriptionId the subId of the subscription
+     * @param userHandle user handle of the user
+     * @return {@code true} if subscription is associated with user
+     * {code true} if there are no subscriptions on device
+     * else {@code false} if subscription is not associated with user.
+     *
+     * @throws IllegalArgumentException if subscription is invalid.
+     * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws IllegalStateException if subscription service is not available.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+    public boolean isSubscriptionAssociatedWithUser(int subscriptionId,
+            @NonNull UserHandle userHandle) {
+        if (!isValidSubscriptionId(subscriptionId)) {
+            throw new IllegalArgumentException("[isSubscriptionAssociatedWithUser]: "
+                    + "Invalid subscriptionId: " + subscriptionId);
+        }
+
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                return iSub.isSubscriptionAssociatedWithUser(subscriptionId, userHandle);
+            } else {
+                throw new IllegalStateException("[isSubscriptionAssociatedWithUser]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return false;
+    }
+
+    /**
+     * Get list of subscriptions associated with user.
+     *
+     * @param userHandle user handle of the user
+     * @return list of subscriptionInfo associated with the user.
+     *
+     * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws IllegalStateException if subscription service is not available.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.MANAGE_SUBSCRIPTION_USER_ASSOCIATION)
+    public @NonNull List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(
+            @NonNull UserHandle userHandle) {
+        try {
+            ISub iSub = TelephonyManager.getSubscriptionService();
+            if (iSub != null) {
+                return iSub.getSubscriptionInfoListAssociatedWithUser(userHandle);
+            } else {
+                throw new IllegalStateException("[getSubscriptionInfoListAssociatedWithUser]: "
+                        + "subscription service unavailable");
+            }
+        } catch (RemoteException ex) {
+            ex.rethrowAsRuntimeException();
+        }
+        return new ArrayList<>();
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index c5f6902..25a714a 100644
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -326,4 +326,34 @@
      * @throws IllegalArgumentException if subId is invalid.
      */
      UserHandle getSubscriptionUserHandle(int subId);
+
+     /**
+      * Check if subscription and user are associated with each other.
+      *
+      * @param subscriptionId the subId of the subscription
+      * @param userHandle user handle of the user
+      * @return {@code true} if subscription is associated with user
+      * {code true} if there are no subscriptions on device
+      * else {@code false} if subscription is not associated with user.
+      *
+      * @throws IllegalArgumentException if subscription is invalid.
+      * @throws SecurityException if the caller doesn't have permissions required.
+      * @throws IllegalStateException if subscription service is not available.
+      *
+      * @hide
+      */
+      boolean isSubscriptionAssociatedWithUser(int subscriptionId, in UserHandle userHandle);
+
+      /**
+       * Get list of subscriptions associated with user.
+       *
+       * @param userHandle user handle of the user
+       * @return list of subscriptionInfo associated with the user.
+       *
+       * @throws SecurityException if the caller doesn't have permissions required.
+       * @throws IllegalStateException if subscription service is not available.
+       *
+       * @hide
+       */
+       List<SubscriptionInfo> getSubscriptionInfoListAssociatedWithUser(in UserHandle userHandle);
 }
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index 8fc8c7d..b63fbe6 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -887,6 +887,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public void updateDeviceId(int deviceId) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public int getDeviceId() {
         throw new UnsupportedOperationException();
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 948288a..566ec9a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -23,7 +23,6 @@
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.junit.FlickerBuilderProvider
 import com.android.server.wm.traces.common.ComponentNameMatcher
-import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import org.junit.Assume
 import org.junit.AssumptionViolatedException
 import org.junit.Test
@@ -41,12 +40,6 @@
     protected val tapl: LauncherInstrumentation = LauncherInstrumentation()
 ) {
     init {
-        flicker.scenario.setIsTablet(
-            WindowManagerStateHelper(instrumentation, clearCacheAfterParsing = false)
-                .currentState
-                .wmState
-                .isTablet
-        )
         tapl.setExpectedRotationCheckEnabled(true)
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index f9a245a..f26ce25 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -22,6 +22,8 @@
 import com.android.server.wm.flicker.traces.region.RegionSubject
 import com.android.server.wm.traces.common.ComponentNameMatcher
 import com.android.server.wm.traces.common.IComponentNameMatcher
+import com.android.server.wm.traces.common.service.PlatformConsts
+import com.android.server.wm.traces.common.windowmanager.WindowManagerTrace
 
 /**
  * Checks that [ComponentNameMatcher.STATUS_BAR] window is visible and above the app windows in all
@@ -202,13 +204,15 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the start
  * of the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtStart() {
+fun FlickerTest.statusBarLayerPositionAtStart(
+    wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
+) {
+    // collect navbar position for the equivalent WM state
+    val state = wmTrace?.firstOrNull() ?: error("WM state missing in $this")
+    val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+    val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display)
     assertLayersStart {
-        val display =
-            this.entry.displays.minByOrNull { it.id }
-                ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversExactly(WindowUtils.getStatusBarPosition(display))
+        this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition)
     }
 }
 
@@ -216,13 +220,15 @@
  * Asserts that the [ComponentNameMatcher.STATUS_BAR] layer is at the correct position at the end of
  * the SF trace
  */
-fun FlickerTest.statusBarLayerPositionAtEnd() {
+fun FlickerTest.statusBarLayerPositionAtEnd(
+    wmTrace: WindowManagerTrace? = this.reader.readWmTrace()
+) {
+    // collect navbar position for the equivalent WM state
+    val state = wmTrace?.lastOrNull() ?: error("WM state missing in $this")
+    val display = state.getDisplay(PlatformConsts.DEFAULT_DISPLAY) ?: error("Display not found")
+    val navBarPosition = WindowUtils.getExpectedStatusBarPosition(display)
     assertLayersEnd {
-        val display =
-            this.entry.displays.minByOrNull { it.id }
-                ?: throw RuntimeException("There is no display!")
-        this.visibleRegion(ComponentNameMatcher.STATUS_BAR)
-            .coversExactly(WindowUtils.getStatusBarPosition(display))
+        this.visibleRegion(ComponentNameMatcher.STATUS_BAR).coversExactly(navBarPosition)
     }
 }
 
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
index efe242c..d3a5885 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/MeshActivity.java
@@ -32,6 +32,7 @@
 
 import java.nio.FloatBuffer;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
 
 public class MeshActivity extends Activity {
     @Override
@@ -64,7 +65,9 @@
             vertexBuffer.put(5, 400.0f);
             vertexBuffer.rewind();
             Mesh mesh = Mesh.make(
-                    meshSpec, Mesh.Mode.Triangles, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+                    meshSpec, Mesh.TRIANGLES, vertexBuffer, 3, new Rect(0, 0, 1000, 1000));
+
+            canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
 
             int numTriangles = 100;
             // number of triangles plus first 2 vertices
@@ -95,12 +98,10 @@
             }
             iVertexBuffer.rewind();
             indexBuffer.rewind();
-            Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.Mode.Triangles, iVertexBuffer, 102,
-                    indexBuffer, new Rect(0, 0, 1000, 1000));
-
+            Mesh mesh2 = Mesh.makeIndexed(meshSpec, Mesh.TRIANGLES, iVertexBuffer, 102, indexBuffer,
+                    new Rect(0, 0, 1000, 1000));
             Paint paint = new Paint();
             paint.setColor(Color.RED);
-            canvas.drawMesh(mesh, BlendMode.COLOR, new Paint());
             canvas.drawMesh(mesh2, BlendMode.COLOR, paint);
         }
 
@@ -114,10 +115,9 @@
                     + "      color = vec4(1.0, 0.0, 0.0, 1.0);"
                     + "      return varyings.position;\n"
                     + "}";
-            Attribute[] attList =
-                    new Attribute[] {new Attribute(MeshSpecification.FLOAT2, 0, "position")};
-            Varying[] varyList =
-                    new MeshSpecification.Varying[] {};
+            ArrayList<Attribute> attList = new ArrayList<>();
+            attList.add(new Attribute(MeshSpecification.FLOAT2, 0, "position"));
+            ArrayList<Varying> varyList = new ArrayList<>();
             return MeshSpecification.make(attList, 8, varyList, vs, fs);
         }
     }
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 8e4ecf1..9b0f952 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -61,10 +61,11 @@
 public final class AutoShowTest {
 
     @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
-
-    @Rule
-    public ScreenCaptureRule mScreenCaptureRule =
+    @Rule public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+    @Rule public DisableLockScreenRule mDisableLockScreenRule = new DisableLockScreenRule();
+    @Rule public ScreenOrientationRule mScreenOrientationRule =
+            new ScreenOrientationRule(true /* isPortrait */);
 
     // TODO(b/240359838): add test case {@code Configuration.SCREENLAYOUT_SIZE_LARGE}.
     @Parameterized.Parameters(
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DisableLockScreenRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DisableLockScreenRule.java
new file mode 100644
index 0000000..d95decf
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DisableLockScreenRule.java
@@ -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.inputmethod.stresstest;
+
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+
+/** Disable lock screen during the test. */
+public class DisableLockScreenRule extends TestWatcher {
+    private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
+    private static final String LOCK_SCREEN_ON_COMMAND = "locksettings set-disabled false";
+
+    private final UiDevice mUiDevice =
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+    @Override
+    protected void starting(Description description) {
+        try {
+            mUiDevice.executeShellCommand(LOCK_SCREEN_OFF_COMMAND);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not disable lock screen.", e);
+        }
+    }
+
+    @Override
+    protected void finished(Description description) {
+        try {
+            mUiDevice.executeShellCommand(LOCK_SCREEN_ON_COMMAND);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not enable lock screen.", e);
+        }
+    }
+}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index 82acfb6..a6131ce 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -67,10 +67,11 @@
     private static final int NUM_TEST_ITERATIONS = 10;
 
     @Rule public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule();
-
-    @Rule
-    public ScreenCaptureRule mScreenCaptureRule =
+    @Rule public ScreenCaptureRule mScreenCaptureRule =
             new ScreenCaptureRule("/sdcard/InputMethodStressTest");
+    @Rule public DisableLockScreenRule mDisableLockScreenRule = new DisableLockScreenRule();
+    @Rule public ScreenOrientationRule mScreenOrientationRule =
+            new ScreenOrientationRule(true /* isPortrait */);
 
     private final Instrumentation mInstrumentation;
     private final int mSoftInputFlags;
@@ -485,7 +486,6 @@
 
         UiDevice uiDevice = UiDevice.getInstance(mInstrumentation);
 
-        uiDevice.freezeRotation();
         uiDevice.setOrientationRight();
         uiDevice.waitForIdle();
         Thread.sleep(1000);
@@ -502,7 +502,6 @@
 
         uiDevice.setOrientationNatural();
         uiDevice.waitForIdle();
-        uiDevice.unfreezeRotation();
     }
 
     private static void verifyShowBehavior(TestActivity activity) {
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenOrientationRule.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenOrientationRule.java
new file mode 100644
index 0000000..bc3b1ef
--- /dev/null
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ScreenOrientationRule.java
@@ -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.inputmethod.stresstest;
+
+import android.os.RemoteException;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+import java.io.IOException;
+
+/**
+ * Disable auto-rotate during the test and set the screen orientation to portrait or landscape
+ * before the test starts.
+ */
+public class ScreenOrientationRule extends TestWatcher {
+    private static final String SET_PORTRAIT_MODE_CMD = "settings put system user_rotation 0";
+    private static final String SET_LANDSCAPE_MODE_CMD = "settings put system user_rotation 1";
+
+    private final boolean mIsPortrait;
+    private final UiDevice mUiDevice =
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+    ScreenOrientationRule(boolean isPortrait) {
+        mIsPortrait = isPortrait;
+    }
+
+    @Override
+    protected void starting(Description description) {
+        try {
+            mUiDevice.freezeRotation();
+            mUiDevice.executeShellCommand(mIsPortrait ? SET_PORTRAIT_MODE_CMD :
+                    SET_LANDSCAPE_MODE_CMD);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not set screen orientation.", e);
+        } catch (RemoteException e) {
+            throw new RuntimeException("Could not freeze rotation.", e);
+        }
+    }
+
+    @Override
+    protected void finished(Description description) {
+        try {
+            mUiDevice.unfreezeRotation();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Could not unfreeze screen rotation.", e);
+        }
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
new file mode 100644
index 0000000..b24ac3c
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/app/AppLocaleCollectorTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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.app;
+
+import static com.android.internal.app.AppLocaleStore.AppLocaleResult.LocaleStatus.GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyObject;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.LocaleStore.LocaleInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Unit tests for the {@link AppLocaleCollector}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppLocaleCollectorTest {
+    private static final String TAG = "AppLocaleCollectorTest";
+    private AppLocaleCollector mAppLocaleCollector;
+    private LocaleStore.LocaleInfo mAppCurrentLocale;
+    private Set<LocaleInfo> mAllAppActiveLocales;
+    private Set<LocaleInfo> mImeLocales;
+    private List<LocaleInfo> mSystemCurrentLocales;
+    private Set<LocaleInfo> mSystemSupportedLocales;
+    private AppLocaleStore.AppLocaleResult mResult;
+    private static final String PKG1 = "pkg1";
+    private static final int NONE = LocaleInfo.SUGGESTION_TYPE_NONE;
+    private static final int SIM = LocaleInfo.SUGGESTION_TYPE_SIM;
+    private static final int CFG = LocaleInfo.SUGGESTION_TYPE_CFG;
+    private static final int SIM_CFG = SIM | CFG;
+    private static final int CURRENT = LocaleInfo.SUGGESTION_TYPE_CURRENT;
+    private static final int SYSTEM = LocaleInfo.SUGGESTION_TYPE_SYSTEM_LANGUAGE;
+    private static final int OTHERAPP = LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE;
+
+    @Before
+    public void setUp() throws Exception {
+        mAppLocaleCollector = spy(
+                new AppLocaleCollector(InstrumentationRegistry.getContext(), PKG1));
+
+        mAppCurrentLocale = createLocaleInfo("en-US", CURRENT);
+        mAllAppActiveLocales = initAllAppActivatedLocales();
+        mImeLocales = initImeLocales();
+        mSystemSupportedLocales = initSystemSupportedLocales();
+        mSystemCurrentLocales = initSystemCurrentLocales();
+        mResult = new AppLocaleStore.AppLocaleResult(GET_SUPPORTED_LANGUAGE_FROM_LOCAL_CONFIG,
+                initAppSupportedLocale());
+    }
+
+    @Test
+    public void testGetSupportedLocaleList() {
+        doReturn(mAppCurrentLocale).when(mAppLocaleCollector).getAppCurrentLocale();
+        doReturn(mResult).when(mAppLocaleCollector).getAppSupportedLocales();
+        doReturn(mAllAppActiveLocales).when(mAppLocaleCollector).getAllAppActiveLocales();
+        doReturn(mImeLocales).when(mAppLocaleCollector).getActiveImeLocales();
+        doReturn(mSystemSupportedLocales).when(mAppLocaleCollector).getSystemSupportedLocale(
+                anyObject(), eq(null), eq(true));
+        doReturn(mSystemCurrentLocales).when(mAppLocaleCollector).getSystemCurrentLocale();
+
+        Set<LocaleInfo> result = mAppLocaleCollector.getSupportedLocaleList(null, true, false);
+
+        HashMap<String, Integer> expectedResult = getExpectedResult();
+        assertEquals(result.size(), expectedResult.size());
+        for (LocaleInfo source : result) {
+            int suggestionFlags = expectedResult.getOrDefault(source.getId(), -1);
+            assertEquals(source.mSuggestionFlags, suggestionFlags);
+        }
+    }
+
+    private HashMap<String, Integer> getExpectedResult() {
+        HashMap<String, Integer> map = new HashMap<>();
+        map.put("en-US", CURRENT); // The locale current App activates.
+        map.put("fr", NONE); // The locale App and system support.
+        map.put("zu", NONE); // The locale App and system support.
+        map.put("en", NONE); // Use en because System supports en while APP supports en-CA, en-GB.
+        map.put("ko", NONE); // The locale App and system support.
+        map.put("en-AU", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("en-CA", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("ja-JP", OTHERAPP); // The locale other App activates and current App supports.
+        map.put("zh-Hant-TW", SIM);  // The locale system activates.
+        map.put(createLocaleInfo("", SYSTEM).getId(), SYSTEM); // System language title
+        return map;
+    }
+
+    private Set<LocaleInfo> initSystemSupportedLocales() {
+        return Set.of(
+                createLocaleInfo("en", NONE),
+                createLocaleInfo("fr", NONE),
+                createLocaleInfo("zu", NONE),
+                createLocaleInfo("ko", NONE),
+                // will be filtered because current App doesn't support.
+                createLocaleInfo("es-US", SIM_CFG)
+        );
+    }
+
+    private List<LocaleInfo> initSystemCurrentLocales() {
+        return List.of(createLocaleInfo("zh-Hant-TW", SIM),
+                // will be filtered because current App activates this locale.
+                createLocaleInfo("en-US", SIM));
+    }
+
+    private Set<LocaleInfo> initAllAppActivatedLocales() {
+        return Set.of(
+                createLocaleInfo("en-CA", OTHERAPP),
+                createLocaleInfo("en-AU", OTHERAPP),
+                createLocaleInfo("ja-JP", OTHERAPP),
+                // will be filtered because current App activates this locale.
+                createLocaleInfo("en-US", OTHERAPP));
+    }
+
+    private Set<LocaleInfo> initImeLocales() {
+        return Set.of(
+                // will be filtered because system activates zh-Hant-TW.
+                createLocaleInfo("zh-TW", OTHERAPP),
+                // will be filtered because current App's activats this locale.
+                createLocaleInfo("en-US", OTHERAPP));
+    }
+
+    private HashSet<Locale> initAppSupportedLocale() {
+        HashSet<Locale> hs = new HashSet();
+        hs.add(Locale.forLanguageTag("en-US"));
+        hs.add(Locale.forLanguageTag("en-CA"));
+        hs.add(Locale.forLanguageTag("en-GB"));
+        hs.add(Locale.forLanguageTag("zh-TW"));
+        hs.add(Locale.forLanguageTag("ja"));
+        hs.add(Locale.forLanguageTag("fr"));
+        hs.add(Locale.forLanguageTag("zu"));
+        hs.add(Locale.forLanguageTag("ko"));
+        // will be filtered because it's not in the system language.
+        hs.add(Locale.forLanguageTag("mn"));
+        return hs;
+    }
+
+    private LocaleInfo createLocaleInfo(String languageTag, int suggestionFlag) {
+        LocaleInfo localeInfo = LocaleStore.fromLocale(Locale.forLanguageTag(languageTag));
+        localeInfo.mSuggestionFlags = suggestionFlag;
+        localeInfo.setTranslated(true);
+        return localeInfo;
+    }
+}
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
new file mode 100644
index 0000000..bf6ece1
--- /dev/null
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.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.internal.app;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.view.inputmethod.InputMethodSubtype;
+import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.app.LocaleStore.LocaleInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for the {@link LocaleStore}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class LocaleStoreTest {
+    @Before
+    public void setUp() {
+    }
+
+    @Test
+    public void testTransformImeLanguageTagToLocaleInfo() {
+        List<InputMethodSubtype> list = List.of(
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+        Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+        Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+        assertEquals(localeSet.size(), expectedLanguageTag.size());
+        for (LocaleInfo info : localeSet) {
+            assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_OTHER_APP_LANGUAGE);
+            assertTrue(expectedLanguageTag.contains(info.getId()));
+        }
+    }
+}
diff --git a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
index afaeca1..00fc498 100644
--- a/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
+++ b/tests/JobSchedulerPerfTests/src/com/android/frameworks/perftests/job/JobStorePerfTests.java
@@ -208,6 +208,6 @@
                 .setPersisted(true)
                 .build();
         return JobStatus.createFromJobInfo(
-                jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
+                jobInfo, callingUid, SOURCE_PACKAGE, SOURCE_USER_ID, null, testTag);
     }
 }
diff --git a/tools/lint/README.md b/tools/lint/README.md
index 99149c1..b235ad6 100644
--- a/tools/lint/README.md
+++ b/tools/lint/README.md
@@ -1,15 +1,44 @@
-# Android Framework Lint Checker
+# Android Lint Checks for AOSP
 
-Custom lint checks written here are going to be executed for modules that opt in to those (e.g. any
+Custom Android Lint checks are written here to be executed against java modules
+in AOSP. These checks are broken down into two subdirectories:
+
+1. [Global Checks](#android-global-lint-checker)
+2. [Framework Checks](#android-framework-lint-checker)
+
+# [Android Global Lint Checker](/global)
+Checks written here are executed for the entire tree. The `AndroidGlobalLintChecker`
+build target produces a jar file that is included in the overall build output
+(`AndroidGlobalLintChecker.jar`).  This file is then downloaded as a prebuilt under the
+`prebuilts/cmdline-tools` subproject, and included by soong with all invocations of lint.
+
+## How to add new global lint checks
+1. Write your detector with its issues and put it into
+   `global/checks/src/main/java/com/google/android/lint`.
+2. Add your detector's issues into `AndroidGlobalIssueRegistry`'s `issues`
+   field.
+3. Write unit tests for your detector in one file and put it into
+   `global/checks/test/java/com/google/android/lint`.
+4. Have your change reviewed and merged.  Once your change is merged,
+   obtain a build number from a successful build that includes your change.
+5. Run `prebuilts/cmdline-tools/update-android-global-lint-checker.sh
+   <build_number>`. The script will create a commit that you can upload for
+   approval to the `prebuilts/cmdline-tools` subproject.
+6. Done! Your lint check should be applied in lint report builds across the
+   entire tree!
+
+# [Android Framework Lint Checker](/framework)
+
+Checks written here are going to be executed for modules that opt in to those (e.g. any
 `services.XXX` module) and results will be automatically reported on CLs on gerrit.
 
-## How to add new lint checks
+## How to add new framework lint checks
 
 1. Write your detector with its issues and put it into
-   `checks/src/main/java/com/google/android/lint`.
+   `framework/checks/src/main/java/com/google/android/lint`.
 2. Add your detector's issues into `AndroidFrameworkIssueRegistry`'s `issues` field.
 3. Write unit tests for your detector in one file and put it into
-   `checks/test/java/com/google/android/lint`.
+   `framework/checks/test/java/com/google/android/lint`.
 4. Done! Your lint checks should be applied in lint report builds for modules that include
    `AndroidFrameworkLintChecker`.
 
@@ -44,11 +73,11 @@
   environment variable with the id of the lint. For example:
   `ANDROID_LINT_CHECK=UnusedTokenOfOriginalCallingIdentity m out/[...]/lint-report.html`
 
-## How to apply automatic fixes suggested by lint
+# How to apply automatic fixes suggested by lint
 
 See [lint_fix](fix/README.md)
 
-## Create or update a baseline
+# Create or update a baseline
 
 Baseline files can be used to silence known errors (and warnings) that are deemed to be safe. When
 there is a lint-baseline.xml file in the root folder of the java library, soong will
@@ -79,7 +108,7 @@
 [lint.go](http://cs/aosp-master/build/soong/java/lint.go;l=451;rcl=2e778d5bc4a8d1d77b4f4a3029a4a254ad57db75)
 adding `cmd.Flag("--nowarn")` and running lint again.
 
-## Documentation
+# Documentation
 
 - [Android Lint Docs](https://googlesamples.github.io/android-custom-lint-rules/)
 - [Lint Check Unit Testing](https://googlesamples.github.io/android-custom-lint-rules/api-guide/unit-testing.md.html)
diff --git a/wifi/java/src/android/net/wifi/WifiKeystore.java b/wifi/java/src/android/net/wifi/WifiKeystore.java
new file mode 100644
index 0000000..ca86dde
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/WifiKeystore.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 android.net.wifi;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.security.legacykeystore.ILegacyKeystore;
+import android.util.Log;
+
+/**
+ * @hide This class allows wifi framework to store and access wifi certificate blobs.
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class WifiKeystore {
+    private static final String TAG = "WifiKeystore";
+    private static final String LEGACY_KEYSTORE_SERVICE_NAME = "android.security.legacykeystore";
+
+    private static ILegacyKeystore getService() {
+        return ILegacyKeystore.Stub.asInterface(
+                ServiceManager.checkService(LEGACY_KEYSTORE_SERVICE_NAME));
+    }
+
+    /** @hide */
+    WifiKeystore() {
+    }
+
+    /**
+     * Stores the blob under the alias in the keystore database. Existing blobs by the
+     * same name will be replaced.
+     * @param alias The name of the blob
+     * @param blob The blob.
+     * @return true if the blob was successfully added. False otherwise.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static boolean put(@NonNull String alias, @NonNull byte[] blob) {
+        try {
+            Log.i(TAG, "put blob. alias " + alias);
+            getService().put(alias, Process.WIFI_UID, blob);
+            return true;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to put blob.", e);
+            return false;
+        }
+    }
+
+    /**
+     * Retrieves a blob by the name alias from the blob database.
+     * @param alias Name of the blob to retrieve.
+     * @return The unstructured blob, that is the blob that was stored using
+     *         {@link android.net.wifi.WifiKeystore#put}.
+     *         Returns null if no blob was found.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static @NonNull byte[] get(@NonNull String alias) {
+        try {
+            Log.i(TAG, "get blob. alias " + alias);
+            return getService().get(alias, Process.WIFI_UID);
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode != ILegacyKeystore.ERROR_ENTRY_NOT_FOUND) {
+                Log.e(TAG, "Failed to get blob.", e);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to get blob.", e);
+        }
+        return null;
+    }
+
+    /**
+     * Removes a blob by the name alias from the database.
+     * @param alias Name of the blob to be removed.
+     * @return True if a blob was removed. False if no such blob was found.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static boolean remove(@NonNull String alias) {
+        try {
+            getService().remove(alias, Process.WIFI_UID);
+            return true;
+        } catch (ServiceSpecificException e) {
+            if (e.errorCode != ILegacyKeystore.ERROR_ENTRY_NOT_FOUND) {
+                Log.e(TAG, "Failed to remove blob.", e);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to remove blob.", e);
+        }
+        return false;
+    }
+
+    /**
+     * Lists the blobs stored in the database.
+     * @return An array of strings representing the aliases stored in the database.
+     *         The return value may be empty but never null.
+     * @hide
+     */
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    public static @NonNull String[] list(@NonNull String prefix) {
+        try {
+            final String[] aliases = getService().list(prefix, Process.WIFI_UID);
+            for (int i = 0; i < aliases.length; ++i) {
+                aliases[i] = aliases[i].substring(prefix.length());
+            }
+            return aliases;
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to list blobs.", e);
+        }
+        return new String[0];
+    }
+}
\ No newline at end of file
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 28576bf..2ad5771 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -137,9 +137,17 @@
         void onScanResultReady();
 
         /**
+         * Deprecated in Android 14. Newer wificond implementation should call
+         * onScanRequestFailed().
          * Called when a scan has failed.
+         * @deprecated The usage is replaced by {@link ScanEventCallback#onScanFailed(int)}
          */
+
         void onScanFailed();
+        /**
+         * Called when a scan has failed with errorCode.
+         */
+        default void onScanFailed(int errorCode) {}
     }
 
     /**
@@ -230,6 +238,18 @@
                 Binder.restoreCallingIdentity(token);
             }
         }
+
+        @Override
+        public void OnScanRequestFailed(int errorCode) {
+            Log.d(TAG, "Scan failed event with error code: " + errorCode);
+            final long token = Binder.clearCallingIdentity();
+            try {
+                mExecutor.execute(() -> mCallback.onScanFailed(
+                        toFrameworkScanStatusCode(errorCode)));
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
     }
 
     /**
@@ -1030,6 +1050,32 @@
     }
 
     /**
+     * @deprecated replaced by {@link #startScan2(String, int, Set, List, Bundle)}
+     */
+    @Deprecated
+    public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+            @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+            @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+            @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
+        IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
+        if (scannerImpl == null) {
+            Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
+            return false;
+        }
+        SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs,
+                extraScanningParams);
+        if (settings == null) {
+            return false;
+        }
+        try {
+            return scannerImpl.scan(settings);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request scan due to remote exception");
+        }
+        return false;
+    }
+
+    /**
      * Start a scan using the specified parameters. A scan is an asynchronous operation. The
      * result of the operation is returned in the {@link ScanEventCallback} registered when
      * setting up an interface using
@@ -1049,24 +1095,41 @@
      * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
      *                           no hidden frequencies will be scanned for.
      * @param extraScanningParams bundle of extra scanning parameters.
-     * @return Returns true on success, false on failure (e.g. when called before the interface
-     * has been set up).
+     * @return Returns one of the scan status codes defined in {@code WifiScanner#REASON_*}
      */
-    public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
+    public int startScan2(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
             @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
             @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
             @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
         if (scannerImpl == null) {
             Log.e(TAG, "No valid wificond scanner interface handler for iface=" + ifaceName);
-            return false;
+            return WifiScanner.REASON_INVALID_ARGS;
         }
+        SingleScanSettings settings = createSingleScanSettings(scanType, freqs, hiddenNetworkSSIDs,
+                extraScanningParams);
+        if (settings == null) {
+            return WifiScanner.REASON_INVALID_ARGS;
+        }
+        try {
+            int status = scannerImpl.scanRequest(settings);
+            return toFrameworkScanStatusCode(status);
+        } catch (RemoteException e1) {
+            Log.e(TAG, "Failed to request scan due to remote exception");
+        }
+        return WifiScanner.REASON_UNSPECIFIED;
+    }
+
+    private SingleScanSettings createSingleScanSettings(@WifiAnnotations.ScanType int scanType,
+            @SuppressLint("NullableCollection") @Nullable Set<Integer> freqs,
+            @SuppressLint("NullableCollection") @Nullable List<byte[]> hiddenNetworkSSIDs,
+            @SuppressLint("NullableCollection") @Nullable Bundle extraScanningParams) {
         SingleScanSettings settings = new SingleScanSettings();
         try {
             settings.scanType = getScanType(scanType);
         } catch (IllegalArgumentException e) {
             Log.e(TAG, "Invalid scan type ", e);
-            return false;
+            return null;
         }
         settings.channelSettings  = new ArrayList<>();
         settings.hiddenNetworks  = new ArrayList<>();
@@ -1094,12 +1157,25 @@
             }
         }
 
-        try {
-            return scannerImpl.scan(settings);
-        } catch (RemoteException e1) {
-            Log.e(TAG, "Failed to request scan due to remote exception");
+        return settings;
+    }
+
+    private int toFrameworkScanStatusCode(int scanStatus) {
+        switch(scanStatus) {
+            case IWifiScannerImpl.SCAN_STATUS_SUCCESS:
+                return WifiScanner.REASON_SUCCEEDED;
+            case IWifiScannerImpl.SCAN_STATUS_FAILED_BUSY:
+                return WifiScanner.REASON_BUSY;
+            case IWifiScannerImpl.SCAN_STATUS_FAILED_ABORT:
+                return WifiScanner.REASON_ABORT;
+            case IWifiScannerImpl.SCAN_STATUS_FAILED_NODEV:
+                return WifiScanner.REASON_NO_DEV;
+            case IWifiScannerImpl.SCAN_STATUS_FAILED_INVALID_ARGS:
+                return WifiScanner.REASON_INVALID_ARGS;
+            case IWifiScannerImpl.SCAN_STATUS_FAILED_GENERIC:
+            default:
+                return WifiScanner.REASON_UNSPECIFIED;
         }
-        return false;
     }
 
     /**