Merge "Public self targeting API"
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..3bbbb15 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1251,6 +1251,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..3764249 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.
@@ -364,6 +390,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 +407,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 +450,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/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..c7a2997 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
@@ -1409,7 +1415,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 +1511,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 +1560,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 +1579,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 +1674,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 +1755,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 +1785,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 +1802,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 +2236,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 +2268,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 +2531,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 +2989,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 +3704,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 +3725,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
@@ -3704,11 +3776,24 @@
                     }
                 }
             }
+            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 +3809,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 +3829,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);
             }
@@ -3752,20 +3844,27 @@
                 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 +3882,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 +3973,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 +4173,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 +4186,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 +4216,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 +4232,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 +4260,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 +4308,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 +4321,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 +4334,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 +4356,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 +4369,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 +4382,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 +4404,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 +4432,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 +4445,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 +4631,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..285b982 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,
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..5f5f447 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -66,6 +66,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;
@@ -386,8 +387,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);
     }
 
     /**
@@ -764,6 +765,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());
             }
@@ -1135,6 +1139,7 @@
             }
 
             String sourcePackageName = parser.getAttributeValue(null, "sourcePackageName");
+            final String namespace = parser.getAttributeValue(null, "namespace");
             final String sourceTag = parser.getAttributeValue(null, "sourceTag");
 
             int eventType;
@@ -1292,7 +1297,7 @@
                     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);
@@ -1592,12 +1597,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..5712599 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(),
@@ -680,13 +687,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 +717,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 +734,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 +756,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 +904,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 +1049,10 @@
         return true;
     }
 
+    public String getNamespace() {
+        return mNamespace;
+    }
+
     public String getSourceTag() {
         return sourceTag;
     }
@@ -1362,7 +1379,8 @@
     public UserVisibleJobSummary getUserVisibleJobSummary() {
         if (mUserVisibleJobSummary == null) {
             mUserVisibleJobSummary = new UserVisibleJobSummary(
-                    callingUid, getSourceUserId(), getSourcePackageName(), getJobId());
+                    callingUid, getSourceUserId(), getSourcePackageName(),
+                    getNamespace(), getJobId());
         }
         return mUserVisibleJobSummary;
     }
@@ -1989,8 +2007,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 +2020,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 +2115,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 5ad2a7c..5b2b37f 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
@@ -4612,6 +4613,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);
@@ -7498,8 +7500,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 +7521,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 +7651,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 +7671,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 +7984,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();
@@ -9878,6 +9897,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 +9913,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 +9953,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 +9975,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";
@@ -12019,6 +12042,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);
@@ -12572,6 +12596,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
@@ -13212,13 +13237,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>);
@@ -17806,6 +17832,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 {
@@ -18003,8 +18030,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
@@ -18279,6 +18308,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
@@ -18538,6 +18568,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;
@@ -18676,7 +18707,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 {
@@ -18795,6 +18828,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);
@@ -18802,6 +18836,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
@@ -20441,6 +20476,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 {
@@ -23180,6 +23217,7 @@
 
   public class MediaPlayer implements android.media.AudioRouting android.media.VolumeAutomation {
     ctor public MediaPlayer();
+    ctor public MediaPlayer(@NonNull android.content.Context);
     method public void addOnRoutingChangedListener(android.media.AudioRouting.OnRoutingChangedListener, android.os.Handler);
     method public void addTimedTextSource(String, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
     method public void addTimedTextSource(android.content.Context, android.net.Uri, String) throws java.io.IOException, java.lang.IllegalArgumentException, java.lang.IllegalStateException;
@@ -24140,9 +24178,10 @@
   }
 
   public final class RouteListingPreference implements android.os.Parcelable {
-    ctor public RouteListingPreference(@NonNull java.util.List<android.media.RouteListingPreference.Item>);
+    ctor public RouteListingPreference(@NonNull java.util.List<android.media.RouteListingPreference.Item>, boolean);
     method public int describeContents();
     method @NonNull public java.util.List<android.media.RouteListingPreference.Item> getItems();
+    method public boolean getUseSystemOrdering();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteListingPreference> CREATOR;
   }
@@ -24235,6 +24274,8 @@
     ctor public SoundPool.Builder();
     method public android.media.SoundPool build();
     method public android.media.SoundPool.Builder setAudioAttributes(android.media.AudioAttributes) throws java.lang.IllegalArgumentException;
+    method @NonNull public android.media.SoundPool.Builder setAudioSessionId(int);
+    method @NonNull public android.media.SoundPool.Builder setContext(@NonNull android.content.Context);
     method public android.media.SoundPool.Builder setMaxStreams(int) throws java.lang.IllegalArgumentException;
   }
 
@@ -25556,6 +25597,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();
   }
 
@@ -39373,6 +39415,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";
   }
@@ -41050,6 +41094,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);
@@ -41064,10 +41134,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();
@@ -41098,6 +41179,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();
@@ -41142,6 +41249,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);
@@ -41607,6 +41726,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
@@ -41795,10 +41916,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();
@@ -42626,9 +42764,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
   }
@@ -42800,6 +42945,9 @@
     field public static final int AUTHENTICATION_METHOD_CERT = 1; // 0x1
     field public static final int AUTHENTICATION_METHOD_EAP_ONLY = 0; // 0x0
     field public static final int EPDG_ADDRESS_CELLULAR_LOC = 3; // 0x3
+    field public static final int EPDG_ADDRESS_IPV4_ONLY = 2; // 0x2
+    field public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0; // 0x0
+    field public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1; // 0x1
     field public static final int EPDG_ADDRESS_PCO = 2; // 0x2
     field public static final int EPDG_ADDRESS_PLMN = 1; // 0x1
     field public static final int EPDG_ADDRESS_STATIC = 0; // 0x0
@@ -42814,6 +42962,7 @@
     field public static final String KEY_CHILD_SESSION_AES_CTR_KEY_SIZE_INT_ARRAY = "iwlan.child_session_aes_ctr_key_size_int_array";
     field public static final String KEY_DIFFIE_HELLMAN_GROUPS_INT_ARRAY = "iwlan.diffie_hellman_groups_int_array";
     field public static final String KEY_DPD_TIMER_SEC_INT = "iwlan.dpd_timer_sec_int";
+    field public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT = "iwlan.epdg_address_ip_type_preference_int";
     field public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = "iwlan.epdg_address_priority_int_array";
     field public static final String KEY_EPDG_AUTHENTICATION_METHOD_INT = "iwlan.epdg_authentication_method_int";
     field public static final String KEY_EPDG_PCO_ID_IPV4_INT = "iwlan.epdg_pco_id_ipv4_int";
@@ -43648,9 +43797,12 @@
     method public int getDomain();
     method @Nullable public String getRegisteredPlmn();
     method public int getTransportType();
-    method public boolean isRegistered();
-    method public boolean isRoaming();
-    method public boolean isSearching();
+    method public boolean isNetworkRegistered();
+    method public boolean isNetworkRoaming();
+    method public boolean isNetworkSearching();
+    method @Deprecated public boolean isRegistered();
+    method @Deprecated public boolean isRoaming();
+    method @Deprecated public boolean isSearching();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
     field public static final int DOMAIN_CS = 1; // 0x1
@@ -44108,6 +44260,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
@@ -44420,6 +44595,7 @@
     method public int describeContents();
     method public int getNetworkType();
     method public int getOverrideNetworkType();
+    method public boolean isRoaming();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.TelephonyDisplayInfo> CREATOR;
     field public static final int OVERRIDE_NETWORK_TYPE_LTE_ADVANCED_PRO = 2; // 0x2
@@ -53781,6 +53957,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;
@@ -54232,7 +54409,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 e8f8d7c..bd64f4d 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 {
@@ -3023,6 +3028,8 @@
     method public int describeContents();
     method @NonNull public java.util.Set<android.content.ComponentName> getAllowedActivities();
     method @NonNull public java.util.Set<android.content.ComponentName> getAllowedCrossTaskNavigations();
+    method public int getAudioPlaybackSessionId();
+    method public int getAudioRecordingSessionId();
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedActivities();
     method @NonNull public java.util.Set<android.content.ComponentName> getBlockedCrossTaskNavigations();
     method public int getDefaultActivityPolicy();
@@ -3054,6 +3061,8 @@
     method @NonNull public android.companion.virtual.VirtualDeviceParams build();
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAllowedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioPlaybackSessionId(int);
+    method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setAudioRecordingSessionId(int);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedActivities(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>);
     method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDefaultRecentsPolicy(int);
@@ -3614,7 +3623,6 @@
   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 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;
@@ -4231,6 +4239,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
   }
 
@@ -4522,12 +4531,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;
@@ -5808,6 +5819,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
@@ -6583,6 +6596,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();
@@ -6601,6 +6615,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);
@@ -6608,6 +6623,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);
@@ -6662,6 +6678,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();
@@ -6669,9 +6686,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
@@ -9490,7 +9509,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();
@@ -9524,7 +9544,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();
   }
 
@@ -12514,6 +12535,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";
   }
 
@@ -13289,7 +13313,8 @@
 
   public final class NetworkRegistrationInfo implements android.os.Parcelable {
     method @Nullable public android.telephony.DataSpecificRegistrationInfo getDataSpecificInfo();
-    method public int getRegistrationState();
+    method public int getNetworkRegistrationState();
+    method @Deprecated public int getRegistrationState();
     method public int getRejectCause();
     method public int getRoamingType();
     method public boolean isEmergencyEnabled();
@@ -13790,6 +13815,7 @@
     field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_IMS_CALL_DISCONNECT_CAUSE_CHANGED = 28; // 0x1c
     field @RequiresPermission(android.Manifest.permission.READ_CALL_LOG) public static final int EVENT_LEGACY_CALL_STATE_CHANGED = 36; // 0x24
     field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_LINK_CAPACITY_ESTIMATE_CHANGED = 37; // 0x25
+    field @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39; // 0x27
     field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int EVENT_MESSAGE_WAITING_INDICATOR_CHANGED = 3; // 0x3
     field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final int EVENT_OEM_HOOK_RAW = 15; // 0xf
     field @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public static final int EVENT_OUTGOING_EMERGENCY_CALL = 29; // 0x1d
@@ -13825,6 +13851,10 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onLinkCapacityEstimateChanged(@NonNull java.util.List<android.telephony.LinkCapacityEstimate>);
   }
 
+  public static interface TelephonyCallback.MediaQualityStatusChangedListener {
+    method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void onMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus);
+  }
+
   public static interface TelephonyCallback.OutgoingEmergencyCallListener {
     method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void onOutgoingEmergencyCall(@NonNull android.telephony.emergency.EmergencyNumber, int);
   }
@@ -15367,6 +15397,37 @@
     method public void receiveSessionModifyResponse(int, android.telecom.VideoProfile, android.telecom.VideoProfile);
   }
 
+  public final class MediaQualityStatus implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public String getCallSessionId();
+    method public int getMediaSessionType();
+    method public long getRtpInactivityMillis();
+    method public int getRtpJitterMillis();
+    method @IntRange(from=0, to=100) public int getRtpPacketLossRate();
+    method public int getTransportType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.MediaQualityStatus> CREATOR;
+    field public static final int MEDIA_SESSION_TYPE_AUDIO = 1; // 0x1
+    field public static final int MEDIA_SESSION_TYPE_VIDEO = 2; // 0x2
+  }
+
+  public static final class MediaQualityStatus.Builder {
+    ctor public MediaQualityStatus.Builder(@NonNull String, int, int);
+    method @NonNull public android.telephony.ims.MediaQualityStatus build();
+    method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpInactivityMillis(long);
+    method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpJitterMillis(int);
+    method @NonNull public android.telephony.ims.MediaQualityStatus.Builder setRtpPacketLossRate(@IntRange(from=0, to=100) int);
+  }
+
+  public final class MediaThreshold implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public long[] getThresholdsRtpInactivityTimeMillis();
+    method @NonNull public int[] getThresholdsRtpJitterMillis();
+    method @NonNull public int[] getThresholdsRtpPacketLossRate();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.MediaThreshold> CREATOR;
+  }
+
   public class ProvisioningManager {
     method @NonNull public static android.telephony.ims.ProvisioningManager createForSubscriptionId(int);
     method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
@@ -15818,6 +15879,7 @@
     ctor public MmTelFeature(@NonNull java.util.concurrent.Executor);
     method public void changeEnabledCapabilities(@NonNull android.telephony.ims.feature.CapabilityChangeRequest, @NonNull android.telephony.ims.feature.ImsFeature.CapabilityCallbackProxy);
     method public void changeOfferedRtpHeaderExtensionTypes(@NonNull java.util.Set<android.telephony.ims.RtpHeaderExtensionType>);
+    method public void clearMediaThreshold(int);
     method @Nullable public android.telephony.ims.ImsCallProfile createCallProfile(int, int);
     method @Nullable public android.telephony.ims.stub.ImsCallSessionImplBase createCallSession(@NonNull android.telephony.ims.ImsCallProfile);
     method @NonNull public android.telephony.ims.stub.ImsEcbmImplBase getEcbm();
@@ -15827,6 +15889,7 @@
     method public final void notifyCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
     method @Deprecated public final void notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull android.os.Bundle);
     method @Nullable public final android.telephony.ims.ImsCallSessionListener notifyIncomingCall(@NonNull android.telephony.ims.stub.ImsCallSessionImplBase, @NonNull String, @NonNull android.os.Bundle);
+    method public final void notifyMediaQualityStatusChanged(@NonNull android.telephony.ims.MediaQualityStatus);
     method public final void notifyRejectedCall(@NonNull android.telephony.ims.ImsCallProfile, @NonNull android.telephony.ims.ImsReasonInfo);
     method public void notifySrvccCanceled();
     method public void notifySrvccCompleted();
@@ -15837,7 +15900,9 @@
     method public void onFeatureRemoved();
     method public boolean queryCapabilityConfiguration(int, int);
     method @NonNull public final android.telephony.ims.feature.MmTelFeature.MmTelCapabilities queryCapabilityStatus();
+    method @Nullable public android.telephony.ims.MediaQualityStatus queryMediaQualityStatus(int);
     method public final void setCallAudioHandler(int);
+    method public void setMediaThreshold(int, @NonNull android.telephony.ims.MediaThreshold);
     method public void setTerminalBasedCallWaitingStatus(boolean);
     method public void setUiTtyMode(int, @Nullable android.os.Message);
     method public int shouldProcessCall(@NonNull String[]);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 82cc3fd..42acc22 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();
@@ -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 {
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 4bc051ec..9ce196d 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -502,7 +502,6 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     static volatile Handler sMainThreadHandler;  // set once in main()
-    private long mStartSeq; // Only accesssed from the main thread
 
     Bundle mCoreSettings = null;
 
@@ -5343,6 +5342,11 @@
         }
     }
 
+    @Override
+    public void reportRefresh(ActivityClientRecord r) {
+        ActivityClient.getInstance().activityRefreshed(r.token);
+    }
+
     private void handleSetCoreSettings(Bundle coreSettings) {
         synchronized (mCoreSettingsLock) {
             mCoreSettings = coreSettings;
@@ -6739,12 +6743,6 @@
         }
 
         final IActivityManager mgr = ActivityManager.getService();
-        try {
-            mgr.finishAttachApplication(mStartSeq);
-        } catch (RemoteException ex) {
-            throw ex.rethrowFromSystemServer();
-        }
-
         final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
         mConfigurationController.updateLocaleListFromAppContext(appContext);
 
@@ -7675,8 +7673,6 @@
         sCurrentActivityThread = this;
         mConfigurationController = new ConfigurationController(this);
         mSystemThread = system;
-        mStartSeq = startSeq;
-
         if (!system) {
             android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                     UserHandle.myUserId());
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f322ca9..bd9bab3 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);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 39f7153..a832b9a 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);
@@ -2699,15 +2728,7 @@
 
     @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 +2739,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,
@@ -2965,6 +3012,21 @@
         if (mContextType == CONTEXT_TYPE_NON_UI) {
             mContextType = CONTEXT_TYPE_DISPLAY_CONTEXT;
         }
+        // TODO(b/253201821): Update deviceId when display is updated.
+    }
+
+    @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
@@ -2973,6 +3035,69 @@
     }
 
     @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();
     }
@@ -3193,7 +3318,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 +3351,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/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 938f1f6..0866d94 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -147,7 +147,6 @@
     oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
             boolean abortBroadcast, int flags);
     void attachApplication(in IApplicationThread app, long startSeq);
-    void finishAttachApplication(long startSeq);
     List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
     @UnsupportedAppUsage
     void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -332,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);
@@ -719,8 +719,8 @@
 
     /**
      * Control the app freezer state. Returns true in case of success, false if the operation
-     * didn't succeed (for example, when the app freezer isn't supported).
-     * Handling the freezer state via this method is reentrant, that is it can be
+     * didn't succeed (for example, when the app freezer isn't supported). 
+     * Handling the freezer state via this method is reentrant, that is it can be 
      * disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
      * enable match, the freezer is re-enabled at last enable only.
      * @param enable set it to true to enable the app freezer, false to disable it.
@@ -799,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..f20503c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -249,11 +249,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/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/PendingIntent.java b/core/java/android/app/PendingIntent.java
index 9c33729..512b5e0 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -57,6 +57,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -427,7 +428,7 @@
                         + " PendingIntent, use FLAG_NO_CREATE, however, to create a"
                         + " new PendingIntent with an implicit Intent use"
                         + " FLAG_IMMUTABLE.";
-                Log.wtfStack(TAG, msg);
+                Slog.wtfStack(TAG, msg);
             } else {
                 String msg = "New mutable implicit PendingIntent: pkg=" + packageName
                         + ", action=" + intent.getAction()
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/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/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/IVirtualDeviceManager.aidl b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
index 6e784b2..f0d23ac 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceManager.aidl
@@ -72,4 +72,16 @@
     int createVirtualDisplay(in VirtualDisplayConfig virtualDisplayConfig,
             in IVirtualDisplayCallback callback, in IVirtualDevice virtualDevice,
             String packageName);
+
+    /**
+     * Returns device-specific session id for playback, or AUDIO_SESSION_ID_GENERATE
+     * if there's none.
+     */
+    int getAudioPlaybackSessionId(int deviceId);
+
+    /**
+     * Returns device-specific session id for recording, or AUDIO_SESSION_ID_GENERATE
+     * if there's none.
+     */
+    int getAudioRecordingSessionId(int deviceId);
 }
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index f851c31..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;
@@ -269,9 +270,14 @@
      * @hide
      */
     public int getAudioPlaybackSessionId(int deviceId) {
-        //TODO - Return session id rerouted to VirtualAudioDevice if the VirtualAudioDevice
-        //is configured to operate in context-aware mode.
-        return AUDIO_SESSION_ID_GENERATE;
+        if (mService == null) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+        try {
+            return mService.getAudioPlaybackSessionId(deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -287,9 +293,30 @@
      * @hide
      */
     public int getAudioRecordingSessionId(int deviceId) {
-        //TODO - Return session id corresponding to VirtualAudioDevice injection if the
-        // VirtualAudioDevice is configured to operate in context-aware mode.
-        return AUDIO_SESSION_ID_GENERATE;
+        if (mService == null) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+        try {
+            return mService.getAudioRecordingSessionId(deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * 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.
     }
 
     /**
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index f59a7a9..d4a0a08 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -17,6 +17,7 @@
 package android.companion.virtual;
 
 import static android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -191,6 +192,8 @@
     @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs;
     @RecentsPolicy
     private final int mDefaultRecentsPolicy;
+    private final int mAudioPlaybackSessionId;
+    private final int mAudioRecordingSessionId;
 
     private VirtualDeviceParams(
             @LockState int lockState,
@@ -204,7 +207,9 @@
             @Nullable String name,
             @NonNull SparseIntArray devicePolicies,
             @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
-            @RecentsPolicy int defaultRecentsPolicy) {
+            @RecentsPolicy int defaultRecentsPolicy,
+            int audioPlaybackSessionId,
+            int audioRecordingSessionId) {
         mLockState = lockState;
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
@@ -220,6 +225,9 @@
         mDevicePolicies = Objects.requireNonNull(devicePolicies);
         mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
         mDefaultRecentsPolicy = defaultRecentsPolicy;
+        mAudioPlaybackSessionId = audioPlaybackSessionId;
+        mAudioRecordingSessionId = audioRecordingSessionId;
+
     }
 
     @SuppressWarnings("unchecked")
@@ -237,6 +245,8 @@
         mVirtualSensorConfigs = new ArrayList<>();
         parcel.readTypedList(mVirtualSensorConfigs, VirtualSensorConfig.CREATOR);
         mDefaultRecentsPolicy = parcel.readInt();
+        mAudioPlaybackSessionId = parcel.readInt();
+        mAudioRecordingSessionId = parcel.readInt();
     }
 
     /**
@@ -371,6 +381,24 @@
         return mDefaultRecentsPolicy;
     }
 
+    /**
+     * Returns device-specific audio session id for playback.
+     *
+     * @see Builder#setAudioPlaybackSessionId(int)
+     */
+    public int getAudioPlaybackSessionId() {
+        return mAudioPlaybackSessionId;
+    }
+
+    /**
+     * Returns device-specific audio session id for recording.
+     *
+     * @see Builder#setAudioRecordingSessionId(int)
+     */
+    public int getAudioRecordingSessionId() {
+        return mAudioRecordingSessionId;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -390,6 +418,8 @@
         dest.writeSparseIntArray(mDevicePolicies);
         dest.writeTypedList(mVirtualSensorConfigs);
         dest.writeInt(mDefaultRecentsPolicy);
+        dest.writeInt(mAudioPlaybackSessionId);
+        dest.writeInt(mAudioRecordingSessionId);
     }
 
     @Override
@@ -422,7 +452,9 @@
                 && Objects.equals(mBlockedActivities, that.mBlockedActivities)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
                 && Objects.equals(mName, that.mName)
-                && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy;
+                && mDefaultRecentsPolicy == that.mDefaultRecentsPolicy
+                && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
+                && mAudioRecordingSessionId == that.mAudioRecordingSessionId;
     }
 
     @Override
@@ -431,7 +463,7 @@
                 mLockState, mUsersWithMatchingAccounts, mAllowedCrossTaskNavigations,
                 mBlockedCrossTaskNavigations, mDefaultNavigationPolicy, mAllowedActivities,
                 mBlockedActivities, mDefaultActivityPolicy, mName, mDevicePolicies,
-                mDefaultRecentsPolicy);
+                mDefaultRecentsPolicy, mAudioPlaybackSessionId, mAudioRecordingSessionId);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
             hashCode = 31 * hashCode + mDevicePolicies.valueAt(i);
@@ -454,6 +486,8 @@
                 + " mName=" + mName
                 + " mDevicePolicies=" + mDevicePolicies
                 + " mDefaultRecentsPolicy=" + mDefaultRecentsPolicy
+                + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
+                + " mAudioRecordingSessionId=" + mAudioRecordingSessionId
                 + ")";
     }
 
@@ -490,6 +524,8 @@
         @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray();
         @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>();
         private int mDefaultRecentsPolicy;
+        private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE;
+        private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE;
 
         /**
          * Sets the lock state of the device. The permission {@code ADD_ALWAYS_UNLOCKED_DISPLAY}
@@ -706,6 +742,54 @@
         }
 
         /**
+         * Sets audio playback session id specific for this virtual device.
+         *
+         * <p>Audio players constructed within context associated with this virtual device
+         * will be automatically assigned provided session id.
+         *
+         * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
+         * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
+         * the playback session id is set to value other than
+         * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+         *
+         * @param playbackSessionId requested device-specific audio session id for playback
+         * @see android.media.AudioManager.generateAudioSessionId()
+         * @see android.media.AudioTrack.Builder.setContext(Context)
+         */
+        @NonNull
+        public Builder setAudioPlaybackSessionId(int playbackSessionId) {
+            if (playbackSessionId < 0) {
+                throw new IllegalArgumentException("Invalid playback audio session id");
+            }
+            mAudioPlaybackSessionId = playbackSessionId;
+            return this;
+        }
+
+        /**
+         * Sets audio recording session id specific for this virtual device.
+         *
+         * <p>{@link android.media.AudioRecord} constructed within context associated with this
+         * virtual device will be automatically assigned provided session id.
+         *
+         * <p>Requires {@link #DEVICE_POLICY_CUSTOM} to be set for {@link #POLICY_TYPE_AUDIO},
+         * otherwise {@link #build()} method will throw {@link IllegalArgumentException} if
+         * the recording session id is set to value other than
+         * {@link android.media.AudioManager.AUDIO_SESSION_ID_GENERATE}.
+         *
+         * @param recordingSessionId requested device-specific audio session id for playback
+         * @see android.media.AudioManager.generateAudioSessionId()
+         * @see android.media.AudioRecord.Builder.setContext(Context)
+         */
+        @NonNull
+        public Builder setAudioRecordingSessionId(int recordingSessionId) {
+            if (recordingSessionId < 0) {
+                throw new IllegalArgumentException("Invalid recording audio session id");
+            }
+            mAudioRecordingSessionId = recordingSessionId;
+            return this;
+        }
+
+        /**
          * Builds the {@link VirtualDeviceParams} instance.
          *
          * @throws IllegalArgumentException if there's mismatch between policy definition and
@@ -721,6 +805,15 @@
                         "DEVICE_POLICY_CUSTOM for POLICY_TYPE_SENSORS is required for creating "
                                 + "virtual sensors.");
             }
+
+            if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
+                    || mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
+                    && mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
+                    != DEVICE_POLICY_CUSTOM) {
+                throw new IllegalArgumentException("DEVICE_POLICY_CUSTOM for POLICY_TYPE_AUDIO is "
+                        + "required for configuration of device-specific audio session ids.");
+            }
+
             SparseArray<Set<String>> sensorNameByType = new SparseArray();
             for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
                 VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
@@ -744,7 +837,9 @@
                     mName,
                     mDevicePolicies,
                     mVirtualSensorConfigs,
-                    mDefaultRecentsPolicy);
+                    mDefaultRecentsPolicy,
+                    mAudioPlaybackSessionId,
+                    mAudioRecordingSessionId);
         }
     }
 }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index acc27c1..9c25c32 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -109,6 +109,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 +277,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 +395,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 ***********/
 
@@ -6920,6 +6938,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.
      *
@@ -7255,20 +7277,116 @@
     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}.
+     *
+     * @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/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b6ca159..cbdcc02 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1427,22 +1427,6 @@
      */
     public static final int INSTALL_GRANT_RUNTIME_PERMISSIONS = 0x00000100;
 
-    /**
-     * Flag parameter for {@link #installPackage} to indicate that all restricted
-     * permissions should be whitelisted. If {@link #INSTALL_ALL_USERS}
-     * is set the restricted permissions will be whitelisted for all users, otherwise
-     * only to the owner.
-     *
-     * <p>
-     * <strong>Note: </strong>In retrospect it would have been preferred to use
-     * more inclusive terminology when naming this API. Similar APIs added will
-     * refrain from using the term "whitelist".
-     * </p>
-     *
-     * @hide
-     */
-    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
-
     /** {@hide} */
     public static final int INSTALL_FORCE_VOLUME_UUID = 0x00000200;
 
@@ -1539,11 +1523,27 @@
     public static final int INSTALL_STAGED = 0x00200000;
 
     /**
+     * Flag parameter for {@link #installPackage} to indicate that all restricted
+     * permissions should be allowlisted. If {@link #INSTALL_ALL_USERS}
+     * is set the restricted permissions will be allowlisted for all users, otherwise
+     * only to the owner.
+     *
+     * <p>
+     * <strong>Note: </strong>In retrospect it would have been preferred to use
+     * more inclusive terminology when naming this API. Similar APIs added will
+     * refrain from using the term "whitelist".
+     * </p>
+     *
+     * @hide
+     */
+    public static final int INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS = 0x00400000;
+
+    /**
      * Flag parameter for {@link #installPackage} to indicate that check whether given APEX can be
      * updated should be disabled for this install.
      * @hide
      */
-    public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00400000;
+    public static final int INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK = 0x00800000;
 
     /** @hide */
     @IntDef(flag = true, value = {
@@ -10427,9 +10427,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 {
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/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/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 2eeae46..9c42160 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -31,7 +31,6 @@
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.hardware.BatteryState;
 import android.hardware.SensorManager;
 import android.hardware.lights.Light;
@@ -2034,11 +2033,6 @@
      */
     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public void setStylusEverUsed(@NonNull Context context, boolean stylusEverUsed) {
-        if (context.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("You need WRITE_SECURE_SETTINGS permission "
-                + "to set stylus ever used.");
-        }
         Settings.Global.putInt(context.getContentResolver(),
                 Settings.Global.STYLUS_EVER_USED, stylusEverUsed ? 1 : 0);
     }
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/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/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index fb8f84a..a83dea3 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -27,6 +27,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
@@ -210,6 +211,25 @@
             mGameManager.notifyGraphicsEnvironmentSetup();
         }
         Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+
+        Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "setBlobCacheQuotaBytes");
+        new Thread(() -> {
+            try {
+                // Perform this lookup on a thread since getCacheQuotaBytes can be slow
+                // and we don't need the answer right away. The result is consumed in
+                // checkMultifileCacheSize in egl_cache_multifile.cpp. If the value isn't
+                // ready, a default is used. The results will likely be ready by the time
+                // an app starts using the blobcache.
+                final StorageManager sm = context.getSystemService(StorageManager.class);
+                final long cacheBytes = sm.getCacheQuotaBytes(appInfoWithMetaData.storageUuid);
+                final long scaledCacheBytes =
+                        getScaledCacheQuotaBytes(packageName, cacheBytes, appInfoWithMetaData);
+                setBlobCacheQuotaBytes(scaledCacheBytes);
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to look up getCacheQuotaBytes for package: " + packageName);
+            }
+        }).start();
+        Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
     }
 
     /**
@@ -448,6 +468,31 @@
         return ai;
     }
 
+    private static long getScaledCacheQuotaBytes(String packageName, long cacheBytes,
+                                                 ApplicationInfo appInfoWithMetaData) {
+        if (cacheBytes <= 0) {
+            Log.v(TAG, "cacheBytes are zero for " + packageName);
+            return 0;
+        }
+        // Limit the amount of temp storage available to OpenGL drivers to a percentage
+        // based on the app type. Games will typically need more space than applications,
+        // so give them 50%, 25% for everything else.
+        long scaledCacheBytes = 0;
+        if (((appInfoWithMetaData.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
+                || appInfoWithMetaData.category == ApplicationInfo.CATEGORY_GAME) {
+            scaledCacheBytes = cacheBytes / 2;
+            Log.v(TAG, "Treating " + packageName
+                    + " as a game, setting shader cache quota to 50% ("
+                    + scaledCacheBytes + ")");
+        } else {
+            scaledCacheBytes = cacheBytes / 4;
+            Log.v(TAG, "Treating " + packageName
+                    + " as an application, setting shader cache quota to 25% ("
+                    + scaledCacheBytes + ")");
+        }
+        return scaledCacheBytes;
+    }
+
     /**
      * Return the appropriate "default" driver, unless overridden by isAngleEnabledByGameMode().
      */
@@ -996,4 +1041,7 @@
      * Then the app process is allowed to send stats to GpuStats module.
      */
     public static native void hintActivityLaunch();
+
+    private static native void setBlobCacheQuotaBytes(long cacheBytes);
+
 }
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..efe8238 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.
  */
@@ -2719,6 +2723,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 +18378,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 +18479,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 +18505,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/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/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index dded76c..18fdb83 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -34,6 +34,7 @@
 import android.telephony.TelephonyManager.DataEnabledReason;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -1664,6 +1665,10 @@
                 List<LinkCapacityEstimate> linkCapacityEstimateList) {
             // default implementation empty
         }
+
+        public final void onMediaQualityStatusChanged(MediaQualityStatus mediaQualityStatus) {
+            // not support. Can't override. Use TelephonyCallback.
+        }
     }
 
     private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index e90ae75..f8df668 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -27,6 +27,8 @@
 import android.os.Build;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -104,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
@@ -483,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
@@ -502,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
@@ -592,6 +600,19 @@
     public static final int EVENT_TRIGGER_NOTIFY_ANBR = 38;
 
     /**
+     * Event for changes to the media quality status
+     *
+     * <p>Requires permission {@link android.Manifest.permission#READ_PRECISE_PHONE_STATE}
+     *
+     * @see MediaQualityStatusChangedListener#onMediaQualityStatusChanged
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+    public static final int EVENT_MEDIA_QUALITY_STATUS_CHANGED = 39;
+
+    /**
      * @hide
      */
     @IntDef(prefix = {"EVENT_"}, value = {
@@ -632,7 +653,8 @@
             EVENT_ALLOWED_NETWORK_TYPE_LIST_CHANGED,
             EVENT_LEGACY_CALL_STATE_CHANGED,
             EVENT_LINK_CAPACITY_ESTIMATE_CHANGED,
-            EVENT_TRIGGER_NOTIFY_ANBR
+            EVENT_TRIGGER_NOTIFY_ANBR,
+            EVENT_MEDIA_QUALITY_STATUS_CHANGED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface TelephonyEvent {
@@ -675,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
@@ -1268,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).
@@ -1446,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
          */
@@ -1517,6 +1544,30 @@
     }
 
     /**
+     * Interface for media quality status changed listener.
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface MediaQualityStatusChangedListener {
+        /**
+         * Callback invoked when the media quality status of IMS call changes. This call back
+         * means current media quality status crosses at least one of threshold values in {@link
+         * MediaThreshold}. Listener needs to get quality information & check whether it crossed
+         * listener's threshold.
+         *
+         * <p/> Currently thresholds for this indication can be configurable by CARRIER_CONFIG
+         * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT}
+         * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT}
+         * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
+         *
+         * @param mediaQualityStatus The media quality status currently measured.
+         */
+        @RequiresPermission(Manifest.permission.READ_PRECISE_PHONE_STATE)
+        void onMediaQualityStatusChanged(@NonNull MediaQualityStatus mediaQualityStatus);
+    }
+
+    /**
      * The callback methods need to be called on the handler thread where
      * this object was created.  If the binder did that for us it'd be nice.
      * <p>
@@ -1873,5 +1924,16 @@
                     () -> mExecutor.execute(() -> listener.onLinkCapacityEstimateChanged(
                             linkCapacityEstimateList)));
         }
+
+        public void onMediaQualityStatusChanged(
+                MediaQualityStatus mediaQualityStatus) {
+            MediaQualityStatusChangedListener listener =
+                    (MediaQualityStatusChangedListener) mTelephonyCallbackWeakRef.get();
+            if (listener == null) return;
+
+            Binder.withCleanCallingIdentity(
+                    () -> mExecutor.execute(() -> listener.onMediaQualityStatusChanged(
+                            mediaQualityStatus)));
+        }
     }
 }
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 519647d..8b24e07 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -40,6 +40,7 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -545,6 +546,27 @@
     }
 
     /**
+     * Notify change of media quality status {@link MediaQualityStatus} crosses media quality
+     * threshold
+     * <p/>
+     * Currently thresholds for this indication can be configurable by CARRIER_CONFIG
+     * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_PACKET_LOSS_RATE_INT}
+     * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_INACTIVITY_TIME_IN_MILLIS_INT}
+     * {@link CarrierConfigManager#KEY_VOICE_RTP_THRESHOLDS_JITTER_INT}
+     *
+     * @param status media quality status
+     */
+    public void notifyMediaQualityStatusChanged(
+            int slotIndex, int subId, @NonNull MediaQualityStatus status) {
+        try {
+            sRegistry.notifyMediaQualityStatusChanged(slotIndex, subId, status);
+        } catch (RemoteException ex) {
+            // system server crash
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Notify emergency number list changed on certain subscription.
      *
      * @param slotIndex for which emergency number list changed. Can be derived from subId except
@@ -1087,6 +1109,10 @@
             eventList.add(TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
         }
 
+        if (telephonyCallback instanceof TelephonyCallback.MediaQualityStatusChangedListener) {
+            eventList.add(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED);
+        }
+
         return eventList;
     }
 
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 897e23a..65bc81b 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -142,6 +142,17 @@
      */
     public static final String SETTINGS_BIOMETRICS2_ENROLLMENT = "settings_biometrics2_enrollment";
 
+    /** Flag to enable/disable entire page in Accessibility -> Hearing aids
+     *  @hide
+     */
+    public static final String SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE =
+            "settings_accessibility_hearing_aid_page";
+
+    /** Flag to enable/disable audio routing change
+     *  @hide
+     */
+    public static final String SETTINGS_AUDIO_ROUTING = "settings_audio_routing";
+
     private static final Map<String, String> DEFAULT_FLAGS;
 
     static {
@@ -179,6 +190,8 @@
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
         DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "false");
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false");
+        DEFAULT_FLAGS.put(SETTINGS_ACCESSIBILITY_HEARING_AID_PAGE, "false");
+        DEFAULT_FLAGS.put(SETTINGS_AUDIO_ROUTING, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
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 02e0fcc..b8cd7b9 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,21 +16,19 @@
 
 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;
 import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING_HIDE_ANIMATION;
-import static android.view.InsetsController.AnimationType;
-import static android.view.InsetsState.ITYPE_IME;
 
 import android.annotation.Nullable;
 import android.os.IBinder;
-import android.os.Trace;
 import android.util.proto.ProtoOutputStream;
 import android.view.SurfaceControl.Transaction;
 import android.view.inputmethod.InputMethodManager;
 
+import com.android.internal.inputmethod.ImeTracing;
+
 import java.util.function.Supplier;
 
 /**
@@ -56,9 +54,41 @@
     private boolean mIsShowRequestedDuringHideAnimation;
 
     public ImeInsetsSourceConsumer(
-            InsetsState state, Supplier<Transaction> transactionSupplier,
+            int id, InsetsState state, Supplier<Transaction> transactionSupplier,
             InsetsController controller) {
-        super(ITYPE_IME, state, transactionSupplier, controller);
+        super(id, WindowInsets.Type.ime(), state, transactionSupplier, controller);
+    }
+
+    @Override
+    public boolean onAnimationStateChanged(boolean running) {
+        if (!running) {
+            ImeTracing.getInstance().triggerClientDump(
+                    "ImeInsetsSourceConsumer#onAnimationFinished",
+                    mController.getHost().getInputMethodManager(), null /* icProto */);
+        }
+        final boolean insetsChanged = super.onAnimationStateChanged(running);
+        final boolean showRequested = (mController.getRequestedVisibleTypes() & getType()) != 0;
+        if (showRequested) {
+            onShowRequested();
+        } else {
+            mIsRequestedVisibleAwaitingControl = false;
+            if (!running) {
+                // Remove IME surface as IME has finished hide animation, if there is no pending
+                // show request.
+                if (!mIsShowRequestedDuringHideAnimation) {
+                    notifyHidden();
+                    removeSurface();
+                }
+            }
+            // Here is reached
+            // (1) before the hide animation starts.
+            // (2) after the hide animation ends.
+            // (3) if the IME is not controllable (animationFinished == true in this case).
+            // We should reset mIsShowRequestedDuringHideAnimation in all cases.
+            mIsHideAnimationRunning = running;
+            mIsShowRequestedDuringHideAnimation = false;
+        }
+        return insetsChanged;
     }
 
     @Override
@@ -78,36 +108,11 @@
     }
 
     @Override
-    public void show(boolean fromIme) {
-        super.show(fromIme);
-        onShowRequested();
-    }
-
-    @Override
-    public void hide() {
-        super.hide();
-        mIsRequestedVisibleAwaitingControl = false;
-    }
-
-    @Override
-    void hide(boolean animationFinished, @AnimationType int animationType) {
-        hide();
-
-        if (animationFinished) {
-            // Remove IME surface as IME has finished hide animation, if there is no pending
-            // show request.
-            if (!mIsShowRequestedDuringHideAnimation) {
-                notifyHidden();
-                removeSurface();
-            }
-        }
-        // This method is called
-        // (1) before the hide animation starts.
-        // (2) after the hide animation ends.
-        // (3) if the IME is not controllable (animationFinished == true in this case).
-        // We should reset mIsShowRequestedDuringHideAnimation in all cases.
-        mIsHideAnimationRunning = !animationFinished;
-        mIsShowRequestedDuringHideAnimation = false;
+    public boolean applyLocalVisibilityOverride() {
+        ImeTracing.getInstance().triggerClientDump(
+                "ImeInsetsSourceConsumer#applyLocalVisibilityOverride",
+                mController.getHost().getInputMethodManager(), null /* icProto */);
+        return super.applyLocalVisibilityOverride();
     }
 
     /**
@@ -116,6 +121,12 @@
      */
     @Override
     public @ShowResult int requestShow(boolean fromIme) {
+        if (fromIme) {
+            ImeTracing.getInstance().triggerClientDump(
+                    "ImeInsetsSourceConsumer#requestShow",
+                    mController.getHost().getInputMethodManager(), null /* icProto */);
+        }
+
         // TODO: ResultReceiver for IME.
         // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
         if (getControl() == null) {
@@ -125,7 +136,7 @@
         // If we had a request before to show from IME (tracked with mImeRequestedShow), reaching
         // this code here means that we now got control, so we can start the animation immediately.
         // If client window is trying to control IME and IME is already visible, it is immediate.
-        if (fromIme || (mState.getSource(getInternalType()).isVisible() && getControl() != null)) {
+        if (fromIme || (mState.getSource(getId()).isVisible() && getControl() != null)) {
             return ShowResult.SHOW_IMMEDIATELY;
         }
 
@@ -137,10 +148,8 @@
      * Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
      * IME insets are hidden.
      */
-    @Override
-    void notifyHidden() {
+    private void notifyHidden() {
         getImm().notifyImeHidden(mController.getHost().getWindowToken());
-        Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
     }
 
     @Override
@@ -154,11 +163,13 @@
     @Override
     public boolean setControl(@Nullable InsetsSourceControl control, int[] showTypes,
             int[] hideTypes) {
+        ImeTracing.getInstance().triggerClientDump("ImeInsetsSourceConsumer#setControl",
+                mController.getHost().getInputMethodManager(), null /* icProto */);
         if (!super.setControl(control, showTypes, hideTypes)) {
             return false;
         }
         if (control == null && !mIsRequestedVisibleAwaitingControl) {
-            hide();
+            mController.setRequestedVisibleTypes(0 /* visibleTypes */, getType());
             removeSurface();
         }
         if (control != null) {
@@ -192,7 +203,8 @@
     }
 
     /**
-     * Called when {@link #show} or {@link InputMethodManager#showSoftInput(View, int)} is called.
+     * Called when {@link #onAnimationStateChanged(boolean)} or
+     * {@link InputMethodManager#showSoftInput(View, int)} is called.
      */
     public void onShowRequested() {
         if (mIsHideAnimationRunning) {
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index a24c1f9..492c938 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -77,7 +77,7 @@
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
         mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
-                inputChannel, mMessageQueue);
+                mInputChannel, mMessageQueue);
 
         mCloseGuard.open("InputEventReceiver.dispose");
     }
diff --git a/core/java/android/view/InputEventSender.java b/core/java/android/view/InputEventSender.java
index 9035f3f..64f62c7 100644
--- a/core/java/android/view/InputEventSender.java
+++ b/core/java/android/view/InputEventSender.java
@@ -65,7 +65,7 @@
         mInputChannel = inputChannel;
         mMessageQueue = looper.getQueue();
         mSenderPtr = nativeInit(new WeakReference<InputEventSender>(this),
-                inputChannel, mMessageQueue);
+                mInputChannel, mMessageQueue);
 
         mCloseGuard.open("InputEventSender.dispose");
     }
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index e775969..1648659 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -217,7 +217,7 @@
     public void updateSurfacePosition(SparseArray<InsetsSourceControl> controls) {
         for (int i = controls.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = controls.valueAt(i);
-            final InsetsSourceControl c = mControls.get(control.getType());
+            final InsetsSourceControl c = mControls.get(control.getId());
             if (c == null) {
                 continue;
             }
@@ -395,7 +395,7 @@
                 // control may be null if it got revoked.
                 continue;
             }
-            state.getSource(control.getType()).setVisible(shown);
+            state.getSource(control.getId()).setVisible(shown);
         }
         return getInsetsFromState(state, frame, typeSideMap);
     }
@@ -413,7 +413,7 @@
                 // control may be null if it got revoked.
                 continue;
             }
-            if (state == null || state.getSource(control.getType()).isVisible()) {
+            if (state == null || state.getSource(control.getId()).isVisible()) {
                 insets = Insets.max(insets, control.getInsetsHint());
             }
         }
@@ -443,7 +443,7 @@
         // TODO: Implement behavior when inset spans over multiple types
         for (int i = controls.size() - 1; i >= 0; i--) {
             final InsetsSourceControl control = controls.valueAt(i);
-            final InsetsSource source = mInitialInsetsState.getSource(control.getType());
+            final InsetsSource source = mInitialInsetsState.getSource(control.getId());
             final SurfaceControl leash = control.getLeash();
 
             mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
@@ -455,8 +455,8 @@
                     : inset != 0;
 
             if (outState != null) {
-                outState.getSource(source.getType()).setVisible(visible);
-                outState.getSource(source.getType()).setFrame(mTmpFrame);
+                outState.getSource(source.getId()).setVisible(visible);
+                outState.getSource(source.getId()).setFrame(mTmpFrame);
             }
 
             // If the system is controlling the insets source, the leash can be null.
@@ -521,7 +521,7 @@
                 continue;
             }
             @InternalInsetsSide int side = InsetsState.getInsetSide(control.getInsetsHint());
-            if (side == ISIDE_FLOATING && control.getType() == ITYPE_IME) {
+            if (side == ISIDE_FLOATING && control.getType() == WindowInsets.Type.ime()) {
                 side = ISIDE_BOTTOM;
             }
             sideControlsMap.add(side, control);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index fbd8226..709bc2b 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -21,8 +21,6 @@
 import static android.view.InsetsControllerProto.STATE;
 import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.toInternalType;
-import static android.view.InsetsState.toPublicType;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 import static android.view.WindowInsets.Type.FIRST;
 import static android.view.WindowInsets.Type.LAST;
@@ -49,7 +47,6 @@
 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;
@@ -570,8 +567,9 @@
     private final InsetsState mLastDispatchedState = new InsetsState();
 
     private final Rect mFrame = new Rect();
-    private final BiFunction<InsetsController, Integer, InsetsSourceConsumer> mConsumerCreator;
+    private final BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> mConsumerCreator;
     private final SparseArray<InsetsSourceConsumer> mSourceConsumers = new SparseArray<>();
+    private final InsetsSourceConsumer mImeSourceConsumer;
     private final Host mHost;
     private final Handler mHandler;
 
@@ -622,19 +620,20 @@
             this::invokeControllableInsetsChangedListeners;
 
     public InsetsController(Host host) {
-        this(host, (controller, type) -> {
-            if (type == ITYPE_IME) {
-                return new ImeInsetsSourceConsumer(controller.mState, Transaction::new, controller);
+        this(host, (controller, source) -> {
+            if (source.getType() == ime()) {
+                return new ImeInsetsSourceConsumer(source.getId(), controller.mState,
+                        Transaction::new, controller);
             } else {
-                return new InsetsSourceConsumer(type, controller.mState, Transaction::new,
-                        controller);
+                return new InsetsSourceConsumer(source.getId(), source.getType(), controller.mState,
+                        Transaction::new, controller);
             }
         }, host.getHandler());
     }
 
     @VisibleForTesting
     public InsetsController(Host host,
-            BiFunction<InsetsController, Integer, InsetsSourceConsumer> consumerCreator,
+            BiFunction<InsetsController, InsetsSource, InsetsSourceConsumer> consumerCreator,
             Handler handler) {
         mHost = host;
         mConsumerCreator = consumerCreator;
@@ -684,6 +683,9 @@
                 dispatchAnimationEnd(finishedAnimations.get(i));
             }
         };
+
+        // Make mImeSourceConsumer always non-null.
+        mImeSourceConsumer = getSourceConsumer(new InsetsSource(ITYPE_IME, ime()));
     }
 
     @VisibleForTesting
@@ -742,28 +744,43 @@
 
     private void updateState(InsetsState newState) {
         mState.set(newState, 0 /* types */);
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            final InsetsSource source = newState.peekSource(consumer.getId());
+            if (source == null && consumer != mImeSourceConsumer) {
+                // IME source consumer should always be there since we need to communicate with
+                // InputMethodManager no matter we have the source or not.
+                mSourceConsumers.removeAt(i);
+            }
+        }
+        @InsetsType int existingTypes = 0;
         @InsetsType int visibleTypes = 0;
         @InsetsType int disabledUserAnimationTypes = 0;
         @InsetsType int[] cancelledUserAnimationTypes = {0};
-        for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) {
-            InsetsSource source = newState.peekSource(type);
+        for (int i = 0; i < InsetsState.SIZE; i++) {
+            InsetsSource source = newState.peekSource(i);
             if (source == null) continue;
-            @InsetsType int insetsType = toPublicType(type);
-            @AnimationType int animationType = getAnimationType(insetsType);
+            @InsetsType int type = source.getType();
+            @AnimationType int animationType = getAnimationType(type);
             if (!source.isUserControllable()) {
                 // The user animation is not allowed when visible frame is empty.
-                disabledUserAnimationTypes |= insetsType;
+                disabledUserAnimationTypes |= type;
                 if (animationType == ANIMATION_TYPE_USER) {
                     // Existing user animation needs to be cancelled.
                     animationType = ANIMATION_TYPE_NONE;
-                    cancelledUserAnimationTypes[0] |= insetsType;
+                    cancelledUserAnimationTypes[0] |= type;
                 }
             }
-            getSourceConsumer(type).updateSource(source, animationType);
+            getSourceConsumer(source).updateSource(source, animationType);
+            existingTypes |= type;
             if (source.isVisible()) {
-                visibleTypes |= insetsType;
+                visibleTypes |= type;
             }
         }
+
+        // If a type doesn't have a source, treat it as visible if it is visible by default.
+        visibleTypes |= WindowInsets.Type.defaultVisible() & ~existingTypes;
+
         if (mVisibleTypes != visibleTypes) {
             if (WindowInsets.Type.hasCompatSystemBars(mVisibleTypes ^ visibleTypes)) {
                 mCompatSysUiVisibilityStaled = true;
@@ -835,7 +852,7 @@
                     && !fromSource.getFrame().equals(toSource.getFrame())
                     && (Rect.intersects(mFrame, fromSource.getFrame())
                             || Rect.intersects(mFrame, toSource.getFrame()))) {
-                types |= InsetsState.toPublicType(toSource.getType());
+                types |= toSource.getType();
                 if (toState == null) {
                     toState = new InsetsState();
                 }
@@ -888,32 +905,40 @@
             for (InsetsSourceControl activeControl : activeControls) {
                 if (activeControl != null) {
                     // TODO(b/122982984): Figure out why it can be null.
-                    mTmpControlArray.put(activeControl.getType(), activeControl);
+                    mTmpControlArray.put(activeControl.getId(), activeControl);
                 }
             }
         }
 
         @InsetsType int controllableTypes = 0;
+        int consumedControlCount = 0;
         final int[] showTypes = new int[1];
         final int[] hideTypes = new int[1];
 
         // Ensure to update all existing source consumers
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            final InsetsSourceControl control = mTmpControlArray.get(consumer.getInternalType());
+            final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
+            if (control != null) {
+                controllableTypes |= control.getType();
+                consumedControlCount++;
+            }
 
             // control may be null, but we still need to update the control to null if it got
             // revoked.
             consumer.setControl(control, showTypes, hideTypes);
         }
 
-        // Ensure to create source consumers if not available yet.
-        for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
-            final InsetsSourceControl control = mTmpControlArray.valueAt(i);
-            final @InternalInsetsType int type = control.getType();
-            final InsetsSourceConsumer consumer = getSourceConsumer(type);
-            consumer.setControl(control, showTypes, hideTypes);
-            controllableTypes |= InsetsState.toPublicType(type);
+        if (consumedControlCount != mTmpControlArray.size()) {
+            // Whoops! The server sent us some controls without sending corresponding sources.
+            for (int i = mTmpControlArray.size() - 1; i >= 0; i--) {
+                final InsetsSourceControl control = mTmpControlArray.valueAt(i);
+                final InsetsSourceConsumer consumer = mSourceConsumers.get(control.getId());
+                if (consumer == null) {
+                    control.release(SurfaceControl::release);
+                    Log.e(TAG, control + " has no consumer.");
+                }
+            }
         }
 
         if (mTmpControlArray.size() > 0) {
@@ -1104,6 +1129,29 @@
             @AnimationType int animationType,
             @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
             boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
+        final boolean visible = layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+
+        // Basically, we accept the requested visibilities from the upstream callers...
+        setRequestedVisibleTypes(visible ? types : 0, types);
+
+        // However, we might reject the request in some cases, such as delaying showing IME or
+        // rejecting showing IME.
+        controlAnimationUncheckedInner(types, cancellationSignal, listener, frame, fromIme,
+                durationMs, interpolator, animationType, layoutInsetsDuringAnimation,
+                useInsetsAnimationThread, statsToken);
+
+        // We are finishing setting the requested visible types. Report them to the server and/or
+        // the app.
+        reportRequestedVisibleTypes();
+    }
+
+    private void controlAnimationUncheckedInner(@InsetsType int types,
+            @Nullable CancellationSignal cancellationSignal,
+            WindowInsetsAnimationControlListener listener, @Nullable Rect frame, boolean fromIme,
+            long durationMs, Interpolator interpolator,
+            @AnimationType int animationType,
+            @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
+            boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) {
         ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION);
         if ((types & mTypesBeingCancelled) != 0) {
             throw new IllegalStateException("Cannot start a new insets animation of "
@@ -1117,28 +1165,31 @@
             types &= ~mDisabledUserAnimationInsetsTypes;
 
             if (fromIme && (disabledTypes & ime()) != 0
-                    && !mState.getSource(ITYPE_IME).isVisible()) {
+                    && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
                 // We've requested IMM to show IME, but the IME is not controllable. We need to
                 // cancel the request.
-                getSourceConsumer(ITYPE_IME).hide(true, animationType);
+                setRequestedVisibleTypes(0 /* visibleTypes */, ime());
+                if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
+                    notifyVisibilityChanged();
+                }
             }
         }
         if (types == 0) {
             // nothing to animate.
             listener.onCancelled(null);
-            reportRequestedVisibleTypes();
             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;
         }
         cancelExistingControllers(types);
         if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
         mLastStartedAnimTypes |= types;
 
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
 
         Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
-                fromIme, internalTypes, controls, animationType);
+                fromIme, types, controls, animationType);
         int typesReady = typesReadyPair.first;
         boolean imeReady = typesReadyPair.second;
         if (DEBUG) Log.d(TAG, String.format(
@@ -1162,8 +1213,15 @@
                     }
                 });
             }
-            reportRequestedVisibleTypes();
+
+            // The requested visibilities should be delayed as well. Otherwise, the server will
+            // create already visible leashes for us before we play the show animation.
+            setRequestedVisibleTypes(mReportedRequestedVisibleTypes, types);
+
             Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApi", 0);
+            if (!fromIme) {
+                Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
+            }
             return;
         }
 
@@ -1171,6 +1229,10 @@
             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;
         }
 
@@ -1199,12 +1261,20 @@
         } else {
             Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.pendingAnim", 0);
         }
-        if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
-            showDirectly(types, fromIme);
-        } else {
-            hideDirectly(types, false /* animationFinished */, animationType, fromIme);
+        onAnimationStateChanged(types, true /* running */);
+
+        if (fromIme) {
+            switch (animationType) {
+                case ANIMATION_TYPE_SHOW:
+                    Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
+                    break;
+                case ANIMATION_TYPE_HIDE:
+                    Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
+                    break;
+            }
+        } else if (animationType == ANIMATION_TYPE_HIDE) {
+            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
         }
-        reportRequestedVisibleTypes();
     }
 
     // TODO(b/242962223): Make this setter restrictive.
@@ -1217,23 +1287,20 @@
     /**
      * @return Pair of (types ready to animate, IME ready to animate).
      */
-    private Pair<Integer, Boolean> collectSourceControls(boolean fromIme,
-            ArraySet<Integer> internalTypes, SparseArray<InsetsSourceControl> controls,
-            @AnimationType int animationType) {
+    private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
+            SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
         int typesReady = 0;
         boolean imeReady = true;
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            final InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            if ((consumer.getType() & types) == 0) {
+                continue;
+            }
             boolean show = animationType == ANIMATION_TYPE_SHOW
                     || animationType == ANIMATION_TYPE_USER;
             boolean canRun = true;
             if (show) {
                 // Show request
-                if (fromIme) {
-                    ImeTracing.getInstance().triggerClientDump(
-                            "ImeInsetsSourceConsumer#requestShow", mHost.getInputMethodManager(),
-                            null /* icProto */);
-                }
                 switch(consumer.requestShow(fromIme)) {
                     case ShowResult.SHOW_IMMEDIATELY:
                         break;
@@ -1247,44 +1314,22 @@
                         // IME cannot be shown (since it didn't have focus), proceed
                         // with animation of other types.
                         canRun = false;
+
+                        // Reject the show request.
+                        setRequestedVisibleTypes(0 /* visibleTypes */, consumer.getType());
                         break;
                 }
-            } else {
-                // Hide request
-                // TODO: Move notifyHidden() to beginning of the hide animation
-                // (when visibility actually changes using hideDirectly()).
-                if (!fromIme) {
-                    consumer.notifyHidden();
-                }
             }
             if (!canRun) {
                 if (WARN) Log.w(TAG, String.format(
                         "collectSourceControls can't continue show for type: %s fromIme: %b",
-                        InsetsState.typeToString(consumer.getInternalType()), fromIme));
+                        WindowInsets.Type.toString(consumer.getType()), fromIme));
                 continue;
             }
             final InsetsSourceControl control = consumer.getControl();
             if (control != null && control.getLeash() != null) {
-                controls.put(control.getType(), new InsetsSourceControl(control));
+                controls.put(control.getId(), new InsetsSourceControl(control));
                 typesReady |= consumer.getType();
-            } else if (animationType == ANIMATION_TYPE_SHOW) {
-                if (DEBUG) Log.d(TAG, "collectSourceControls no control for show(). fromIme: "
-                        + fromIme);
-                // We don't have a control at the moment. However, we still want to update requested
-                // visibility state such that in case we get control, we can apply show animation.
-                if (fromIme) {
-                    ImeTracing.getInstance().triggerClientDump(
-                            "InsetsSourceConsumer#show", mHost.getInputMethodManager(),
-                            null /* icProto */);
-                }
-                consumer.show(fromIme);
-            } else if (animationType == ANIMATION_TYPE_HIDE) {
-                if (fromIme) {
-                    ImeTracing.getInstance().triggerClientDump(
-                            "InsetsSourceConsumer#hide", mHost.getInputMethodManager(),
-                            null /* icProto */);
-                }
-                consumer.hide();
             }
         }
         return new Pair<>(typesReady, imeReady);
@@ -1333,6 +1378,7 @@
     @VisibleForTesting
     @Override
     public void notifyFinished(InsetsAnimationControlRunner runner, boolean shown) {
+        setRequestedVisibleTypes(shown ? runner.getTypes() : 0, runner.getTypes());
         cancelAnimation(runner, false /* invokeCallback */);
         if (DEBUG) Log.d(TAG, "notifyFinished. shown: " + shown);
         if (runner.getAnimationType() == ANIMATION_TYPE_RESIZE) {
@@ -1344,15 +1390,13 @@
         if (shown) {
             ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW);
-            showDirectly(runner.getTypes(), true /* fromIme */);
             ImeTracker.get().onShown(statsToken);
         } else {
             ImeTracker.get().onProgress(statsToken,
                     ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE);
-            hideDirectly(runner.getTypes(), true /* animationFinished */,
-                    runner.getAnimationType(), true /* fromIme */);
             ImeTracker.get().onHidden(statsToken);
         }
+        reportRequestedVisibleTypes();
     }
 
     @Override
@@ -1389,28 +1433,31 @@
                     "cancelAnimation of types: %d, animType: %d, host: %s",
                     control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle()));
         }
-        boolean stateChanged = false;
+        @InsetsType int removedTypes = 0;
         for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
             RunningAnimation runningAnimation = mRunningAnimations.get(i);
             if (runningAnimation.runner == control) {
                 mRunningAnimations.remove(i);
-                ArraySet<Integer> types = toInternalType(control.getTypes());
-                for (int j = types.size() - 1; j >= 0; j--) {
-                    if (types.valueAt(j) == ITYPE_IME) {
-                        ImeTracing.getInstance().triggerClientDump(
-                                "InsetsSourceConsumer#notifyAnimationFinished",
-                                mHost.getInputMethodManager(), null /* icProto */);
-                    }
-                    stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished();
-                }
+                removedTypes = control.getTypes();
                 if (invokeCallback) {
                     dispatchAnimationEnd(runningAnimation.runner.getAnimation());
                 }
                 break;
             }
         }
-        if (stateChanged) {
-            mHost.notifyInsetsChanged();
+        onAnimationStateChanged(removedTypes, false /* running */);
+    }
+
+    private void onAnimationStateChanged(@InsetsType int types, boolean running) {
+        boolean insetsChanged = false;
+        for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
+            final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
+            if ((consumer.getType() & types) != 0) {
+                insetsChanged |= consumer.onAnimationStateChanged(running);
+            }
+        }
+        if (insetsChanged) {
+            notifyVisibilityChanged();
         }
     }
 
@@ -1422,14 +1469,27 @@
     }
 
     @VisibleForTesting
-    public @NonNull InsetsSourceConsumer getSourceConsumer(@InternalInsetsType int type) {
-        InsetsSourceConsumer controller = mSourceConsumers.get(type);
-        if (controller != null) {
-            return controller;
+    public @NonNull InsetsSourceConsumer getSourceConsumer(InsetsSource source) {
+        final int sourceId = source.getId();
+        InsetsSourceConsumer consumer = mSourceConsumers.get(sourceId);
+        if (consumer != null) {
+            return consumer;
         }
-        controller = mConsumerCreator.apply(this, type);
-        mSourceConsumers.put(type, controller);
-        return controller;
+        if (source.getType() == ime() && mImeSourceConsumer != null) {
+            // WindowInsets.Type.ime() should be only provided by one source.
+            mSourceConsumers.remove(mImeSourceConsumer.getId());
+            consumer = mImeSourceConsumer;
+            consumer.setId(sourceId);
+        } else {
+            consumer = mConsumerCreator.apply(this, source);
+        }
+        mSourceConsumers.put(sourceId, consumer);
+        return consumer;
+    }
+
+    @VisibleForTesting
+    public @NonNull InsetsSourceConsumer getImeSourceConsumer() {
+        return mImeSourceConsumer;
     }
 
     @VisibleForTesting
@@ -1452,14 +1512,14 @@
      * Called when current window gains focus.
      */
     public void onWindowFocusGained(boolean hasViewFocused) {
-        getSourceConsumer(ITYPE_IME).onWindowFocusGained(hasViewFocused);
+        mImeSourceConsumer.onWindowFocusGained(hasViewFocused);
     }
 
     /**
      * Called when current window loses focus.
      */
     public void onWindowFocusLost() {
-        getSourceConsumer(ITYPE_IME).onWindowFocusLost();
+        mImeSourceConsumer.onWindowFocusLost();
     }
 
     @VisibleForTesting
@@ -1473,27 +1533,28 @@
         return ANIMATION_TYPE_NONE;
     }
 
-    void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void setRequestedVisibleTypes(@InsetsType int visibleTypes, @InsetsType int mask) {
         final @InsetsType int requestedVisibleTypes =
                 (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask);
         if (mRequestedVisibleTypes != requestedVisibleTypes) {
-            if (WindowInsets.Type.hasCompatSystemBars(
-                    mRequestedVisibleTypes ^ requestedVisibleTypes)) {
-                mCompatSysUiVisibilityStaled = true;
-            }
             mRequestedVisibleTypes = requestedVisibleTypes;
         }
     }
 
     /**
-     * Sends the requested visible types to window manager if any of them is changed.
+     * Called when finishing setting requested visible types or finishing setting controls.
      */
     private void reportRequestedVisibleTypes() {
-        updateCompatSysUiVisibility();
         if (mReportedRequestedVisibleTypes != mRequestedVisibleTypes) {
+            final @InsetsType int diff = mRequestedVisibleTypes ^ mReportedRequestedVisibleTypes;
+            if (WindowInsets.Type.hasCompatSystemBars(diff)) {
+                mCompatSysUiVisibilityStaled = true;
+            }
             mReportedRequestedVisibleTypes = mRequestedVisibleTypes;
             mHost.updateRequestedVisibleTypes(mReportedRequestedVisibleTypes);
         }
+        updateCompatSysUiVisibility();
     }
 
     @VisibleForTesting
@@ -1502,13 +1563,12 @@
         // TODO(b/166736352): We should only skip the animation of specific types, not all types.
         boolean skipAnim = false;
         if ((types & ime()) != 0) {
-            final InsetsSourceConsumer consumer = mSourceConsumers.get(ITYPE_IME);
-            final InsetsSourceControl imeControl = consumer != null ? consumer.getControl() : null;
+            final InsetsSourceControl imeControl = mImeSourceConsumer.getControl();
             // Skip showing animation once that made by system for some reason.
             // (e.g. starting window with IME snapshot)
             if (imeControl != null) {
                 skipAnim = imeControl.getAndClearSkipAnimationOnce() && show
-                        && consumer.hasViewFocusWhenWindowFocusGain();
+                        && mImeSourceConsumer.hasViewFocusWhenWindowFocusGain();
             }
         }
         applyAnimation(types, show, fromIme, skipAnim, statsToken);
@@ -1520,6 +1580,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;
         }
 
@@ -1539,39 +1603,6 @@
                 !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken);
     }
 
-    private void hideDirectly(@InsetsType int types, boolean animationFinished,
-            @AnimationType int animationType, boolean fromIme) {
-        if ((types & ime()) != 0) {
-            ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly",
-                    mHost.getInputMethodManager(), null /* icProto */);
-        }
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType);
-        }
-        reportRequestedVisibleTypes();
-
-        if (fromIme) {
-            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0);
-        }
-    }
-
-    private void showDirectly(@InsetsType int types, boolean fromIme) {
-        if ((types & ime()) != 0) {
-            ImeTracing.getInstance().triggerClientDump("InsetsController#showDirectly",
-                    mHost.getInputMethodManager(), null /* icProto */);
-        }
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
-        for (int i = internalTypes.size() - 1; i >= 0; i--) {
-            getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */);
-        }
-        reportRequestedVisibleTypes();
-
-        if (fromIme) {
-            Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0);
-        }
-    }
-
     /**
      * Cancel on-going animation to show/hide {@link InsetsType}.
      */
@@ -1700,7 +1731,7 @@
         @InsetsType int result = 0;
         for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
             InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i);
-            InsetsSource source = mState.peekSource(consumer.getInternalType());
+            InsetsSource source = mState.peekSource(consumer.getId());
             if (consumer.getControl() != null && source != null && source.isUserControllable()) {
                 result |= consumer.getType();
             }
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 58ee59d..0a2b06c 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -323,7 +323,8 @@
         public String toString() {
             StringBuilder sb = new StringBuilder(32);
             sb.append("TypedInsetsSize: {");
-            sb.append("windowType=").append(windowType);
+            sb.append("windowType=").append(ViewDebug.intToString(
+                    WindowManager.LayoutParams.class, "type", windowType));
             sb.append(", insetsSize=").append(insetsSize);
             sb.append("}");
             return sb.toString();
diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java
index 778c677..cf64eedf 100644
--- a/core/java/android/view/InsetsResizeAnimationRunner.java
+++ b/core/java/android/view/InsetsResizeAnimationRunner.java
@@ -155,7 +155,7 @@
                     (int) (fromFrame.top + fraction * (toFrame.top - fromFrame.top)),
                     (int) (fromFrame.right + fraction * (toFrame.right - fromFrame.right)),
                     (int) (fromFrame.bottom + fraction * (toFrame.bottom - fromFrame.bottom)));
-            final InsetsSource source = new InsetsSource(type);
+            final InsetsSource source = new InsetsSource(type, fromSource.getType());
             source.setFrame(frame);
             source.setVisible(toSource.isVisible());
             outState.addSource(source);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index c8c941a..f6b063d 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -20,8 +20,6 @@
 import static android.view.InsetsSourceProto.TYPE;
 import static android.view.InsetsSourceProto.VISIBLE;
 import static android.view.InsetsSourceProto.VISIBLE_FRAME;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
 import static android.view.ViewRootImpl.CAPTION_ON_SHELL;
 
 import android.annotation.NonNull;
@@ -31,34 +29,42 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
 
 import java.io.PrintWriter;
 import java.util.Objects;
 
 /**
- * Represents the state of a single window generating insets for clients.
+ * Represents the state of a single entity generating insets for clients.
  * @hide
  */
 public class InsetsSource implements Parcelable {
 
-    private final @InternalInsetsType int mType;
+    /**
+     * An unique integer to identify this source across processes.
+     */
+    private final int mId;
+
+    private final @InsetsType int mType;
 
     /** Frame of the source in screen coordinate space */
     private final Rect mFrame;
     private @Nullable Rect mVisibleFrame;
+
     private boolean mVisible;
     private boolean mInsetsRoundedCornerFrame;
 
     private final Rect mTmpFrame = new Rect();
 
-    public InsetsSource(@InternalInsetsType int type) {
+    public InsetsSource(int id, @InsetsType int type) {
+        mId = id;
         mType = type;
         mFrame = new Rect();
-        mVisible = InsetsState.getDefaultVisibility(type);
+        mVisible = (WindowInsets.Type.defaultVisible() & type) != 0;
     }
 
     public InsetsSource(InsetsSource other) {
+        mId = other.mId;
         mType = other.mType;
         mFrame = new Rect(other.mFrame);
         mVisible = other.mVisible;
@@ -86,14 +92,18 @@
     }
 
     public void setVisibleFrame(@Nullable Rect visibleFrame) {
-        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+        mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
     }
 
     public void setVisible(boolean visible) {
         mVisible = visible;
     }
 
-    public @InternalInsetsType int getType() {
+    public int getId() {
+        return mId;
+    }
+
+    public @InsetsType int getType() {
         return mType;
     }
 
@@ -149,7 +159,7 @@
         // During drag-move and drag-resizing, the caption insets position may not get updated
         // before the app frame get updated. To layout the app content correctly during drag events,
         // we always return the insets with the corresponding height covering the top.
-        if (!CAPTION_ON_SHELL && getType() == ITYPE_CAPTION_BAR) {
+        if (!CAPTION_ON_SHELL && getType() == WindowInsets.Type.captionBar()) {
             return Insets.of(0, frame.height(), 0, 0);
         }
         // Checks for whether there is shared edge with insets for 0-width/height window.
@@ -162,7 +172,7 @@
 
         // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
         // However, we should let the policy decide from the server.
-        if (getType() == ITYPE_IME) {
+        if (getType() == WindowInsets.Type.ime()) {
             return Insets.of(0, 0, 0, mTmpFrame.height());
         }
 
@@ -220,7 +230,7 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(TYPE, InsetsState.typeToString(mType));
+        proto.write(TYPE, WindowInsets.Type.toString(mType));
         mFrame.dumpDebug(proto, FRAME);
         if (mVisibleFrame != null) {
             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
@@ -231,7 +241,8 @@
 
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
-        pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
+        pw.print("InsetsSource id="); pw.print(mId);
+        pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
         pw.print(" frame="); pw.print(mFrame.toShortString());
         if (mVisibleFrame != null) {
             pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString());
@@ -256,9 +267,10 @@
 
         InsetsSource that = (InsetsSource) o;
 
+        if (mId != that.mId) return false;
         if (mType != that.mType) return false;
         if (mVisible != that.mVisible) return false;
-        if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true;
+        if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
         if (mInsetsRoundedCornerFrame != that.mInsetsRoundedCornerFrame) return false;
         return mFrame.equals(that.mFrame);
@@ -266,15 +278,11 @@
 
     @Override
     public int hashCode() {
-        int result = mType;
-        result = 31 * result + mFrame.hashCode();
-        result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0);
-        result = 31 * result + (mVisible ? 1 : 0);
-        result = 31 * result + (mInsetsRoundedCornerFrame ? 1 : 0);
-        return result;
+        return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mInsetsRoundedCornerFrame);
     }
 
     public InsetsSource(Parcel in) {
+        mId = in.readInt();
         mType = in.readInt();
         mFrame = Rect.CREATOR.createFromParcel(in);
         if (in.readInt() != 0) {
@@ -293,6 +301,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mId);
         dest.writeInt(mType);
         mFrame.writeToParcel(dest, 0);
         if (mVisibleFrame != null) {
@@ -308,14 +317,15 @@
     @Override
     public String toString() {
         return "InsetsSource: {"
-                + "mType=" + InsetsState.typeToString(mType)
-                + ", mFrame=" + mFrame.toShortString()
-                + ", mVisible=" + mVisible
-                + ", mInsetsRoundedCornerFrame=" + mInsetsRoundedCornerFrame
+                + "mId=" + mId
+                + " mType=" + WindowInsets.Type.toString(mType)
+                + " mFrame=" + mFrame.toShortString()
+                + " mVisible=" + mVisible
+                + (mInsetsRoundedCornerFrame ? " insetsRoundedCornerFrame" : "")
                 + "}";
     }
 
-    public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() {
+    public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() {
 
         public InsetsSource createFromParcel(Parcel in) {
             return new InsetsSource(in);
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 21c0395..f46eb34 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -25,8 +25,6 @@
 import static android.view.InsetsSourceConsumerProto.PENDING_FRAME;
 import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME;
 import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.getDefaultVisibility;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
@@ -35,12 +33,10 @@
 import android.graphics.Rect;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets.Type.InsetsType;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.ImeTracing;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -74,7 +70,7 @@
 
     protected final InsetsController mController;
     protected final InsetsState mState;
-    private final @InternalInsetsType int mInternalType;
+    private int mId;
     private final @InsetsType int mType;
 
     private static final String TAG = "InsetsSourceConsumer";
@@ -90,16 +86,17 @@
     private Rect mPendingVisibleFrame;
 
     /**
-     * @param type The {@link InternalInsetsType} of the consumed insets.
+     * @param id The ID of the consumed insets.
+     * @param type The {@link InsetsType} of the consumed insets.
      * @param state The current {@link InsetsState} of the consumed insets.
      * @param transactionSupplier The source of new {@link Transaction} instances. The supplier
      *         must provide *new* instances, which will be explicitly closed by this class.
      * @param controller The {@link InsetsController} to use for insets interaction.
      */
-    public InsetsSourceConsumer(@InternalInsetsType int type, InsetsState state,
+    public InsetsSourceConsumer(int id, @InsetsType int type, InsetsState state,
             Supplier<Transaction> transactionSupplier, InsetsController controller) {
-        mType = InsetsState.toPublicType(type);
-        mInternalType = type;
+        mId = id;
+        mType = type;
         mState = state;
         mTransactionSupplier = transactionSupplier;
         mController = controller;
@@ -116,10 +113,6 @@
      */
     public boolean setControl(@Nullable InsetsSourceControl control,
             @InsetsType int[] showTypes, @InsetsType int[] hideTypes) {
-        if (mInternalType == ITYPE_IME) {
-            ImeTracing.getInstance().triggerClientDump("InsetsSourceConsumer#setControl",
-                    mController.getHost().getInputMethodManager(), null /* icProto */);
-        }
         if (Objects.equals(mSourceControl, control)) {
             if (mSourceControl != null && mSourceControl != control) {
                 mSourceControl.release(SurfaceControl::release);
@@ -132,7 +125,7 @@
         mSourceControl = control;
         if (control != null) {
             if (DEBUG) Log.d(TAG, String.format("setControl -> %s on %s",
-                    InsetsState.typeToString(control.getType()),
+                    WindowInsets.Type.toString(control.getType()),
                     mController.getHost().getRootViewTitle()));
         }
         if (mSourceControl == null) {
@@ -140,12 +133,11 @@
             mController.notifyControlRevoked(this);
 
             // Check if we need to restore server visibility.
-            final InsetsSource source = mState.getSource(mInternalType);
-            final boolean serverVisibility =
-                    mController.getLastDispatchedState().getSourceOrDefaultVisibility(
-                            mInternalType);
-            if (source.isVisible() != serverVisibility) {
-                source.setVisible(serverVisibility);
+            final InsetsSource localSource = mState.peekSource(mId);
+            final InsetsSource serverSource = mController.getLastDispatchedState().peekSource(mId);
+            if (localSource != null && serverSource != null
+                    && localSource.isVisible() != serverSource.isVisible()) {
+                localSource.setVisible(serverSource.isVisible());
                 mController.notifyVisibilityChanged();
             }
         } else {
@@ -202,30 +194,41 @@
         return (mController.getRequestedVisibleTypes() & mType) != 0;
     }
 
+    int getId() {
+        return mId;
+    }
+
+    void setId(int id) {
+        mId = id;
+    }
+
     @InsetsType int getType() {
         return mType;
     }
 
-    @InternalInsetsType int getInternalType() {
-        return mInternalType;
-    }
+    /**
+     * Called right after the animation is started or finished.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    public boolean onAnimationStateChanged(boolean running) {
+        boolean insetsChanged = false;
+        if (!running && mPendingFrame != null) {
+            final InsetsSource source = mState.peekSource(mId);
+            if (source != null) {
+                source.setFrame(mPendingFrame);
+                source.setVisibleFrame(mPendingVisibleFrame);
+                insetsChanged = true;
+            }
+            mPendingFrame = null;
+            mPendingVisibleFrame = null;
+        }
 
-    @VisibleForTesting
-    public void show(boolean fromIme) {
-        if (DEBUG) Log.d(TAG, String.format("Call show() for type: %s fromIme: %b ",
-                InsetsState.typeToString(mInternalType), fromIme));
-        setRequestedVisible(true);
-    }
-
-    @VisibleForTesting
-    public void hide() {
-        if (DEBUG) Log.d(TAG, String.format("Call hide for %s on %s",
-                InsetsState.typeToString(mInternalType), mController.getHost().getRootViewTitle()));
-        setRequestedVisible(false);
-    }
-
-    void hide(boolean animationFinished, @AnimationType int animationType) {
-        hide();
+        // We apply the visibility override after the animation is started. We don't do this before
+        // that because we need to know the initial insets state while creating the animation.
+        // We also need to apply the override after the animation is finished because the requested
+        // visibility can be set when finishing the user animation.
+        insetsChanged |= applyLocalVisibilityOverride();
+        return insetsChanged;
     }
 
     /**
@@ -247,32 +250,27 @@
         return mHasViewFocusWhenWindowFocusGain;
     }
 
-    boolean applyLocalVisibilityOverride() {
-        final InsetsSource source = mState.peekSource(mInternalType);
-        final boolean isVisible = source != null ? source.isVisible() : getDefaultVisibility(
-                mInternalType);
-        final boolean hasControl = mSourceControl != null;
+    @VisibleForTesting(visibility = PACKAGE)
+    public boolean applyLocalVisibilityOverride() {
+        final InsetsSource source = mState.peekSource(mId);
+        if (source == null) {
+            return false;
+        }
         final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
 
-        if (mInternalType == ITYPE_IME) {
-            ImeTracing.getInstance().triggerClientDump(
-                    "InsetsSourceConsumer#applyLocalVisibilityOverride",
-                    mController.getHost().getInputMethodManager(), null /* icProto */);
-        }
-
         // If we don't have control, we are not able to change the visibility.
-        if (!hasControl) {
+        if (mSourceControl == null) {
             if (DEBUG) Log.d(TAG, "applyLocalVisibilityOverride: No control in "
                     + mController.getHost().getRootViewTitle()
                     + " requestedVisible=" + requestedVisible);
             return false;
         }
-        if (isVisible == requestedVisible) {
+        if (source.isVisible() == requestedVisible) {
             return false;
         }
         if (DEBUG) Log.d(TAG, String.format("applyLocalVisibilityOverride: %s requestedVisible: %b",
                 mController.getHost().getRootViewTitle(), requestedVisible));
-        mState.getSource(mInternalType).setVisible(requestedVisible);
+        source.setVisible(requestedVisible);
         return true;
     }
 
@@ -299,13 +297,6 @@
     }
 
     /**
-     * Notify listeners that window is now hidden.
-     */
-    void notifyHidden() {
-        // no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
-    }
-
-    /**
      * Remove surface on which this consumer type is drawn.
      */
     public void removeSurface() {
@@ -314,7 +305,7 @@
 
     @VisibleForTesting(visibility = PACKAGE)
     public void updateSource(InsetsSource newSource, @AnimationType int animationType) {
-        InsetsSource source = mState.peekSource(mInternalType);
+        InsetsSource source = mState.peekSource(mId);
         if (source == null || animationType == ANIMATION_TYPE_NONE
                 || source.getFrame().equals(newSource.getFrame())) {
             mPendingFrame = null;
@@ -336,31 +327,6 @@
         if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
     }
 
-    @VisibleForTesting(visibility = PACKAGE)
-    public boolean notifyAnimationFinished() {
-        if (mPendingFrame != null) {
-            InsetsSource source = mState.getSource(mInternalType);
-            source.setFrame(mPendingFrame);
-            source.setVisibleFrame(mPendingVisibleFrame);
-            mPendingFrame = null;
-            mPendingVisibleFrame = null;
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Sets requested visibility from the client, regardless of whether we are able to control it at
-     * the moment.
-     */
-    protected void setRequestedVisible(boolean requestedVisible) {
-        mController.setRequestedVisibleTypes(requestedVisible ? mType : 0, mType);
-        if (DEBUG) Log.d(TAG, "setRequestedVisible: " + requestedVisible);
-        if (applyLocalVisibilityOverride()) {
-            mController.notifyVisibilityChanged();
-        }
-    }
-
     private void applyRequestedVisibilityToControl() {
         if (mSourceControl == null || mSourceControl.getLeash() == null) {
             return;
@@ -383,7 +349,7 @@
 
     void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mInternalType));
+        proto.write(INTERNAL_INSETS_TYPE, WindowInsets.Type.toString(mType));
         proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus);
         proto.write(IS_REQUESTED_VISIBLE, (mController.getRequestedVisibleTypes() & mType) != 0);
         if (mSourceControl != null) {
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 5f1cbba..610cfe4 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -22,13 +22,14 @@
 import static android.view.InsetsSourceControlProto.POSITION;
 import static android.view.InsetsSourceControlProto.TYPE;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Insets;
 import android.graphics.Point;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets.Type.InsetsType;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -40,7 +41,8 @@
  */
 public class InsetsSourceControl implements Parcelable {
 
-    private final @InternalInsetsType int mType;
+    private final int mId;
+    private final @InsetsType int mType;
     private final @Nullable SurfaceControl mLeash;
     private final boolean mInitiallyVisible;
     private final Point mSurfacePosition;
@@ -52,8 +54,9 @@
     private boolean mSkipAnimationOnce;
     private int mParcelableFlags;
 
-    public InsetsSourceControl(@InternalInsetsType int type, @Nullable SurfaceControl leash,
+    public InsetsSourceControl(int id, @InsetsType int type, @Nullable SurfaceControl leash,
             boolean initiallyVisible, Point surfacePosition, Insets insetsHint) {
+        mId = id;
         mType = type;
         mLeash = leash;
         mInitiallyVisible = initiallyVisible;
@@ -62,6 +65,7 @@
     }
 
     public InsetsSourceControl(InsetsSourceControl other) {
+        mId = other.mId;
         mType = other.mType;
         if (other.mLeash != null) {
             mLeash = new SurfaceControl(other.mLeash, "InsetsSourceControl");
@@ -75,6 +79,7 @@
     }
 
     public InsetsSourceControl(Parcel in) {
+        mId = in.readInt();
         mType = in.readInt();
         mLeash = in.readTypedObject(SurfaceControl.CREATOR);
         mInitiallyVisible = in.readBoolean();
@@ -83,6 +88,10 @@
         mSkipAnimationOnce = in.readBoolean();
     }
 
+    public int getId() {
+        return mId;
+    }
+
     public int getType() {
         return mType;
     }
@@ -153,6 +162,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mId);
         dest.writeInt(mType);
         dest.writeTypedObject(mLeash, mParcelableFlags);
         dest.writeBoolean(mInitiallyVisible);
@@ -177,7 +187,8 @@
         }
         final InsetsSourceControl that = (InsetsSourceControl) o;
         final SurfaceControl thatLeash = that.mLeash;
-        return mType == that.mType
+        return mId == that.mId
+                && mType == that.mType
                 && ((mLeash == thatLeash)
                         || (mLeash != null && thatLeash != null && mLeash.isSameSurface(thatLeash)))
                 && mInitiallyVisible == that.mInitiallyVisible
@@ -188,22 +199,26 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
+        return Objects.hash(mId, mType, mLeash, mInitiallyVisible, mSurfacePosition, mInsetsHint,
                 mSkipAnimationOnce);
     }
 
     @Override
     public String toString() {
         return "InsetsSourceControl: {"
-                + "type=" + InsetsState.typeToString(mType)
-                + ", mSurfacePosition=" + mSurfacePosition
-                + ", mInsetsHint=" + mInsetsHint
+                + "mId=" + mId
+                + " mType=" + WindowInsets.Type.toString(mType)
+                + (mInitiallyVisible ? " initiallyVisible" : "")
+                + " mSurfacePosition=" + mSurfacePosition
+                + " mInsetsHint=" + mInsetsHint
+                + (mSkipAnimationOnce ? " skipAnimationOnce" : "")
                 + "}";
     }
 
     public void dump(String prefix, PrintWriter pw) {
         pw.print(prefix);
-        pw.print("InsetsSourceControl type="); pw.print(InsetsState.typeToString(mType));
+        pw.print("InsetsSourceControl mId="); pw.print(mId);
+        pw.print(" mType="); pw.print(WindowInsets.Type.toString(mType));
         pw.print(" mLeash="); pw.print(mLeash);
         pw.print(" mInitiallyVisible="); pw.print(mInitiallyVisible);
         pw.print(" mSurfacePosition="); pw.print(mSurfacePosition);
@@ -212,8 +227,7 @@
         pw.println();
     }
 
-    public static final @android.annotation.NonNull Creator<InsetsSourceControl> CREATOR
-            = new Creator<InsetsSourceControl>() {
+    public static final @NonNull Creator<InsetsSourceControl> CREATOR = new Creator<>() {
         public InsetsSourceControl createFromParcel(Parcel in) {
             return new InsetsSourceControl(in);
         }
@@ -231,7 +245,7 @@
      */
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
-        proto.write(TYPE, InsetsState.typeToString(mType));
+        proto.write(TYPE, WindowInsets.Type.toString(mType));
 
         final long surfaceToken = proto.start(POSITION);
         proto.write(X, mSurfacePosition.x);
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c56d618..054d177 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -245,7 +245,7 @@
 
             // IME won't be reported in max insets as the size depends on the EditorInfo of the IME
             // target.
-            if (source.getType() != ITYPE_IME) {
+            if (source.getType() != WindowInsets.Type.ime()) {
                 InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
                         ? ignoringVisibilityState.getSource(type)
                         : source;
@@ -442,7 +442,7 @@
             @Nullable boolean[] typeVisibilityMap) {
         Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
 
-        int type = toPublicType(source.getType());
+        final int type = source.getType();
         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
                 insets, type);
 
@@ -486,7 +486,7 @@
         if (typeSideMap != null) {
             @InternalInsetsSide int insetSide = getInsetSide(insets);
             if (insetSide != ISIDE_UNKNOWN) {
-                typeSideMap.put(source.getType(), insetSide);
+                typeSideMap.put(source.getId(), insetSide);
             }
         }
     }
@@ -519,7 +519,7 @@
         if (source != null) {
             return source;
         }
-        source = new InsetsSource(type);
+        source = new InsetsSource(type, toPublicType(type));
         mSources[type] = source;
         return source;
     }
@@ -708,7 +708,7 @@
     }
 
     public void addSource(InsetsSource source) {
-        mSources[source.getType()] = source;
+        mSources[source.getId()] = source;
     }
 
     public static boolean clearsCompatInsets(int windowType, int windowFlags, int windowingMode) {
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..ea7a64e 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;
@@ -1806,7 +1803,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 +1819,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;
 
@@ -3028,7 +3023,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 +3270,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();
@@ -8841,7 +8836,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 +8858,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 +10250,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 +10459,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;
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/WindowManager.java b/core/java/android/view/WindowManager.java
index f375ccb..e422732 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4759,10 +4759,10 @@
             }
             if (providedInsets != null) {
                 sb.append(System.lineSeparator());
-                sb.append(" providedInsets=");
+                sb.append(prefix).append("  providedInsets:");
                 for (int i = 0; i < providedInsets.length; ++i) {
-                    if (i > 0) sb.append(' ');
-                    sb.append((providedInsets[i]));
+                    sb.append(System.lineSeparator());
+                    sb.append(prefix).append("    ").append(providedInsets[i]);
                 }
             }
             if (insetsRoundedCornerFrame) {
@@ -4771,10 +4771,12 @@
             }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
-                sb.append(prefix).append("  paramsForRotation=");
+                sb.append(prefix).append("  paramsForRotation:");
                 for (int i = 0; i < paramsForRotation.length; ++i) {
-                    if (i > 0) sb.append(' ');
-                    sb.append(paramsForRotation[i].toString());
+                    // Additional prefix needed for the beginning of the params of the new rotation.
+                    sb.append(System.lineSeparator()).append(prefix).append("    ");
+                    sb.append(Surface.rotationToString(i)).append("=");
+                    sb.append(paramsForRotation[i].toString(prefix + "    "));
                 }
             }
 
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/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index ee31fd5..8331155 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -2460,6 +2460,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 +2592,7 @@
             switch (res.result) {
                 case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                     mRestartOnNextWindowFocus = true;
+                    mServedView = null;
                     break;
             }
             if (mCompletions != null) {
@@ -2598,10 +2600,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);
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/WindowMetricsController.java b/core/java/android/window/WindowMetricsController.java
index 47d532c..5b08879 100644
--- a/core/java/android/window/WindowMetricsController.java
+++ b/core/java/android/window/WindowMetricsController.java
@@ -22,10 +22,12 @@
 
 import android.annotation.NonNull;
 import android.app.ResourcesManager;
+import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.RemoteException;
+import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InsetsState;
@@ -57,48 +59,52 @@
 
     /** @see WindowManager#getCurrentWindowMetrics() */
     public WindowMetrics getCurrentWindowMetrics() {
-        final Rect bounds = getCurrentBounds(mContext);
-
-        // TODO(b/187712731): Provide density for WindowMetrics.
-        return new WindowMetrics(bounds, computeWindowInsets(bounds));
-    }
-
-    private static Rect getCurrentBounds(Context context) {
-        synchronized (ResourcesManager.getInstance()) {
-            return context.getResources().getConfiguration().windowConfiguration.getBounds();
-        }
+        return getWindowMetricsInternal(false /* isMaximum */);
     }
 
     /** @see WindowManager#getMaximumWindowMetrics() */
     public WindowMetrics getMaximumWindowMetrics() {
-        final Rect maxBounds = getMaximumBounds(mContext);
-
-        // TODO(b/187712731): Provide density for WindowMetrics.
-        return new WindowMetrics(maxBounds, computeWindowInsets(maxBounds));
+        return getWindowMetricsInternal(true /* isMaximum */);
     }
 
-    private static Rect getMaximumBounds(Context context) {
-        synchronized (ResourcesManager.getInstance()) {
-            return context.getResources().getConfiguration().windowConfiguration.getMaxBounds();
-        }
-    }
-
-    private WindowInsets computeWindowInsets(Rect bounds) {
-        // Initialize params which used for obtaining all system insets.
-        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-        params.token = Context.getToken(mContext);
-        return getWindowInsetsFromServerForCurrentDisplay(params, bounds);
-    }
-
-    private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
-            WindowManager.LayoutParams attrs, Rect bounds) {
+    /**
+     * The core implementation to obtain {@link WindowMetrics}
+     *
+     * @param isMaximum {@code true} to obtain {@link WindowManager#getCurrentWindowMetrics()}.
+     *                  {@code false} to obtain {@link WindowManager#getMaximumWindowMetrics()}.
+     */
+    private WindowMetrics getWindowMetricsInternal(boolean isMaximum) {
+        final Rect bounds;
+        final float density;
         final boolean isScreenRound;
         final int windowingMode;
         synchronized (ResourcesManager.getInstance()) {
             final Configuration config = mContext.getResources().getConfiguration();
+            final WindowConfiguration winConfig = config.windowConfiguration;
+            bounds = (isMaximum) ? winConfig.getMaxBounds() : winConfig.getBounds();
+            // Multiply default density scale because WindowMetrics provide the density value with
+            // the scaling factor for the Density Independent Pixel unit, which is the same unit
+            // as DisplayMetrics#density
+            density = config.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
             isScreenRound = config.isScreenRound();
-            windowingMode = config.windowConfiguration.getWindowingMode();
+            windowingMode = winConfig.getWindowingMode();
         }
+        final WindowInsets windowInsets = computeWindowInsets(bounds, isScreenRound, windowingMode);
+        return new WindowMetrics(bounds, windowInsets, density);
+    }
+
+    private WindowInsets computeWindowInsets(Rect bounds, boolean isScreenRound,
+            @WindowConfiguration.WindowingMode int windowingMode) {
+        // Initialize params which used for obtaining all system insets.
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.token = Context.getToken(mContext);
+        return getWindowInsetsFromServerForCurrentDisplay(params, bounds, isScreenRound,
+                windowingMode);
+    }
+
+    private WindowInsets getWindowInsetsFromServerForCurrentDisplay(
+            WindowManager.LayoutParams attrs, Rect bounds, boolean isScreenRound,
+            @WindowConfiguration.WindowingMode int windowingMode) {
         return getWindowInsetsFromServerForDisplay(mContext.getDisplayId(), attrs, bounds,
                 isScreenRound, windowingMode);
     }
@@ -165,7 +171,12 @@
                             currentDisplayInfo.roundedCorners)
                     .setDisplayCutout(currentDisplayInfo.displayCutout).build();
 
-            maxMetrics.add(new WindowMetrics(maxBounds, windowInsets));
+            // Multiply default density scale because WindowMetrics provide the density value with
+            // the scaling factor for the Density Independent Pixel unit, which is the same unit
+            // as DisplayMetrics#density
+            final float density = currentDisplayInfo.logicalDensityDpi
+                    * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+            maxMetrics.add(new WindowMetrics(maxBounds, windowInsets, density));
         }
         return maxMetrics;
     }
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/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/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 9cb2e68..3e9f1cb 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -31,6 +31,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 
 /**
  * {@hide}
@@ -76,4 +77,5 @@
     void onDataEnabledChanged(boolean enabled, int reason);
     void onAllowedNetworkTypesChanged(in int reason, in long allowedNetworkType);
     void onLinkCapacityEstimateChanged(in List<LinkCapacityEstimate> linkCapacityEstimateList);
+    void onMediaQualityStatusChanged(in MediaQualityStatus mediaQualityStatus);
 }
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 54936c6..fd9239d 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -32,6 +32,8 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.emergency.EmergencyNumber;
+import android.telephony.ims.MediaQualityStatus;
+
 import com.android.internal.telephony.ICarrierConfigChangeListener;
 import com.android.internal.telephony.ICarrierPrivilegesCallback;
 import com.android.internal.telephony.IPhoneStateListener;
@@ -92,6 +94,7 @@
             in EmergencyNumber emergencyNumber);
     void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
             int callNetworkType);
+    void notifyMediaQualityStatusChanged(int phoneId, int subId, in MediaQualityStatus status);
     void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
     void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
             String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
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/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/android_os_GraphicsEnvironment.cpp b/core/jni/android_os_GraphicsEnvironment.cpp
index 78e2d31..f0ae488 100644
--- a/core/jni/android_os_GraphicsEnvironment.cpp
+++ b/core/jni/android_os_GraphicsEnvironment.cpp
@@ -122,6 +122,10 @@
     android::GraphicsEnv::getInstance().hintActivityLaunch();
 }
 
+void setBlobCacheQuotaBytes_native(JNIEnv* env, jobject clazz, jlong cacheBytes) {
+    android::GraphicsEnv::getInstance().setBlobCacheQuotaBytes(cacheBytes);
+}
+
 const JNINativeMethod g_methods[] = {
         {"isDebuggable", "()Z", reinterpret_cast<void*>(isDebuggable_native)},
         {"setDriverPathAndSphalLibraries", "(Ljava/lang/String;Ljava/lang/String;)V",
@@ -143,6 +147,7 @@
         {"setDebugLayersGLES", "(Ljava/lang/String;)V",
          reinterpret_cast<void*>(setDebugLayersGLES_native)},
         {"hintActivityLaunch", "()V", reinterpret_cast<void*>(hintActivityLaunch_native)},
+        {"setBlobCacheQuotaBytes", "(J)V", reinterpret_cast<void*>(setBlobCacheQuotaBytes_native)},
 };
 
 const char* const kGraphicsEnvironmentName = "android/os/GraphicsEnvironment";
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/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml
index a5608af..7aef82a 100644
--- a/core/res/res/layout/notification_material_action_list.xml
+++ b/core/res/res/layout/notification_material_action_list.xml
@@ -27,6 +27,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:gravity="end"
+        android:layout_gravity="bottom"
         android:orientation="horizontal"
         android:background="@color/notification_action_list_background_color"
         >
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index d4644c5..44f85da 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2963,6 +2963,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..35ff7e8 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.  -->
@@ -4149,12 +4154,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 +5226,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 +5283,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 +5330,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 +5366,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..2924ddf 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -121,6 +121,7 @@
     <public name="visualQueryDetectionService" />
     <public name="physicalKeyboardHintLanguageTag" />
     <public name="physicalKeyboardHintLayoutType" />
+    <public name="allowSharedIsolatedProcess" />
   </staging-public-group>
 
   <staging-public-group type="id" first-id="0x01cd0000">
@@ -134,6 +135,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 6fa5534..c73f2f4 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" />
@@ -467,7 +468,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 +660,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" />
@@ -2256,6 +2261,7 @@
   <java-symbol type="drawable" name="scrubber_control_selector_holo" />
   <java-symbol type="drawable" name="scrubber_progress_horizontal_holo_dark" />
   <java-symbol type="drawable" name="progress_small_material" />
+  <java-symbol type="drawable" name="ic_chevron_end" />
   <java-symbol type="string" name="chooseUsbActivity" />
   <java-symbol type="string" name="ext_media_badremoval_notification_message" />
   <java-symbol type="string" name="ext_media_badremoval_notification_title" />
@@ -3698,6 +3704,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" />
@@ -4397,6 +4407,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" />
@@ -4409,13 +4421,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/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/benchmarks/src/android/os/ParcelableBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
index 372bca4..b1991c2 100644
--- a/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
+++ b/core/tests/benchmarks/src/android/os/ParcelableBenchmark.java
@@ -61,7 +61,7 @@
     public void timeReadWriteInsetsState(int reps) {
         final InsetsState insetsState = new InsetsState();
         for (int i = 0; i < InsetsState.SIZE; i++) {
-            insetsState.addSource(new InsetsSource(i));
+            insetsState.addSource(new InsetsSource(i, InsetsState.toPublicType(i)));
         }
         for (int i = 0; i < reps; i++) {
             insetsState.writeToParcel(mParcel, 0);
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/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0bf133f..958fdc6 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -60,7 +60,7 @@
 public class ImeInsetsSourceConsumerTest {
 
     Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-    ImeInsetsSourceConsumer mImeConsumer;
+    InsetsSourceConsumer mImeConsumer;
     @Spy InsetsController mController;
     SurfaceControl mLeash;
 
@@ -86,14 +86,14 @@
                     false,
                     TYPE_APPLICATION, WINDOWING_MODE_UNDEFINED,
                     SOFT_INPUT_ADJUST_RESIZE, 0, 0);
-            mImeConsumer = (ImeInsetsSourceConsumer) mController.getSourceConsumer(ITYPE_IME);
+            mImeConsumer = mController.getImeSourceConsumer();
         });
     }
 
     @Test
     public void testImeVisibility() {
-        final InsetsSourceControl ime =
-                new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
+        final InsetsSourceControl ime = new InsetsSourceControl(ITYPE_IME, WindowInsets.Type.ime(),
+                mLeash, false, new Point(), Insets.NONE);
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
@@ -121,8 +121,8 @@
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
 
             // set control and verify visibility is applied.
-            InsetsSourceControl control =
-                    new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE);
+            InsetsSourceControl control = new InsetsSourceControl(ITYPE_IME,
+                    WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE);
             mController.onControlsChanged(new InsetsSourceControl[] { control });
             // IME show animation should be triggered when control becomes available.
             verify(mController).applyAnimation(
@@ -161,8 +161,8 @@
             }
 
             // set control and verify visibility is applied.
-            InsetsSourceControl control = Mockito.spy(
-                    new InsetsSourceControl(ITYPE_IME, mLeash, false, new Point(), Insets.NONE));
+            InsetsSourceControl control = Mockito.spy(new InsetsSourceControl(ITYPE_IME,
+                    WindowInsets.Type.ime(), mLeash, false, new Point(), Insets.NONE));
             // Simulate IME source control set this flag when the target has starting window.
             control.setSkipAnimationOnce(true);
 
@@ -173,7 +173,7 @@
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(false) /* fromIme */,
-                        eq(expectSkipAnim) /* skipAnim */, null /* statsToken */);
+                        eq(expectSkipAnim) /* skipAnim */, eq(null) /* statsToken */);
             }
 
             // If previously hasViewFocus is false, verify when requesting the IME visible next
@@ -187,7 +187,7 @@
                 verify(control).getAndClearSkipAnimationOnce();
                 verify(mController).applyAnimation(eq(WindowInsets.Type.ime()),
                         eq(true) /* show */, eq(true) /* fromIme */,
-                        eq(false) /* skipAnim */, null /* statsToken */);
+                        eq(false) /* skipAnim */, eq(null) /* statsToken */);
             }
         });
     }
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index c88255e..cc5f7f8 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -69,7 +69,7 @@
     private InsetsAnimationControlImpl mController;
 
     private SurfaceSession mSession = new SurfaceSession();
-    private SurfaceControl mTopLeash;
+    private SurfaceControl mStatusLeash;
     private SurfaceControl mNavLeash;
     private InsetsState mInsetsState;
 
@@ -80,7 +80,7 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mTopLeash = new SurfaceControl.Builder(mSession)
+        mStatusLeash = new SurfaceControl.Builder(mSession)
                 .setName("testSurface")
                 .build();
         mNavLeash = new SurfaceControl.Builder(mSession)
@@ -89,19 +89,23 @@
         mInsetsState = new InsetsState();
         mInsetsState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 500, 100));
         mInsetsState.getSource(ITYPE_NAVIGATION_BAR).setFrame(new Rect(400, 0, 500, 500));
-        InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mInsetsState,
+        InsetsSourceConsumer topConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR,
+                WindowInsets.Type.statusBars(), mInsetsState,
                 () -> mMockTransaction, mMockController);
         topConsumer.setControl(
-                new InsetsSourceControl(
-                        ITYPE_STATUS_BAR, mTopLeash, true, new Point(0, 0),
-                        Insets.of(0, 100, 0, 0)),
+                new InsetsSourceControl(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars(),
+                        mStatusLeash, true, new Point(0, 0), Insets.of(0, 100, 0, 0)),
                 new int[1], new int[1]);
 
         InsetsSourceConsumer navConsumer = new InsetsSourceConsumer(ITYPE_NAVIGATION_BAR,
-                mInsetsState, () -> mMockTransaction, mMockController);
-        navConsumer.setControl(new InsetsSourceControl(ITYPE_NAVIGATION_BAR, mNavLeash, true,
-                new Point(400, 0), Insets.of(0, 0, 100, 0)), new int[1], new int[1]);
-        navConsumer.hide();
+                WindowInsets.Type.navigationBars(), mInsetsState,
+                () -> mMockTransaction, mMockController);
+        navConsumer.setControl(
+                new InsetsSourceControl(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars(),
+                        mNavLeash, true, new Point(400, 0), Insets.of(0, 0, 100, 0)),
+                new int[1], new int[1]);
+        mMockController.setRequestedVisibleTypes(0, WindowInsets.Type.navigationBars());
+        navConsumer.applyLocalVisibilityOverride();
 
         SparseArray<InsetsSourceControl> controls = new SparseArray<>();
         controls.put(ITYPE_STATUS_BAR, topConsumer.getControl());
@@ -143,7 +147,7 @@
         assertEquals(2, params.size());
         SurfaceParams first = params.get(0);
         SurfaceParams second = params.get(1);
-        SurfaceParams topParams = first.surface == mTopLeash ? first : second;
+        SurfaceParams topParams = first.surface == mStatusLeash ? first : second;
         SurfaceParams navParams = first.surface == mNavLeash ? first : second;
         assertPosition(topParams.matrix, new Rect(0, 0, 500, 100), new Rect(0, -70, 500, 30));
         assertPosition(navParams.matrix, new Rect(400, 0, 500, 500), new Rect(460, 0, 560, 500));
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c6fa778..c917302 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -100,6 +100,9 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public class InsetsControllerTest {
+    private InsetsSource mStatusSource;
+    private InsetsSource mNavSource;
+    private InsetsSource mImeSource;
     private InsetsController mController;
     private SurfaceSession mSession = new SurfaceSession();
     private SurfaceControl mLeash;
@@ -125,24 +128,17 @@
             mTestClock = new OffsettableClock();
             mTestHandler = new TestHandler(null, mTestClock);
             mTestHost = spy(new TestHost(mViewRoot));
-            mController = new InsetsController(mTestHost, (controller, type) -> {
-                if (type == ITYPE_IME) {
-                    return new InsetsSourceConsumer(type, controller.getState(),
-                            Transaction::new, controller) {
+            mController = new InsetsController(mTestHost, (controller, source) -> {
+                if (source.getType() == ime()) {
+                    return new InsetsSourceConsumer(source.getId(), source.getType(),
+                            controller.getState(), Transaction::new, controller) {
 
                         private boolean mImeRequestedShow;
 
                         @Override
-                        public void show(boolean fromIme) {
-                            super.show(fromIme);
-                            if (fromIme) {
-                                mImeRequestedShow = true;
-                            }
-                        }
-
-                        @Override
                         public int requestShow(boolean fromController) {
                             if (fromController || mImeRequestedShow) {
+                                mImeRequestedShow = true;
                                 return SHOW_IMMEDIATELY;
                             } else {
                                 return IME_SHOW_DELAYED;
@@ -150,18 +146,25 @@
                         }
                     };
                 } else {
-                    return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
-                            controller);
+                    return new InsetsSourceConsumer(source.getId(), source.getType(),
+                            controller.getState(), Transaction::new, controller);
                 }
             }, mTestHandler);
             final Rect rect = new Rect(5, 5, 5, 5);
-            mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
-            mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame(
-                    new Rect(0, 90, 100, 100));
-            mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100));
-            mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
-            mController.getState().setDisplayCutout(new DisplayCutout(
+            mStatusSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
+            mStatusSource.setFrame(new Rect(0, 0, 100, 10));
+            mNavSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+            mNavSource.setFrame(new Rect(0, 90, 100, 100));
+            mImeSource = new InsetsSource(ITYPE_IME, ime());
+            mImeSource.setFrame(new Rect(0, 0, 100, 10));
+            InsetsState state = new InsetsState();
+            state.addSource(mStatusSource);
+            state.addSource(mNavSource);
+            state.addSource(mImeSource);
+            state.setDisplayFrame(new Rect(0, 0, 100, 100));
+            state.setDisplayCutout(new DisplayCutout(
                     Insets.of(10, 10, 10, 10), rect, rect, rect, rect));
+            mController.onStateChanged(state);
             mController.calculateInsets(
                     false,
                     false,
@@ -174,8 +177,8 @@
 
     @Test
     public void testControlsChanged() {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
-        assertNotNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl().getLeash());
+        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
+        assertNotNull(mController.getSourceConsumer(mStatusSource).getControl().getLeash());
         mController.addOnControllableInsetsChangedListener(
                 ((controller, typeMask) -> assertEquals(statusBars(), typeMask)));
     }
@@ -185,9 +188,9 @@
         OnControllableInsetsChangedListener listener
                 = mock(OnControllableInsetsChangedListener.class);
         mController.addOnControllableInsetsChangedListener(listener);
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
         mController.onControlsChanged(new InsetsSourceControl[0]);
-        assertNull(mController.getSourceConsumer(ITYPE_STATUS_BAR).getControl());
+        assertNull(mController.getSourceConsumer(mStatusSource).getControl());
         InOrder inOrder = Mockito.inOrder(listener);
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(0));
         inOrder.verify(listener).onControllableInsetsChanged(eq(mController), eq(statusBars()));
@@ -197,7 +200,7 @@
     @Test
     public void testControlsRevoked_duringAnim() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
 
             ArgumentCaptor<WindowInsetsAnimationController> animationController =
                     ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
@@ -226,7 +229,8 @@
 
             InsetsSourceControl control =
                     new InsetsSourceControl(
-                            ITYPE_STATUS_BAR, mLeash, true, new Point(), Insets.of(0, 10, 0, 0));
+                            ITYPE_STATUS_BAR, statusBars(), mLeash, true, new Point(),
+                            Insets.of(0, 10, 0, 0));
             mController.onControlsChanged(new InsetsSourceControl[]{control});
             mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
                     new LinearInterpolator(),
@@ -246,7 +250,7 @@
             WindowInsetsAnimationControlListener loggingListener =
                     mock(WindowInsetsAnimationControlListener.class);
             mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener);
-            mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             verify(loggingListener).onReady(notNull(), anyInt());
@@ -258,7 +262,7 @@
         prepareControls();
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
             // since there is no focused view, forcefully make IME visible.
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.show(all());
@@ -271,24 +275,24 @@
             mController.hide(all());
             mController.cancelExistingAnimations();
             assertEquals(0, mController.getRequestedVisibleTypes() & types);
-            mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
+            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
 
     @Test
     public void testApplyImeVisibility() {
-        InsetsSourceControl ime = createControl(ITYPE_IME);
+        InsetsSourceControl ime = createControl(ITYPE_IME, ime());
         mController.onControlsChanged(new InsetsSourceControl[] { ime });
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true);
+            mController.getSourceConsumer(mImeSource).onWindowFocusGained(true);
             mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertTrue(isRequestedVisible(mController, ime()));
             mController.hide(ime(), true /* fromIme */, null /* statsToken */);
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, ime()));
-            mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
+            mController.getSourceConsumer(mImeSource).onWindowFocusLost();
         });
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
     }
@@ -417,7 +421,7 @@
 
     @Test
     public void testRestoreStartsAnimation() {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mController.hide(statusBars());
@@ -434,7 +438,7 @@
             assertTrue(mController.getState().getSource(ITYPE_STATUS_BAR).isVisible());
 
             // Gaining control
-            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+            mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
             assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(statusBars()));
             mController.cancelExistingAnimations();
             assertFalse(isRequestedVisible(mController, statusBars()));
@@ -455,7 +459,7 @@
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
 
             // Gaining control shortly after
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
 
             assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime()));
             mController.cancelExistingAnimations();
@@ -473,7 +477,7 @@
             assertFalse(mController.getState().getSource(ITYPE_IME).isVisible());
 
             // Gaining control shortly after
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
 
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -488,7 +492,7 @@
 
     @Test
     public void testAnimationEndState_controller() throws Exception {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
@@ -514,7 +518,7 @@
 
     @Test
     public void testCancellation_afterGainingControl() throws Exception {
-        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR));
+        mController.onControlsChanged(createSingletonControl(ITYPE_STATUS_BAR, statusBars()));
 
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             WindowInsetsAnimationControlListener mockListener =
@@ -635,7 +639,7 @@
     public void testFrameUpdateDuringAnimation() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
 
-            mController.onControlsChanged(createSingletonControl(ITYPE_IME));
+            mController.onControlsChanged(createSingletonControl(ITYPE_IME, ime()));
 
             // Pretend IME is calling
             mController.show(ime(), true /* fromIme */, null /* statsToken */);
@@ -814,10 +818,10 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             prepareControls();
 
-            // Hiding visible system bars should only causes insets change once for each bar.
+            // Calling to hide system bars once should only cause insets change once.
             clearInvocations(mTestHost);
             mController.hide(statusBars() | navigationBars());
-            verify(mTestHost, times(2)).notifyInsetsChanged();
+            verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
             // This simulates the callback from server after hiding system bars.
@@ -825,10 +829,10 @@
             mController.onStateChanged(mController.getState());
             verify(mTestHost, never()).notifyInsetsChanged();
 
-            // Showing invisible system bars should only causes insets change once for each bar.
+            // Calling to show system bars once should only cause insets change once.
             clearInvocations(mTestHost);
             mController.show(statusBars() | navigationBars());
-            verify(mTestHost, times(2)).notifyInsetsChanged();
+            verify(mTestHost, times(1)).notifyInsetsChanged();
 
             // Sending the same insets state should not cause insets change.
             // This simulates the callback from server after showing system bars.
@@ -907,11 +911,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Simulate IME insets is not controllable
             mController.onControlsChanged(new InsetsSourceControl[0]);
-            final InsetsSourceConsumer imeInsetsConsumer = mController.getSourceConsumer(ITYPE_IME);
+            final InsetsSourceConsumer imeInsetsConsumer =
+                    mController.getSourceConsumer(mImeSource);
             assertNull(imeInsetsConsumer.getControl());
 
             // Verify IME requested visibility should be updated to IME consumer from controller.
-            mController.show(ime());
+            mController.show(ime(), true /* fromIme */, null /* statsToken */);
             assertTrue(isRequestedVisible(mController, ime()));
 
             mController.hide(ime());
@@ -926,23 +931,23 @@
         latch.await();
     }
 
-    private InsetsSourceControl createControl(@InternalInsetsType int type) {
+    private InsetsSourceControl createControl(int id, @InsetsType int type) {
 
         // Simulate binder behavior by copying SurfaceControl. Otherwise, InsetsController will
         // attempt to release mLeash directly.
         SurfaceControl copy = new SurfaceControl(mLeash, "InsetsControllerTest.createControl");
-        return new InsetsSourceControl(type, copy, InsetsState.getDefaultVisibility(type),
-                new Point(), Insets.NONE);
+        return new InsetsSourceControl(id, type, copy,
+                (type & WindowInsets.Type.defaultVisible()) != 0, new Point(), Insets.NONE);
     }
 
-    private InsetsSourceControl[] createSingletonControl(@InternalInsetsType int type) {
-        return new InsetsSourceControl[] { createControl(type) };
+    private InsetsSourceControl[] createSingletonControl(int id, @InsetsType int type) {
+        return new InsetsSourceControl[] { createControl(id, type) };
     }
 
     private InsetsSourceControl[] prepareControls() {
-        final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR);
-        final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR);
-        final InsetsSourceControl ime = createControl(ITYPE_IME);
+        final InsetsSourceControl navBar = createControl(ITYPE_NAVIGATION_BAR, navigationBars());
+        final InsetsSourceControl statusBar = createControl(ITYPE_STATUS_BAR, statusBars());
+        final InsetsSourceControl ime = createControl(ITYPE_IME, ime());
 
         InsetsSourceControl[] controls = new InsetsSourceControl[3];
         controls[0] = navBar;
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 1253278..3a3eeee 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -29,7 +29,6 @@
 import static junit.framework.TestCase.assertTrue;
 
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
@@ -97,11 +96,11 @@
                 // activity isn't running, lets ignore BadTokenException.
             }
             mState = new InsetsState();
-            mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR));
+            mSpyInsetsSource = Mockito.spy(new InsetsSource(ITYPE_STATUS_BAR, statusBars()));
             mState.addSource(mSpyInsetsSource);
 
             mController = new InsetsController(new ViewRootInsetsControllerHost(mViewRoot));
-            mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, mState,
+            mConsumer = new InsetsSourceConsumer(ITYPE_STATUS_BAR, statusBars(), mState,
                     () -> mMockTransaction, mController) {
                 @Override
                 public void removeSurface() {
@@ -113,40 +112,43 @@
         instrumentation.waitForIdleSync();
 
         mConsumer.setControl(
-                new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
-                        new Point(), Insets.NONE),
+                new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                        true /* initialVisible */, new Point(), Insets.NONE),
                 new int[1], new int[1]);
     }
 
     @Test
-    public void testHide() {
+    public void testOnAnimationStateChanged_requestedInvisible() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mConsumer.hide();
+            mController.setRequestedVisibleTypes(0 /* visibleTypes */, mSpyInsetsSource.getType());
+            mConsumer.onAnimationStateChanged(false /* running */);
             verify(mSpyInsetsSource).setVisible(eq(false));
         });
-
     }
 
     @Test
-    public void testShow() {
+    public void testOnAnimationStateChanged_requestedVisible() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             // Insets source starts out visible
-            mConsumer.hide();
-            mConsumer.show(false /* fromIme */);
+            final int type = mSpyInsetsSource.getType();
+            mController.setRequestedVisibleTypes(0 /* visibleTypes */, type);
+            mConsumer.onAnimationStateChanged(false /* running */);
+            mController.setRequestedVisibleTypes(type, type);
+            mConsumer.onAnimationStateChanged(false /* running */);
             verify(mSpyInsetsSource).setVisible(eq(false));
             verify(mSpyInsetsSource).setVisible(eq(true));
         });
-
     }
 
     @Test
     public void testPendingStates() {
         InsetsState state = new InsetsState();
-        InsetsController controller = mock(InsetsController.class);
+        InsetsController controller = new InsetsController(new ViewRootInsetsControllerHost(
+                mViewRoot));
         InsetsSourceConsumer consumer = new InsetsSourceConsumer(
-                ITYPE_IME, state, null, controller);
+                ITYPE_IME, ime(), state, null, controller);
 
-        InsetsSource source = new InsetsSource(ITYPE_IME);
+        InsetsSource source = new InsetsSource(ITYPE_IME, ime());
         source.setFrame(0, 1, 2, 3);
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_NONE);
 
@@ -156,7 +158,7 @@
         assertEquals(new Rect(0, 1, 2, 3), state.peekSource(ITYPE_IME).getFrame());
 
         // Finish the animation, now the pending frame should be applied
-        assertTrue(consumer.notifyAnimationFinished());
+        assertTrue(consumer.onAnimationStateChanged(false /* running */));
         assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
 
         // Animating again, updates are delayed
@@ -169,7 +171,7 @@
         source.setFrame(4, 5, 6, 7);
         consumer.updateSource(new InsetsSource(source), ANIMATION_TYPE_USER);
 
-        assertFalse(consumer.notifyAnimationFinished());
+        assertFalse(consumer.onAnimationStateChanged(false /* running */));
         assertEquals(new Rect(4, 5, 6, 7), state.peekSource(ITYPE_IME).getFrame());
     }
 
@@ -178,12 +180,12 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             mConsumer.setControl(null, new int[1], new int[1]);
             reset(mMockTransaction);
-            mConsumer.hide();
+            mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
             verifyZeroInteractions(mMockTransaction);
             int[] hideTypes = new int[1];
             mConsumer.setControl(
-                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, true /* initialVisible */,
-                            new Point(), Insets.NONE),
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                            true /* initialVisible */, new Point(), Insets.NONE),
                     new int[1], hideTypes);
             assertEquals(statusBars(), hideTypes[0]);
             assertFalse(mRemoveSurfaceCalled);
@@ -193,15 +195,15 @@
     @Test
     public void testRestore_noAnimation() {
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
-            mConsumer.hide();
+            mController.setRequestedVisibleTypes(0 /* visibleTypes */, statusBars());
             mConsumer.setControl(null, new int[1], new int[1]);
             reset(mMockTransaction);
             verifyZeroInteractions(mMockTransaction);
             mRemoveSurfaceCalled = false;
             int[] hideTypes = new int[1];
             mConsumer.setControl(
-                    new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, false /* initialVisible */,
-                            new Point(), Insets.NONE),
+                    new InsetsSourceControl(ITYPE_STATUS_BAR, statusBars(), mLeash,
+                            false /* initialVisible */, new Point(), Insets.NONE),
                     new int[1], hideTypes);
             assertTrue(mRemoveSurfaceCalled);
             assertEquals(0, hideTypes[0]);
@@ -214,9 +216,9 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
             InsetsState state = new InsetsState();
             ViewRootInsetsControllerHost host = new ViewRootInsetsControllerHost(mViewRoot);
-            InsetsController insetsController = new InsetsController(host, (controller, type) -> {
-                if (type == ITYPE_IME) {
-                    return new InsetsSourceConsumer(ITYPE_IME, state,
+            InsetsController insetsController = new InsetsController(host, (controller, source) -> {
+                if (source.getType() == ime()) {
+                    return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
                             () -> mMockTransaction, controller) {
                         @Override
                         public int requestShow(boolean fromController) {
@@ -224,13 +226,14 @@
                         }
                     };
                 }
-                return new InsetsSourceConsumer(type, controller.getState(), Transaction::new,
-                        controller);
+                return new InsetsSourceConsumer(source.getId(), source.getType(),
+                        controller.getState(), Transaction::new, controller);
             }, host.getHandler());
-            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(ITYPE_IME);
+            InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
+            InsetsSourceConsumer imeConsumer = insetsController.getSourceConsumer(imeSource);
 
             // Initial IME insets source control with its leash.
-            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
                     false /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             reset(mMockTransaction);
 
@@ -239,7 +242,7 @@
             insetsController.controlWindowInsetsAnimation(ime(), 0L,
                     null /* interpolator */, null /* cancellationSignal */, null /* listener */);
             assertEquals(ANIMATION_TYPE_USER, insetsController.getAnimationType(ime()));
-            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, mLeash,
+            imeConsumer.setControl(new InsetsSourceControl(ITYPE_IME, ime(), mLeash,
                     true /* initialVisible */, new Point(), Insets.NONE), new int[1], new int[1]);
             verify(mMockTransaction, never()).show(mLeash);
         });
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 2106b4b..e01440c 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -16,9 +16,9 @@
 
 package android.view;
 
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.captionBar;
+import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 
 import static org.junit.Assert.assertEquals;
 
@@ -45,9 +45,9 @@
 @RunWith(AndroidJUnit4.class)
 public class InsetsSourceTest {
 
-    private InsetsSource mSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
-    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
-    private InsetsSource mCaptionSource = new InsetsSource(ITYPE_CAPTION_BAR);
+    private final InsetsSource mSource = new InsetsSource(0 /* id */, navigationBars());
+    private final InsetsSource mImeSource = new InsetsSource(1 /* id */, ime());
+    private final InsetsSource mCaptionSource = new InsetsSource(2 /* id */, captionBar());
 
     @Before
     public void setUp() {
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/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/Mesh.java b/graphics/java/android/graphics/Mesh.java
index f32e0ee..1f693166 100644
--- a/graphics/java/android/graphics/Mesh.java
+++ b/graphics/java/android/graphics/Mesh.java
@@ -29,7 +29,8 @@
  * {@link #makeIndexed(MeshSpecification, Mode, 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
  */
@@ -53,8 +54,12 @@
      *
      * @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 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.
      */
@@ -70,13 +75,20 @@
     }
 
     /**
-     * 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 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.
      */
@@ -93,7 +105,10 @@
     }
 
     /**
-     * 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
@@ -104,7 +119,10 @@
     }
 
     /**
-     * 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
@@ -116,7 +134,10 @@
     }
 
     /**
-     * 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
@@ -132,7 +153,9 @@
     }
 
     /**
-     * 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.
@@ -142,7 +165,9 @@
     }
 
     /**
-     * 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.
@@ -153,7 +178,9 @@
     }
 
     /**
-     * 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.
@@ -166,7 +193,9 @@
     }
 
     /**
-     * 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.
@@ -180,7 +209,10 @@
     }
 
     /**
-     * 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.
@@ -210,7 +242,9 @@
     }
 
     /**
-     * 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.
@@ -220,7 +254,9 @@
     }
 
     /**
-     * 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.
@@ -231,7 +267,9 @@
     }
 
     /**
-     * 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.
@@ -243,7 +281,9 @@
     }
 
     /**
-     * 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.
@@ -256,7 +296,10 @@
     }
 
     /**
-     * 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.
diff --git a/graphics/java/android/graphics/MeshSpecification.java b/graphics/java/android/graphics/MeshSpecification.java
index 45c13af..dd8fb7a 100644
--- a/graphics/java/android/graphics/MeshSpecification.java
+++ b/graphics/java/android/graphics/MeshSpecification.java
@@ -25,7 +25,7 @@
  * generates a {@link MeshSpecification} via the Make method, where multiple parameters to set up
  * the mesh are supplied, including attributes, vertex stride, varyings, and
  * vertex/fragment shaders. There are also additional methods to provide an optional
- * {@link ColorSpace} as well as an alpha type.
+ * {@link ColorSpace} as well as an {@link AlphaType}.
  *
  * Note that there are several limitations on various mesh specifications:
  * 1. The max amount of attributes allowed is 8.
@@ -43,15 +43,30 @@
 
     /**
      * Constants for {@link #make(Attribute[], int, Varying[], String, String, ColorSpace, int)}
-     * to determine alpha type
+     * to determine alpha type. Describes how to interpret the alpha component of a pixel.
      */
     @IntDef({UNKNOWN, OPAQUE, PREMUL, UNPREMULT})
     public @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;
 
     /**
@@ -61,15 +76,41 @@
     public @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
@@ -106,14 +147,19 @@
     }
 
     /**
-     * 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,
@@ -131,10 +177,14 @@
      *
      * @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}
@@ -154,10 +204,15 @@
      *
      * @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.
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/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index d9b4f47..7aae633 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -261,7 +261,7 @@
                     if (activeControl == null) {
                         continue;
                     }
-                    if (activeControl.getType() == InsetsState.ITYPE_IME) {
+                    if (activeControl.getType() == WindowInsets.Type.ime()) {
                         imeSourceControl = activeControl;
                     }
                 }
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/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/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 5b656b3..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
@@ -24,6 +24,7 @@
 import com.android.server.wm.flicker.FlickerBuilder
 import com.android.server.wm.flicker.FlickerTest
 import com.android.server.wm.flicker.FlickerTestFactory
+import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
 import com.android.server.wm.flicker.junit.FlickerParametersRunnerFactory
 import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
 import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
@@ -84,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
@@ -106,14 +118,27 @@
 
     @Presubmit
     @Test
-    fun primaryAppBoundsChanges() =
+    fun primaryAppBoundsChanges() {
+        Assume.assumeFalse(isShellTransitionsEnabled)
         flicker.splitAppLayerBoundsChanges(
             primaryApp,
             landscapePosLeft = true,
             portraitPosTop = false
         )
+    }
 
-    @FlakyTest(bugId = 250530664)
+    @FlakyTest(bugId = 263213649)
+    @Test
+    fun primaryAppBoundsChanges_ShellTransit() {
+        Assume.assumeTrue(isShellTransitionsEnabled)
+        flicker.splitAppLayerBoundsChanges(
+            primaryApp,
+            landscapePosLeft = true,
+            portraitPosTop = false
+        )
+    }
+
+    @Presubmit
     @Test
     fun secondaryAppBoundsChanges() =
         flicker.splitAppLayerBoundsChanges(
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/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 40f2e88..22df362 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -127,13 +127,14 @@
     private InsetsSourceControl[] insetsSourceControl() {
         return new InsetsSourceControl[]{
                 new InsetsSourceControl(
-                        ITYPE_IME, mock(SurfaceControl.class), false, new Point(0, 0), Insets.NONE)
+                        ITYPE_IME, ime(), mock(SurfaceControl.class), false, new Point(0, 0),
+                        Insets.NONE)
         };
     }
 
     private InsetsState insetsStateWithIme(boolean visible) {
         InsetsState state = new InsetsState();
-        state.addSource(new InsetsSource(ITYPE_IME));
+        state.addSource(new InsetsSource(ITYPE_IME, ime()));
         state.setSourceVisible(ITYPE_IME, visible);
         return state;
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 2fc0914..d4408d7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -19,6 +19,7 @@
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -291,7 +292,7 @@
         mController.onCompatInfoChanged(createTaskInfo(DISPLAY_ID, TASK_ID,
                 /* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
         InsetsState insetsState = new InsetsState();
-        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
         insetsSource.setFrame(0, 0, 1000, 1000);
         insetsState.addSource(insetsSource);
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
index e79b803..c4d78bb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIWindowManagerTest.java
@@ -21,6 +21,7 @@
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
 import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
@@ -328,7 +329,7 @@
         // Update if the insets change on the existing display layout
         clearInvocations(mWindowManager);
         InsetsState insetsState = new InsetsState();
-        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        InsetsSource insetsSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
         insetsSource.setFrame(0, 0, 1000, 1000);
         insetsState.addSource(insetsSource);
         displayLayout.setInsets(mContext.getResources(), insetsState);
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/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/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 203429e..50749e7 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -16,9 +16,6 @@
 
 package android.media;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
-import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
-import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
 import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
 
 import android.annotation.CallbackExecutor;
@@ -30,7 +27,6 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
-import android.companion.virtual.VirtualDeviceManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.media.audiopolicy.AudioMix;
@@ -824,12 +820,7 @@
 
         int[] sampleRate = new int[] {mSampleRate};
         int[] session = new int[1];
-        if (sessionId == AUDIO_SESSION_ID_GENERATE) {
-            // If there's no specific session id requested, try to get one from context.
-            session[0] = getSessionIdForContext(context);
-        } else {
-            session[0] = sessionId;
-        }
+        session[0] = resolvePlaybackSessionId(context, sessionId);
 
         // native initialization
         int initResult = native_setup(new WeakReference<AudioTrack>(this), mAttributes,
@@ -1416,32 +1407,6 @@
     }
 
     /**
-     * Helper method to extract device specific audio session id from Context.
-     *
-     * @param context {@link Context} to use for extraction of device specific session id.
-     * @return device specific session id. If context is null or doesn't have specific audio
-     *   session id associated, this method returns {@link AUDIO_SESSION_ID_GENERATE}.
-     */
-    private static int getSessionIdForContext(@Nullable Context context) {
-        if (context == null) {
-            return AUDIO_SESSION_ID_GENERATE;
-        }
-
-        int deviceId = context.getDeviceId();
-        if (deviceId == DEVICE_ID_DEFAULT) {
-            return AUDIO_SESSION_ID_GENERATE;
-        }
-
-        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
-        if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
-                == DEVICE_POLICY_DEFAULT) {
-            return AUDIO_SESSION_ID_GENERATE;
-        }
-
-        return vdm.getAudioPlaybackSessionId(deviceId);
-    }
-
-    /**
      * Sets an {@link AudioPolicy} to automatically unregister when the track is released.
      *
      * <p>This is to prevent users of the call audio injection API from having to manually
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/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index 79a5902..273c7af 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -661,10 +661,26 @@
      * Doing so frees any resources you have previously acquired.
      */
     public MediaPlayer() {
-        this(AudioSystem.AUDIO_SESSION_ALLOCATE);
+        this(/*context=*/null, AudioSystem.AUDIO_SESSION_ALLOCATE);
     }
 
-    private MediaPlayer(int sessionId) {
+
+    /**
+     * Default constructor with context.
+     *
+     *  <p>Consider using one of the create() methods for synchronously instantiating a
+     *  MediaPlayer from a Uri or resource.
+     *
+     * @param context non-null context. This context will be used to pull information,
+     *  such as {@link android.content.AttributionSource} and device specific session ids, which
+     *  will be associated with the {@link MediaPlayer}.
+     *  However, the context itself will not be retained by the MediaPlayer.
+     */
+    public MediaPlayer(@NonNull Context context) {
+        this(Objects.requireNonNull(context), AudioSystem.AUDIO_SESSION_ALLOCATE);
+    }
+
+    private MediaPlayer(Context context, int sessionId) {
         super(new AudioAttributes.Builder().build(),
                 AudioPlaybackConfiguration.PLAYER_TYPE_JAM_MEDIAPLAYER);
 
@@ -680,7 +696,9 @@
         mTimeProvider = new TimeProvider(this);
         mOpenSubtitleSources = new Vector<InputStream>();
 
-        AttributionSource attributionSource = AttributionSource.myAttributionSource();
+        AttributionSource attributionSource =
+                context == null ? AttributionSource.myAttributionSource()
+                        : context.getAttributionSource();
         // set the package name to empty if it was null
         if (attributionSource.getPackageName() == null) {
             attributionSource = attributionSource.withPackageName("");
@@ -693,7 +711,9 @@
             native_setup(new WeakReference<MediaPlayer>(this), attributionSourceState.getParcel());
         }
 
-        baseRegisterPlayer(sessionId);
+        int effectiveSessionId = resolvePlaybackSessionId(context, sessionId);
+        baseRegisterPlayer(effectiveSessionId);
+        native_setAudioSessionId(effectiveSessionId);
     }
 
     private Parcel createPlayerIIdParcel() {
@@ -932,11 +952,10 @@
             AudioAttributes audioAttributes, int audioSessionId) {
 
         try {
-            MediaPlayer mp = new MediaPlayer(audioSessionId);
+            MediaPlayer mp = new MediaPlayer(context, audioSessionId);
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
             mp.setAudioAttributes(aa);
-            mp.native_setAudioSessionId(audioSessionId);
             mp.setDataSource(context, uri);
             if (holder != null) {
                 mp.setDisplay(holder);
@@ -998,7 +1017,7 @@
             AssetFileDescriptor afd = context.getResources().openRawResourceFd(resid);
             if (afd == null) return null;
 
-            MediaPlayer mp = new MediaPlayer(audioSessionId);
+            MediaPlayer mp = new MediaPlayer(context, audioSessionId);
 
             final AudioAttributes aa = audioAttributes != null ? audioAttributes :
                 new AudioAttributes.Builder().build();
@@ -2402,6 +2421,9 @@
      * However, it is possible to force this player to be part of an already existing audio session
      * by calling this method.
      * This method must be called before one of the overloaded <code> setDataSource </code> methods.
+     * Note that session id set using this method will override device-specific audio session id,
+     * if the {@link MediaPlayer} was instantiated using device-specific {@link Context} -
+     * see {@link MediaPlayer#MediaPlayer(Context)}.
      * @throws IllegalStateException if it is called in an invalid state
      */
     public void setAudioSessionId(int sessionId)
diff --git a/media/java/android/media/PlayerBase.java b/media/java/android/media/PlayerBase.java
index 72ee00f..38115e1 100644
--- a/media/java/android/media/PlayerBase.java
+++ b/media/java/android/media/PlayerBase.java
@@ -16,9 +16,15 @@
 
 package android.media;
 
+import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
+import android.companion.virtual.VirtualDeviceManager;
 import android.content.Context;
 import android.os.IBinder;
 import android.os.Parcel;
@@ -535,4 +541,44 @@
     protected String getCurrentOpPackageName() {
         return TextUtils.emptyIfNull(ActivityThread.currentOpPackageName());
     }
+
+    /**
+     * Helper method to resolve which session id should be used for player initialization.
+     *
+     * This method will assign session id in following way:
+     * 1. Explicitly requested session id has the highest priority, if there is one,
+     *    it will be used.
+     * 2. If there's device-specific session id associated with the provided context,
+     *    it will be used.
+     * 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
+     *
+     * @param context {@link Context} to use for extraction of device specific session id.
+     * @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
+     * @return session id to be passed to AudioService for the player initialization given
+     *   provided {@link Context} instance and explicitly requested session id.
+     */
+    protected static int resolvePlaybackSessionId(@Nullable Context context,
+            int requestedSessionId) {
+        if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
+            // Use explicitly requested session id.
+            return requestedSessionId;
+        }
+
+        if (context == null) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+
+        int deviceId = context.getDeviceId();
+        if (deviceId == DEVICE_ID_DEFAULT) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+
+        VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
+        if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
+                == DEVICE_POLICY_DEFAULT) {
+            return AUDIO_SESSION_ID_GENERATE;
+        }
+
+        return vdm.getAudioPlaybackSessionId(deviceId);
+    }
 }
diff --git a/media/java/android/media/RouteListingPreference.java b/media/java/android/media/RouteListingPreference.java
index b7043cb..d74df7a 100644
--- a/media/java/android/media/RouteListingPreference.java
+++ b/media/java/android/media/RouteListingPreference.java
@@ -54,20 +54,24 @@
             };
 
     @NonNull private final List<Item> mItems;
+    private final boolean mUseSystemOrdering;
 
     /**
      * Creates an instance with the given values.
      *
      * @param items See {@link #getItems()}.
+     * @param useSystemOrdering See {@link #getUseSystemOrdering()}
      */
-    public RouteListingPreference(@NonNull List<Item> items) {
+    public RouteListingPreference(@NonNull List<Item> items, boolean useSystemOrdering) {
         mItems = List.copyOf(Objects.requireNonNull(items));
+        mUseSystemOrdering = useSystemOrdering;
     }
 
     private RouteListingPreference(Parcel in) {
         List<Item> items =
                 in.readParcelableList(new ArrayList<>(), Item.class.getClassLoader(), Item.class);
         mItems = List.copyOf(items);
+        mUseSystemOrdering = in.readBoolean();
     }
 
     /**
@@ -79,6 +83,18 @@
         return mItems;
     }
 
+    /**
+     * 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;
+    }
+
     // RouteListingPreference Parcelable implementation.
 
     @Override
@@ -89,6 +105,7 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeParcelableList(mItems, flags);
+        dest.writeBoolean(mUseSystemOrdering);
     }
 
     // Equals and hashCode.
@@ -102,12 +119,12 @@
             return false;
         }
         RouteListingPreference that = (RouteListingPreference) other;
-        return mItems.equals(that.mItems);
+        return mItems.equals(that.mItems) && mUseSystemOrdering == that.mUseSystemOrdering;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mItems);
+        return Objects.hash(mItems, mUseSystemOrdering);
     }
 
     /** Holds preference information for a specific route in a {@link RouteListingPreference}. */
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index dc09359..f33a744 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -16,6 +16,8 @@
 
 package android.media;
 
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -29,6 +31,7 @@
 
 import java.io.File;
 import java.io.FileDescriptor;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
 
 
@@ -146,12 +149,14 @@
      *     SoundPool instance
      */
     public SoundPool(int maxStreams, int streamType, int srcQuality) {
-        this(maxStreams,
-                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build());
+        this(/*context=*/null, maxStreams,
+                new AudioAttributes.Builder().setInternalLegacyStreamType(streamType).build(),
+                AUDIO_SESSION_ID_GENERATE);
         PlayerBase.deprecateStreamTypeForPlayback(streamType, "SoundPool", "SoundPool()");
     }
 
-    private SoundPool(int maxStreams, AudioAttributes attributes) {
+    private SoundPool(@Nullable Context context, int maxStreams,
+            @NonNull AudioAttributes attributes, int sessionId) {
         super(attributes, AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL);
 
         // do native setup
@@ -160,8 +165,7 @@
         }
         mAttributes = attributes;
 
-        // FIXME: b/174876164 implement session id for soundpool
-        baseRegisterPlayer(AudioSystem.AUDIO_SESSION_ALLOCATE);
+        baseRegisterPlayer(resolvePlaybackSessionId(context, sessionId));
     }
 
     /**
@@ -555,6 +559,8 @@
     public static class Builder {
         private int mMaxStreams = 1;
         private AudioAttributes mAudioAttributes;
+        private Context mContext;
+        private int mSessionId = AUDIO_SESSION_ID_GENERATE;
 
         /**
          * Constructs a new Builder with the defaults format values.
@@ -596,12 +602,49 @@
             return this;
         }
 
+        /**
+         * Sets the session ID the {@link SoundPool} will be attached to.
+         *
+         * Note, that if there's a device specific session id associated with the context
+         * (see {@link Builder#setContext(Context)}), explicitly setting a session id using this
+         * method will override it.
+         *
+         * @param sessionId a strictly positive ID number retrieved from another player or
+         *   allocated by {@link AudioManager} via {@link AudioManager#generateAudioSessionId()},
+         *   or {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
+         * @return the same {@link Builder} instance
+         * @throws IllegalArgumentException when sessionId is invalid.
+         */
+        public @NonNull Builder setAudioSessionId(int sessionId) {
+            if ((sessionId != AUDIO_SESSION_ID_GENERATE) && (sessionId < 1)) {
+                throw new IllegalArgumentException("Invalid audio session ID " + sessionId);
+            }
+            mSessionId = sessionId;
+            return this;
+        }
+
+        /**
+         * Sets the context the SoundPool belongs to.
+         *
+         * The context will be used to pull information, such as
+         * {@link android.content.AttributionSource} and device specific audio session ids,
+         * which will be associated with the {@link SoundPool}. However, the context itself will
+         * not be retained by the {@link SoundPool} instance after initialization.
+         *
+         * @param context a non-null {@link Context} instance
+         * @return the same {@link Builder} instance.
+         */
+        public @NonNull Builder setContext(@NonNull Context context) {
+            mContext = Objects.requireNonNull(context);
+            return this;
+        }
+
         public SoundPool build() {
             if (mAudioAttributes == null) {
                 mAudioAttributes = new AudioAttributes.Builder()
                         .setUsage(AudioAttributes.USAGE_MEDIA).build();
             }
-            return new SoundPool(mMaxStreams, mAudioAttributes);
+            return new SoundPool(mContext, mMaxStreams, mAudioAttributes, mSessionId);
         }
     }
 }
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/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/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 1183ca3..d8705a7 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -1285,7 +1285,7 @@
     CHECK(clazz.get() != NULL);
 
     jmethodID constructID =
-        env->GetMethodID(clazz.get(), "<init>", "(ILjava/lang/String;)V");
+        env->GetMethodID(clazz.get(), "<init>", "(Ljava/lang/String;IIII)V");
     CHECK(constructID != NULL);
 
     std::string defaultMsg = "Unknown Error";
@@ -1335,14 +1335,14 @@
             break;
     }
 
-    std::string msgStr(msg != NULL ? msg : defaultMsg.c_str());
-    if (crypto != NULL) {
-        msgStr = DrmUtils::GetExceptionMessage(err, msgStr.c_str(), crypto);
-    }
-    jstring msgObj = env->NewStringUTF(msgStr.c_str());
+    std::string originalMsg(msg != NULL ? msg : defaultMsg.c_str());
+    DrmStatus dStatus(err, originalMsg.c_str());
+    std::string detailedMsg(DrmUtils::GetExceptionMessage(dStatus, defaultMsg.c_str(), crypto));
+    jstring msgObj = env->NewStringUTF(detailedMsg.c_str());
 
     jthrowable exception =
-        (jthrowable)env->NewObject(clazz.get(), constructID, jerr, msgObj);
+        (jthrowable)env->NewObject(clazz.get(), constructID, msgObj, jerr,
+                                   dStatus.getCdmErr(), dStatus.getOemErr(), dStatus.getContext());
 
     env->Throw(exception);
 }
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/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
new file mode 100644
index 0000000..f480566
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/MediaPlayerUnitTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.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.AUDIO_SESSION_ID_GENERATE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.companion.virtual.VirtualDeviceManager;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.test.mock.MockContext;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(AndroidJUnit4.class)
+public class MediaPlayerUnitTest {
+
+    private static final int TEST_VIRTUAL_DEVICE_ID = 42;
+
+    @Test
+    public void testConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
+        int vdmPlaybackSessionId = getContext().getSystemService(
+                AudioManager.class).generateAudioSessionId();
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
+        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+
+        assertNotEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
+    }
+
+    @Test
+    public void testConstructionWithContext_virtualDeviceCustomAudioPolicy() {
+        int vdmPlaybackSessionId = getContext().getSystemService(
+                AudioManager.class).generateAudioSessionId();
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+
+        assertEquals(vdmPlaybackSessionId, mediaPlayer.getAudioSessionId());
+    }
+
+    @Test
+    public void testConstructionWithContext_virtualSetSessionIdOverridesContext() {
+        int vdmPlaybackSessionId = getContext().getSystemService(
+                AudioManager.class).generateAudioSessionId();
+        int anotherSessionId = getContext().getSystemService(
+                AudioManager.class).generateAudioSessionId();
+        VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
+                vdmPlaybackSessionId, DEVICE_POLICY_CUSTOM);
+        Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
+
+        MediaPlayer mediaPlayer = new MediaPlayer(virtualDeviceContext);
+        mediaPlayer.setAudioSessionId(anotherSessionId);
+
+        assertEquals(anotherSessionId, mediaPlayer.getAudioSessionId());
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
+        MockContext mockContext = mock(MockContext.class);
+        when(mockContext.getDeviceId()).thenReturn(deviceId);
+        when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
+        when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
+        return mockContext;
+    }
+
+    private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
+            int playbackSessionId, int audioDevicePolicy) {
+        VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
+        when(vdmMock.getAudioPlaybackSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
+        when(vdmMock.getAudioPlaybackSessionId(deviceId)).thenReturn(playbackSessionId);
+        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/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/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/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/applications/AppUtils.java b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
index 7f65837..cac3103 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/AppUtils.java
@@ -32,6 +32,7 @@
 import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -306,4 +307,17 @@
             }
         }
     }
+
+    /**
+     * Returns clone user profile id if present. Returns -1 if not present.
+     */
+    public static int getCloneUserId(Context context) {
+        UserManager userManager = context.getSystemService(UserManager.class);
+        for (UserHandle userHandle : userManager.getUserProfiles()) {
+            if (userManager.getUserInfo(userHandle.getIdentifier()).isCloneProfile()) {
+                return userHandle.getIdentifier();
+            }
+        }
+        return -1;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index ca5f57d..21e7d81 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -123,6 +123,7 @@
     final int mAdminRetrieveFlags;
     final int mRetrieveFlags;
     PackageIntentReceiver mPackageIntentReceiver;
+    PackageIntentReceiver mClonePackageIntentReceiver;
 
     boolean mResumed;
     boolean mHaveDisabledApps;
@@ -265,6 +266,15 @@
             mPackageIntentReceiver.registerReceiver();
         }
 
+        // Listen to any package additions in clone user to refresh the app list.
+        if (mClonePackageIntentReceiver == null) {
+            int cloneUserId = AppUtils.getCloneUserId(mContext);
+            if (cloneUserId != -1) {
+                mClonePackageIntentReceiver = new PackageIntentReceiver();
+                mClonePackageIntentReceiver.registerReceiverForClone(cloneUserId);
+            }
+        }
+
         final List<ApplicationInfo> prevApplications = mApplications;
         mApplications = new ArrayList<>();
         for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
@@ -456,6 +466,10 @@
             mPackageIntentReceiver.unregisterReceiver();
             mPackageIntentReceiver = null;
         }
+        if (mClonePackageIntentReceiver != null) {
+            mClonePackageIntentReceiver.unregisterReceiver();
+            mClonePackageIntentReceiver = null;
+        }
     }
 
     public AppEntry getEntry(String packageName, int userId) {
@@ -1526,6 +1540,12 @@
                 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
             }
         }
+
+        public void registerReceiverForClone(int cloneId) {
+            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
+            filter.addDataScheme("package");
+            mContext.registerReceiverAsUser(this, UserHandle.of(cloneId), filter, null, null);
+        }
     }
 
     /**
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/mobile/MobileStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
index 39b4b8e..35e3dd3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/MobileStatusTracker.java
@@ -231,7 +231,7 @@
         public SignalStrength signalStrength;
         public TelephonyDisplayInfo telephonyDisplayInfo =
                 new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+                        TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
 
         /**
          * Empty constructor
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/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index ed8a457..ed1a0f3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -564,6 +564,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 +2357,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/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index 68679c79..6f7d20a 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -455,8 +455,7 @@
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_HASH, bugreportHash);
         intent.putExtra(DevicePolicyManager.EXTRA_REMOTE_BUGREPORT_NONCE, nonce);
         intent.putExtra(EXTRA_BUGREPORT, bugreportFileName);
-        context.sendBroadcastAsUser(intent, UserHandle.SYSTEM,
-                android.Manifest.permission.DUMP);
+        context.sendBroadcast(intent, android.Manifest.permission.DUMP);
     }
 
     /**
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 87354c7..e1000e0 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -222,6 +222,7 @@
         "WindowManager-Shell",
         "LowLightDreamLib",
         "motion_tool_lib",
+        "androidx.core_core-animation-testing-nodeps",
     ],
 }
 
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/compose/core/src/com/android/systemui/compose/animation/Expandable.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
index 0abdeca..d31ca51 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/Expandable.kt
@@ -20,13 +20,20 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
 import androidx.compose.material3.contentColorFor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -34,45 +41,71 @@
 import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCompositionContext
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.draw.drawWithContent
 import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.RoundRect
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathOperation
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.drawscope.scale
 import androidx.compose.ui.layout.boundsInRoot
+import androidx.compose.ui.layout.findRootCoordinates
+import androidx.compose.ui.layout.layout
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.platform.ComposeView
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.dp
 import androidx.lifecycle.ViewTreeLifecycleOwner
 import androidx.lifecycle.ViewTreeViewModelStoreOwner
+import com.android.systemui.animation.Expandable
 import com.android.systemui.animation.LaunchAnimator
+import kotlin.math.max
 import kotlin.math.min
+import kotlin.math.roundToInt
 
 /**
  * Create an expandable shape that can launch into an Activity or a Dialog.
  *
+ * If this expandable should be expanded when it is clicked directly, then you should specify a
+ * [onClick] handler, which will ensure that this expandable interactive size and background size
+ * are consistent with the M3 components (48dp and 40dp respectively).
+ *
+ * If this expandable should be expanded when a children component is clicked, like a button inside
+ * the expandable, then you can use the Expandable parameter passed to the [content] lambda.
+ *
  * Example:
  * ```
  *    Expandable(
  *      color = MaterialTheme.colorScheme.primary,
  *      shape = RoundedCornerShape(16.dp),
- *    ) { controller ->
- *      Row(
- *        Modifier
- *          // For activities:
- *          .clickable { activityStarter.startActivity(intent, controller.forActivity()) }
  *
- *          // For dialogs:
- *          .clickable { dialogLaunchAnimator.show(dialog, controller.forDialog()) }
- *      ) { ... }
+ *      // For activities:
+ *      onClick = { expandable ->
+ *          activityStarter.startActivity(intent, expandable.activityLaunchController())
+ *      },
+ *
+ *      // For dialogs:
+ *      onClick = { expandable ->
+ *          dialogLaunchAnimator.show(dialog, controller.dialogLaunchController())
+ *      },
+ *    ) {
+ *      ...
  *    }
  * ```
  *
@@ -85,11 +118,16 @@
     shape: Shape,
     modifier: Modifier = Modifier,
     contentColor: Color = contentColorFor(color),
-    content: @Composable (ExpandableController) -> Unit,
+    borderStroke: BorderStroke? = null,
+    onClick: ((Expandable) -> Unit)? = null,
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable (Expandable) -> Unit,
 ) {
     Expandable(
-        rememberExpandableController(color, shape, contentColor),
+        rememberExpandableController(color, shape, contentColor, borderStroke),
         modifier,
+        onClick,
+        interactionSource,
         content,
     )
 }
@@ -118,11 +156,14 @@
  * @sample com.android.systemui.compose.gallery.ActivityLaunchScreen
  * @sample com.android.systemui.compose.gallery.DialogLaunchScreen
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun Expandable(
     controller: ExpandableController,
     modifier: Modifier = Modifier,
-    content: @Composable (ExpandableController) -> Unit,
+    onClick: ((Expandable) -> Unit)? = null,
+    interactionSource: MutableInteractionSource? = null,
+    content: @Composable (Expandable) -> Unit,
 ) {
     val controller = controller as ExpandableControllerImpl
     val color = controller.color
@@ -136,12 +177,34 @@
             CompositionLocalProvider(
                 LocalContentColor provides contentColor,
             ) {
-                content(controller)
+                // We make sure that the content itself (wrapped by the background) is at least
+                // 40.dp, which is the same as the M3 buttons. This applies even if onClick is
+                // null, to make it easier to write expandables that are sometimes clickable and
+                // sometimes not. There shouldn't be any Expandable smaller than 40dp because if
+                // the expandable is not clickable directly, then something in its content should
+                // be (and with a size >= 40dp).
+                val minSize = 40.dp
+                Box(
+                    Modifier.defaultMinSize(minWidth = minSize, minHeight = minSize),
+                    contentAlignment = Alignment.Center,
+                ) {
+                    content(controller.expandable)
+                }
             }
         }
 
-    val thisExpandableSize by remember {
-        derivedStateOf { controller.boundsInComposeViewRoot.value.size }
+    var thisExpandableSize by remember { mutableStateOf(Size.Zero) }
+
+    /** Set the current element size as this Expandable size. */
+    fun Modifier.updateExpandableSize(): Modifier {
+        return this.onGloballyPositioned { coords ->
+            thisExpandableSize =
+                coords
+                    .findRootCoordinates()
+                    // Make sure that we report the actual size, and not the visual/clipped one.
+                    .localBoundingBoxOf(coords, clipBounds = false)
+                    .size
+        }
     }
 
     // Make sure we don't read animatorState directly here to avoid recomposition every time the
@@ -152,22 +215,42 @@
         }
     }
 
+    // If this expandable is expanded when it's being directly clicked on, let's ensure that it has
+    // the minimum interactive size followed by all M3 components (48.dp).
+    val minInteractiveSizeModifier =
+        if (onClick != null && LocalMinimumTouchTargetEnforcement.current) {
+            // TODO(b/242040009): Replace this by Modifier.minimumInteractiveComponentSize() once
+            // http://aosp/2305511 is available.
+            val minTouchSize = LocalViewConfiguration.current.minimumTouchTargetSize
+            Modifier.layout { measurable, constraints ->
+                // Copied from androidx.compose.material3.InteractiveComponentSize.kt
+                val placeable = measurable.measure(constraints)
+                val width = maxOf(placeable.width, minTouchSize.width.roundToPx())
+                val height = maxOf(placeable.height, minTouchSize.height.roundToPx())
+                layout(width, height) {
+                    val centerX = ((width - placeable.width) / 2f).roundToInt()
+                    val centerY = ((height - placeable.height) / 2f).roundToInt()
+                    placeable.place(centerX, centerY)
+                }
+            }
+        } else {
+            Modifier
+        }
+
     when {
         isAnimating -> {
             // Don't compose the movable content during the animation, as it should be composed only
             // once at all times. We make this spacer exactly the same size as this Expandable when
             // it is visible.
             Spacer(
-                modifier
-                    .clip(shape)
-                    .requiredSize(with(controller.density) { thisExpandableSize.toDpSize() })
+                modifier.requiredSize(with(controller.density) { thisExpandableSize.toDpSize() })
             )
 
             // The content and its animated background in the overlay. We draw it only when we are
             // animating.
             AnimatedContentInOverlay(
                 color,
-                thisExpandableSize,
+                controller.boundsInComposeViewRoot.value.size,
                 controller.animatorState,
                 controller.overlay.value
                     ?: error("AnimatedContentInOverlay shouldn't be composed with null overlay."),
@@ -181,6 +264,8 @@
         controller.isDialogShowing.value -> {
             Box(
                 modifier
+                    .updateExpandableSize()
+                    .then(minInteractiveSizeModifier)
                     .drawWithContent { /* Don't draw anything when the dialog is shown. */}
                     .onGloballyPositioned {
                         controller.boundsInComposeViewRoot.value = it.boundsInRoot()
@@ -188,11 +273,36 @@
             ) { wrappedContent(controller) }
         }
         else -> {
-            Box(
-                modifier.clip(shape).background(color, shape).onGloballyPositioned {
-                    controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+            val clickModifier =
+                if (onClick != null) {
+                    if (interactionSource != null) {
+                        // If the caller provided an interaction source, then that means that they
+                        // will draw the click indication themselves.
+                        Modifier.clickable(interactionSource, indication = null) {
+                            onClick(controller.expandable)
+                        }
+                    } else {
+                        // If no interaction source is provided, we draw the default indication (a
+                        // ripple) and make sure it's clipped by the expandable shape.
+                        Modifier.clip(shape).clickable { onClick(controller.expandable) }
+                    }
+                } else {
+                    Modifier
                 }
-            ) { wrappedContent(controller) }
+
+            Box(
+                modifier
+                    .updateExpandableSize()
+                    .then(minInteractiveSizeModifier)
+                    .then(clickModifier)
+                    .background(color, shape)
+                    .border(controller)
+                    .onGloballyPositioned {
+                        controller.boundsInComposeViewRoot.value = it.boundsInRoot()
+                    },
+            ) {
+                wrappedContent(controller)
+            }
         }
     }
 }
@@ -204,7 +314,7 @@
     sizeInOriginalLayout: Size,
     animatorState: State<LaunchAnimator.State?>,
     overlay: ViewGroupOverlay,
-    controller: ExpandableController,
+    controller: ExpandableControllerImpl,
     content: @Composable (ExpandableController) -> Unit,
     composeViewRoot: View,
     onOverlayComposeViewChanged: (View?) -> Unit,
@@ -254,24 +364,7 @@
                                     return@drawWithContent
                                 }
 
-                                val topRadius = animatorState.topCornerRadius
-                                val bottomRadius = animatorState.bottomCornerRadius
-                                if (topRadius == bottomRadius) {
-                                    // Shortcut to avoid Outline calculation and allocation.
-                                    val cornerRadius = CornerRadius(topRadius)
-                                    drawRoundRect(color, cornerRadius = cornerRadius)
-                                } else {
-                                    val shape =
-                                        RoundedCornerShape(
-                                            topStart = topRadius,
-                                            topEnd = topRadius,
-                                            bottomStart = bottomRadius,
-                                            bottomEnd = bottomRadius,
-                                        )
-                                    val outline = shape.createOutline(size, layoutDirection, this)
-                                    drawOutline(outline, color = color)
-                                }
-
+                                drawBackground(animatorState, color, controller.borderStroke)
                                 drawContent()
                             },
                             // We center the content in the expanding container.
@@ -360,3 +453,107 @@
     overlay.remove(view)
     return current as ViewGroup
 }
+
+private fun Modifier.border(controller: ExpandableControllerImpl): Modifier {
+    return if (controller.borderStroke != null) {
+        this.border(controller.borderStroke, controller.shape)
+    } else {
+        this
+    }
+}
+
+private fun ContentDrawScope.drawBackground(
+    animatorState: LaunchAnimator.State,
+    color: Color,
+    border: BorderStroke?,
+) {
+    val topRadius = animatorState.topCornerRadius
+    val bottomRadius = animatorState.bottomCornerRadius
+    if (topRadius == bottomRadius) {
+        // Shortcut to avoid Outline calculation and allocation.
+        val cornerRadius = CornerRadius(topRadius)
+
+        // Draw the background.
+        drawRoundRect(color, cornerRadius = cornerRadius)
+
+        // Draw the border.
+        if (border != null) {
+            // Copied from androidx.compose.foundation.Border.kt
+            val strokeWidth = border.width.toPx()
+            val halfStroke = strokeWidth / 2
+            val borderStroke = Stroke(strokeWidth)
+
+            drawRoundRect(
+                brush = border.brush,
+                topLeft = Offset(halfStroke, halfStroke),
+                size = Size(size.width - strokeWidth, size.height - strokeWidth),
+                cornerRadius = cornerRadius.shrink(halfStroke),
+                style = borderStroke
+            )
+        }
+    } else {
+        val shape =
+            RoundedCornerShape(
+                topStart = topRadius,
+                topEnd = topRadius,
+                bottomStart = bottomRadius,
+                bottomEnd = bottomRadius,
+            )
+        val outline = shape.createOutline(size, layoutDirection, this)
+
+        // Draw the background.
+        drawOutline(outline, color = color)
+
+        // Draw the border.
+        if (border != null) {
+            // Copied from androidx.compose.foundation.Border.kt.
+            val strokeWidth = border.width.toPx()
+            val path =
+                createRoundRectPath(
+                    (outline as Outline.Rounded).roundRect,
+                    strokeWidth,
+                )
+
+            drawPath(path, border.brush)
+        }
+    }
+}
+
+/**
+ * Helper method that creates a round rect with the inner region removed by the given stroke width.
+ *
+ * Copied from androidx.compose.foundation.Border.kt.
+ */
+private fun createRoundRectPath(
+    roundedRect: RoundRect,
+    strokeWidth: Float,
+): Path {
+    return Path().apply {
+        addRoundRect(roundedRect)
+        val insetPath =
+            Path().apply { addRoundRect(createInsetRoundedRect(strokeWidth, roundedRect)) }
+        op(this, insetPath, PathOperation.Difference)
+    }
+}
+
+/* Copied from androidx.compose.foundation.Border.kt. */
+private fun createInsetRoundedRect(widthPx: Float, roundedRect: RoundRect) =
+    RoundRect(
+        left = widthPx,
+        top = widthPx,
+        right = roundedRect.width - widthPx,
+        bottom = roundedRect.height - widthPx,
+        topLeftCornerRadius = roundedRect.topLeftCornerRadius.shrink(widthPx),
+        topRightCornerRadius = roundedRect.topRightCornerRadius.shrink(widthPx),
+        bottomLeftCornerRadius = roundedRect.bottomLeftCornerRadius.shrink(widthPx),
+        bottomRightCornerRadius = roundedRect.bottomRightCornerRadius.shrink(widthPx)
+    )
+
+/**
+ * Helper method to shrink the corner radius by the given value, clamping to 0 if the resultant
+ * corner radius would be negative.
+ *
+ * Copied from androidx.compose.foundation.Border.kt.
+ */
+private fun CornerRadius.shrink(value: Float): CornerRadius =
+    CornerRadius(max(0f, this.x - value), max(0f, this.y - value))
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
index d6db574..f75b3a8 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/animation/ExpandableController.kt
@@ -20,6 +20,7 @@
 import android.view.ViewGroup
 import android.view.ViewGroupOverlay
 import android.view.ViewRootImpl
+import androidx.compose.foundation.BorderStroke
 import androidx.compose.material3.contentColorFor
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
@@ -62,6 +63,7 @@
     color: Color,
     shape: Shape,
     contentColor: Color = contentColorFor(color),
+    borderStroke: BorderStroke? = null,
 ): ExpandableController {
     val composeViewRoot = LocalView.current
     val density = LocalDensity.current
@@ -87,11 +89,20 @@
     val isComposed = remember { mutableStateOf(true) }
     DisposableEffect(Unit) { onDispose { isComposed.value = false } }
 
-    return remember(color, contentColor, shape, composeViewRoot, density, layoutDirection) {
+    return remember(
+        color,
+        contentColor,
+        shape,
+        borderStroke,
+        composeViewRoot,
+        density,
+        layoutDirection,
+    ) {
         ExpandableControllerImpl(
             color,
             contentColor,
             shape,
+            borderStroke,
             composeViewRoot,
             density,
             animatorState,
@@ -109,6 +120,7 @@
     internal val color: Color,
     internal val contentColor: Color,
     internal val shape: Shape,
+    internal val borderStroke: BorderStroke?,
     internal val composeViewRoot: View,
     internal val density: Density,
     internal val animatorState: MutableState<LaunchAnimator.State?>,
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/FadingBackground.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/FadingBackground.kt
new file mode 100644
index 0000000..121bf2c
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/modifiers/FadingBackground.kt
@@ -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.systemui.compose.modifiers
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.DrawModifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Outline
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawOutline
+import androidx.compose.ui.graphics.drawscope.ContentDrawScope
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.platform.InspectorValueInfo
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.unit.LayoutDirection
+
+/**
+ * Draws a fading [shape] with a solid [color] and [alpha] behind the content.
+ *
+ * @param color color to paint background with
+ * @param alpha alpha of the background
+ * @param shape desired shape of the background
+ */
+fun Modifier.background(
+    color: Color,
+    alpha: () -> Float,
+    shape: Shape = RectangleShape,
+) =
+    this.then(
+        FadingBackground(
+            brush = SolidColor(color),
+            alpha = alpha,
+            shape = shape,
+            inspectorInfo =
+                debugInspectorInfo {
+                    name = "background"
+                    value = color
+                    properties["color"] = color
+                    properties["alpha"] = alpha
+                    properties["shape"] = shape
+                }
+        )
+    )
+
+private class FadingBackground
+constructor(
+    private val brush: Brush,
+    private val shape: Shape,
+    private val alpha: () -> Float,
+    inspectorInfo: InspectorInfo.() -> Unit
+) : DrawModifier, InspectorValueInfo(inspectorInfo) {
+    // naive cache outline calculation if size is the same
+    private var lastSize: Size? = null
+    private var lastLayoutDirection: LayoutDirection? = null
+    private var lastOutline: Outline? = null
+
+    override fun ContentDrawScope.draw() {
+        if (shape === RectangleShape) {
+            // shortcut to avoid Outline calculation and allocation
+            drawRect()
+        } else {
+            drawOutline()
+        }
+        drawContent()
+    }
+
+    private fun ContentDrawScope.drawRect() {
+        drawRect(brush, alpha = alpha())
+    }
+
+    private fun ContentDrawScope.drawOutline() {
+        val outline =
+            if (size == lastSize && layoutDirection == lastLayoutDirection) {
+                lastOutline!!
+            } else {
+                shape.createOutline(size, layoutDirection, this)
+            }
+        drawOutline(outline, brush = brush, alpha = alpha())
+        lastOutline = outline
+        lastSize = size
+        lastLayoutDirection = layoutDirection
+    }
+
+    override fun hashCode(): Int {
+        var result = brush.hashCode()
+        result = 31 * result + alpha.hashCode()
+        result = 31 * result + shape.hashCode()
+        return result
+    }
+
+    override fun equals(other: Any?): Boolean {
+        val otherModifier = other as? FadingBackground ?: return false
+        return brush == otherModifier.brush &&
+            alpha == otherModifier.alpha &&
+            shape == otherModifier.shape
+    }
+
+    override fun toString(): String = "FadingBackground(brush=$brush, alpha = $alpha, shape=$shape)"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
index b8639e6..caa7e5f 100644
--- a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/AndroidColorScheme.kt
@@ -65,10 +65,12 @@
     val colorForeground = getColor(context, R.attr.colorForeground)
     val colorForegroundInverse = getColor(context, R.attr.colorForegroundInverse)
 
-    private fun getColor(context: Context, attr: Int): Color {
-        val ta = context.obtainStyledAttributes(intArrayOf(attr))
-        @ColorInt val color = ta.getColor(0, 0)
-        ta.recycle()
-        return Color(color)
+    companion object {
+        fun getColor(context: Context, attr: Int): Color {
+            val ta = context.obtainStyledAttributes(intArrayOf(attr))
+            @ColorInt val color = ta.getColor(0, 0)
+            ta.recycle()
+            return Color(color)
+        }
     }
 }
diff --git a/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt
new file mode 100644
index 0000000..de47cce
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/systemui/compose/theme/Color.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.compose.theme
+
+import android.annotation.AttrRes
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+
+/** Read the [Color] from the given [attribute]. */
+@Composable
+@ReadOnlyComposable
+fun colorAttr(@AttrRes attribute: Int): Color {
+    return AndroidColorScheme.getColor(LocalContext.current, attribute)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
new file mode 100644
index 0000000..4a5ad65
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/ContentDescription.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.common.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.android.systemui.common.shared.model.ContentDescription
+
+/** Returns the loaded [String] or `null` if there isn't one. */
+@Composable
+fun ContentDescription.load(): String? {
+    return when (this) {
+        is ContentDescription.Loaded -> description
+        is ContentDescription.Resource -> stringResource(res)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.kt
new file mode 100644
index 0000000..6e83124
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/common/ui/compose/Icon.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.systemui.common.ui.compose
+
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asImageBitmap
+import androidx.compose.ui.res.painterResource
+import androidx.core.graphics.drawable.toBitmap
+import com.android.systemui.common.shared.model.Icon
+
+/**
+ * Icon composable that draws [icon] using [tint].
+ *
+ * Note: You can use [Color.Unspecified] to disable the tint and keep the original icon colors.
+ */
+@Composable
+fun Icon(
+    icon: Icon,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current,
+) {
+    val contentDescription = icon.contentDescription?.load()
+    when (icon) {
+        is Icon.Loaded -> {
+            Icon(icon.drawable.toBitmap().asImageBitmap(), contentDescription, modifier, tint)
+        }
+        is Icon.Resource -> Icon(painterResource(icon.res), contentDescription, modifier, tint)
+    }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index 2bf1937..2aac46e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -53,7 +53,6 @@
 import com.android.systemui.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
 import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import kotlinx.coroutines.flow.collect
 
 /**
  * Compose the screen associated to a [PeopleViewModel].
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
new file mode 100644
index 0000000..654b723
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/FooterActions.kt
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.footer.ui.compose
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.LocalIndication
+import androidx.compose.foundation.indication
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalContentColor
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.em
+import androidx.compose.ui.unit.sp
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.animation.Expandable
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.ui.compose.Icon
+import com.android.systemui.compose.animation.Expandable
+import com.android.systemui.compose.modifiers.background
+import com.android.systemui.compose.theme.LocalAndroidColorScheme
+import com.android.systemui.compose.theme.colorAttr
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import kotlinx.coroutines.launch
+
+/** The Quick Settings footer actions row. */
+@Composable
+fun FooterActions(
+    viewModel: FooterActionsViewModel,
+    qsVisibilityLifecycleOwner: LifecycleOwner,
+    modifier: Modifier = Modifier,
+) {
+    val context = LocalContext.current
+
+    // Collect visibility and alphas as soon as we are composed, even when not visible.
+    val isVisible by viewModel.isVisible.collectAsState()
+    val alpha by viewModel.alpha.collectAsState()
+    val backgroundAlpha = viewModel.backgroundAlpha.collectAsState()
+
+    var security by remember { mutableStateOf<FooterActionsSecurityButtonViewModel?>(null) }
+    var foregroundServices by remember {
+        mutableStateOf<FooterActionsForegroundServicesButtonViewModel?>(null)
+    }
+    var userSwitcher by remember { mutableStateOf<FooterActionsButtonViewModel?>(null) }
+
+    LaunchedEffect(
+        context,
+        qsVisibilityLifecycleOwner,
+        viewModel,
+        viewModel.security,
+        viewModel.foregroundServices,
+        viewModel.userSwitcher,
+    ) {
+        launch {
+            // Listen for dialog requests as soon as we are composed, even when not visible.
+            viewModel.observeDeviceMonitoringDialogRequests(context)
+        }
+
+        // Listen for model changes only when QS are visible.
+        qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+            launch { viewModel.security.collect { security = it } }
+            launch { viewModel.foregroundServices.collect { foregroundServices = it } }
+            launch { viewModel.userSwitcher.collect { userSwitcher = it } }
+        }
+    }
+
+    val backgroundColor = colorAttr(R.attr.underSurfaceColor)
+    val contentColor = LocalAndroidColorScheme.current.textColorPrimary
+    val backgroundTopRadius = dimensionResource(R.dimen.qs_corner_radius)
+    val backgroundModifier =
+        remember(
+            backgroundColor,
+            backgroundAlpha,
+            backgroundTopRadius,
+        ) {
+            Modifier.background(
+                backgroundColor,
+                backgroundAlpha::value,
+                RoundedCornerShape(topStart = backgroundTopRadius, topEnd = backgroundTopRadius),
+            )
+        }
+
+    Row(
+        modifier
+            .fillMaxWidth()
+            .graphicsLayer { this.alpha = alpha }
+            .drawWithContent {
+                if (isVisible) {
+                    drawContent()
+                }
+            }
+            .then(backgroundModifier)
+            .padding(
+                top = dimensionResource(R.dimen.qs_footer_actions_top_padding),
+                bottom = dimensionResource(R.dimen.qs_footer_actions_bottom_padding),
+            )
+            .layout { measurable, constraints ->
+                // All buttons have a 4dp padding to increase their touch size. To be consistent
+                // with the View implementation, we want to left-most and right-most buttons to be
+                // visually aligned with the left and right sides of this row. So we let this
+                // component be 2*4dp wider and then offset it by -4dp to the start.
+                val inset = 4.dp.roundToPx()
+                val additionalWidth = inset * 2
+                val newConstraints =
+                    if (constraints.hasBoundedWidth) {
+                        constraints.copy(maxWidth = constraints.maxWidth + additionalWidth)
+                    } else {
+                        constraints
+                    }
+                val placeable = measurable.measure(newConstraints)
+
+                val width = constraints.constrainWidth(placeable.width - additionalWidth)
+                val height = constraints.constrainHeight(placeable.height)
+                layout(width, height) { placeable.place(-inset, 0) }
+            },
+        verticalAlignment = Alignment.CenterVertically,
+    ) {
+        CompositionLocalProvider(
+            LocalContentColor provides contentColor,
+        ) {
+            if (security == null && foregroundServices == null) {
+                Spacer(Modifier.weight(1f))
+            }
+
+            security?.let { SecurityButton(it, Modifier.weight(1f)) }
+            foregroundServices?.let { ForegroundServicesButton(it) }
+            userSwitcher?.let { IconButton(it) }
+            IconButton(viewModel.settings)
+            viewModel.power?.let { IconButton(it) }
+        }
+    }
+}
+
+/** The security button. */
+@Composable
+private fun SecurityButton(
+    model: FooterActionsSecurityButtonViewModel,
+    modifier: Modifier = Modifier,
+) {
+    val onClick: ((Expandable) -> Unit)? =
+        model.onClick?.let { onClick ->
+            val context = LocalContext.current
+            { expandable -> onClick(context, expandable) }
+        }
+
+    TextButton(
+        model.icon,
+        model.text,
+        showNewDot = false,
+        onClick = onClick,
+        modifier,
+    )
+}
+
+/** The foreground services button. */
+@Composable
+private fun RowScope.ForegroundServicesButton(
+    model: FooterActionsForegroundServicesButtonViewModel,
+) {
+    if (model.displayText) {
+        TextButton(
+            Icon.Resource(R.drawable.ic_info_outline, contentDescription = null),
+            model.text,
+            showNewDot = model.hasNewChanges,
+            onClick = model.onClick,
+            Modifier.weight(1f),
+        )
+    } else {
+        NumberButton(
+            model.foregroundServicesCount,
+            showNewDot = model.hasNewChanges,
+            onClick = model.onClick,
+        )
+    }
+}
+
+/** A button with an icon. */
+@Composable
+private fun IconButton(
+    model: FooterActionsButtonViewModel,
+    modifier: Modifier = Modifier,
+) {
+    Expandable(
+        color = colorAttr(model.backgroundColor),
+        shape = CircleShape,
+        onClick = model.onClick,
+        modifier = modifier,
+    ) {
+        val tint = model.iconTint?.let { Color(it) } ?: Color.Unspecified
+        Icon(
+            model.icon,
+            tint = tint,
+            modifier = Modifier.size(20.dp),
+        )
+    }
+}
+
+/** A button with a number an an optional dot (to indicate new changes). */
+@Composable
+private fun NumberButton(
+    number: Int,
+    showNewDot: Boolean,
+    onClick: (Expandable) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    // By default Expandable will show a ripple above its content when clicked, and clip the content
+    // with the shape of the expandable. In this case we also want to show a "new changes dot"
+    // outside of the shape, so we can't clip. To work around that we can pass our own interaction
+    // source and draw the ripple indication ourselves above the text but below the "new changes
+    // dot".
+    val interactionSource = remember { MutableInteractionSource() }
+
+    Expandable(
+        color = colorAttr(R.attr.offStateColor),
+        shape = CircleShape,
+        onClick = onClick,
+        interactionSource = interactionSource,
+        modifier = modifier,
+    ) {
+        Box(Modifier.size(40.dp)) {
+            Box(
+                Modifier.fillMaxSize()
+                    .clip(CircleShape)
+                    .indication(
+                        interactionSource,
+                        LocalIndication.current,
+                    )
+            ) {
+                Text(
+                    number.toString(),
+                    modifier = Modifier.align(Alignment.Center),
+                    style = MaterialTheme.typography.bodyLarge,
+                    color = LocalAndroidColorScheme.current.textColorPrimary,
+                    // TODO(b/242040009): This should only use a standard text style instead and
+                    // should not override the text size.
+                    fontSize = 18.sp,
+                )
+            }
+
+            if (showNewDot) {
+                NewChangesDot(Modifier.align(Alignment.BottomEnd))
+            }
+        }
+    }
+}
+
+/** A dot that indicates new changes. */
+@Composable
+private fun NewChangesDot(modifier: Modifier = Modifier) {
+    val contentDescription = stringResource(R.string.fgs_dot_content_description)
+    val color = LocalAndroidColorScheme.current.colorAccentTertiary
+
+    Canvas(modifier.size(12.dp).semantics { this.contentDescription = contentDescription }) {
+        drawCircle(color)
+    }
+}
+
+/** A larger button with an icon, some text and an optional dot (to indicate new changes). */
+@Composable
+private fun TextButton(
+    icon: Icon,
+    text: String,
+    showNewDot: Boolean,
+    onClick: ((Expandable) -> Unit)?,
+    modifier: Modifier = Modifier,
+) {
+    Expandable(
+        shape = CircleShape,
+        color = colorAttr(R.attr.underSurfaceColor),
+        contentColor = LocalAndroidColorScheme.current.textColorSecondary,
+        borderStroke = BorderStroke(1.dp, LocalAndroidColorScheme.current.colorBackground),
+        modifier = modifier.padding(horizontal = 4.dp),
+        onClick = onClick,
+    ) {
+        Row(
+            Modifier.padding(horizontal = dimensionResource(R.dimen.qs_footer_padding)),
+            verticalAlignment = Alignment.CenterVertically,
+        ) {
+            Icon(icon, Modifier.padding(end = 12.dp).size(20.dp))
+
+            Text(
+                text,
+                Modifier.weight(1f),
+                style = MaterialTheme.typography.bodyMedium,
+                // TODO(b/242040009): Remove this letter spacing. We should only use the M3 text
+                // styles without modifying them.
+                letterSpacing = 0.01.em,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+            )
+
+            if (showNewDot) {
+                NewChangesDot(Modifier.padding(start = 8.dp))
+            }
+
+            if (onClick != null) {
+                Icon(
+                    painterResource(com.android.internal.R.drawable.ic_chevron_end),
+                    contentDescription = null,
+                    Modifier.padding(start = 8.dp).size(20.dp),
+                )
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
index 923b99f..e197752 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/data/content/KeyguardQuickAffordanceProviderContract.kt
@@ -145,6 +145,9 @@
         const val TABLE_NAME = "flags"
         val URI: Uri = BASE_URI.buildUpon().path(TABLE_NAME).build()
 
+        /** Flag denoting whether the Wallpaper Picker should use the new, revamped UI. */
+        const val FLAG_NAME_REVAMPED_WALLPAPER_UI = "revamped_wallpaper_ui"
+
         /**
          * Flag denoting whether the customizable lock screen quick affordances feature is enabled.
          */
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-keyguard/layout/fsi_chrome_view.xml b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
new file mode 100644
index 0000000..4ff2967
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/fsi_chrome_view.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.android.systemui.statusbar.notification.fsi.FsiChromeView android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_margin="50dp"
+    android:orientation="vertical"
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:id="@+id/fsi_chrome"
+        android:layout_height="50dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/fsi_app_icon"
+            android:layout_width="50dp"
+            android:layout_height="match_parent"
+            android:contentDescription="@null" />
+
+        <TextView
+            android:id="@+id/fsi_app_name"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:padding="10dp"
+            android:textSize="22dp"
+            android:gravity="center"
+            android:textColor="#FFFFFF"
+            android:text="AppName" />
+
+        <Space
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_weight="1" />
+
+        <Button
+            android:id="@+id/fsi_fullscreen_button"
+            android:layout_width="100dp"
+            android:layout_height="match_parent"
+            android:text="fullscreen" />
+
+        <Button
+            android:id="@+id/fsi_dismiss_button"
+            android:layout_width="100dp"
+            android:layout_height="match_parent"
+            android:text="dismiss" />
+
+    </LinearLayout>
+
+</com.android.systemui.statusbar.notification.fsi.FsiChromeView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/remote_input.xml b/packages/SystemUI/res/layout/remote_input.xml
index a5b2f80..f4b0a45 100644
--- a/packages/SystemUI/res/layout/remote_input.xml
+++ b/packages/SystemUI/res/layout/remote_input.xml
@@ -20,6 +20,7 @@
 <com.android.systemui.statusbar.policy.RemoteInputView
         xmlns:android="http://schemas.android.com/apk/res/android"
         android:id="@+id/remote_input"
+        android:forceHasOverlappingRendering="false"
         android:layout_height="match_parent"
         android:layout_width="match_parent">
     <LinearLayout
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-television/integers.xml b/packages/SystemUI/res/values-television/integers.xml
index b265d78..02f5d0d 100644
--- a/packages/SystemUI/res/values-television/integers.xml
+++ b/packages/SystemUI/res/values-television/integers.xml
@@ -18,8 +18,9 @@
     <!-- The position of the volume dialog on the screen.
          See com.android.systemui.volume.VolumeDialogImpl.
          Value 81 corresponds to BOTTOM|CENTER_HORIZONTAL.
-         Value 21 corresponds to RIGHT|CENTER_VERTICAL. -->
-    <integer name="volume_dialog_gravity">21</integer>
+         Value 21 corresponds to RIGHT|CENTER_VERTICAL.
+         Value 8388629 corresponds to END|CENTER_VERTICAL -->
+    <integer name="volume_dialog_gravity">8388629</integer>
 
     <integer name="privacy_chip_animation_millis">300</integer>
 </resources>
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 227c0dd..ea1095d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -218,6 +218,9 @@
     <!-- Radius for notifications corners with adjacent notifications -->
     <dimen name="notification_corner_radius_small">4dp</dimen>
 
+    <!-- Vertical padding of the FSI container -->
+    <dimen name="fsi_chrome_vertical_padding">80dp</dimen>
+
     <!-- the padding of the shelf icon container -->
     <dimen name="shelf_icon_container_padding">13dp</dimen>
 
@@ -1283,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/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/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/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index cb6ffbd..76c1158 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -72,6 +72,7 @@
 import android.os.PowerExemptionManager;
 import android.os.PowerManager;
 import android.os.ServiceManager;
+import android.os.SystemUpdateManager;
 import android.os.UserManager;
 import android.os.Vibrator;
 import android.os.storage.StorageManager;
@@ -140,6 +141,12 @@
         return context.getSystemService(AlarmManager.class);
     }
 
+    @Provides
+    @Singleton
+    static Optional<SystemUpdateManager> provideSystemUpdateManager(Context context) {
+        return Optional.ofNullable(context.getSystemService(SystemUpdateManager.class));
+    }
+
     /** */
     @Provides
     public AmbientDisplayConfiguration provideAmbientDisplayConfiguration(Context context) {
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/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/DreamCallbackController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
new file mode 100644
index 0000000..ab4632b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamCallbackController.kt
@@ -0,0 +1,45 @@
+/*
+ * 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-related callback information */
+@SysUISingleton
+class DreamCallbackController @Inject constructor() :
+    CallbackController<DreamCallbackController.DreamCallback> {
+
+    private val callbacks = mutableSetOf<DreamCallbackController.DreamCallback>()
+
+    override fun addCallback(callback: DreamCallbackController.DreamCallback) {
+        callbacks.add(callback)
+    }
+
+    override fun removeCallback(callback: DreamCallbackController.DreamCallback) {
+        callbacks.remove(callback)
+    }
+
+    fun onWakeUp() {
+        callbacks.forEach { it.onWakeUp() }
+    }
+
+    interface DreamCallback {
+        fun onWakeUp()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
index 9b8ef71..abe9355 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,63 @@
 
     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 ->
+                            setElementsTranslationYAtPosition(
+                                px,
+                                ComplicationLayoutParams.POSITION_TOP
+                            )
+                            setElementsTranslationYAtPosition(
+                                px,
+                                ComplicationLayoutParams.POSITION_BOTTOM
+                            )
+                        }
+                }
+
+                /* Alpha animations, when moving from DREAMING->LOCKSCREEN state */
+                launch {
+                    transitionViewModel.dreamOverlayAlpha.collect { alpha ->
+                        setElementsAlphaAtPosition(
+                            alpha = alpha,
+                            position = ComplicationLayoutParams.POSITION_TOP,
+                            fadingOut = true,
+                        )
+                        setElementsAlphaAtPosition(
+                            alpha = alpha,
+                            position = ComplicationLayoutParams.POSITION_BOTTOM,
+                            fadingOut = true,
+                        )
+                    }
+                }
+            }
+
+            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 +170,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 +281,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/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..1763dd9 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 DreamCallbackController mDreamCallbackController;
     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,
+            DreamCallbackController dreamCallbackController) {
         mContext = context;
         mExecutor = executor;
         mWindowManager = windowManager;
@@ -158,6 +160,7 @@
         mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback);
         mStateController = stateController;
         mUiEventLogger = uiEventLogger;
+        mDreamCallbackController = dreamCallbackController;
 
         final ViewModelStore viewModelStore = new ViewModelStore();
         final Complication.Host host =
@@ -242,6 +245,7 @@
     public void onWakeUp(@NonNull Runnable onCompletedCallback) {
         mExecutor.execute(() -> {
             if (mDreamOverlayContainerViewController != null) {
+                mDreamCallbackController.onWakeUp();
                 mDreamOverlayContainerViewController.wakeUp(onCompletedCallback, mExecutor);
             }
         });
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 64911ec..4268d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -103,6 +103,11 @@
     // TODO(b/257315550): Tracking Bug
     val NO_HUN_FOR_OLD_WHEN = unreleasedFlag(118, "no_hun_for_old_when", teamfood = true)
 
+    // TODO(b/260335638): Tracking Bug
+    @JvmField
+    val NOTIFICATION_INLINE_REPLY_ANIMATION =
+        unreleasedFlag(174148361, "notification_inline_reply_animation", teamfood = true)
+
     val FILTER_UNSEEN_NOTIFS_ON_KEYGUARD =
         unreleasedFlag(254647461, "filter_unseen_notifs_on_keyguard", teamfood = true)
 
@@ -183,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")
@@ -195,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")
@@ -310,6 +320,10 @@
     // 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")
 
@@ -417,7 +431,7 @@
 
     // TODO(b/261979569): Tracking Bug
     val QUICK_TAP_FLOW_FRAMEWORK =
-            unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
+        unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
 
     // 1500 - chooser
     // TODO(b/254512507): Tracking Bug
@@ -442,11 +456,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..9a0fbbf 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,8 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamCallbackController
+import com.android.systemui.dreams.DreamCallbackController.DreamCallback
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -47,6 +49,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.merge
 
 /** Defines interface for classes that encapsulate application state for the keyguard. */
 interface KeyguardRepository {
@@ -176,6 +179,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val dozeTransitionListener: DozeTransitionListener,
     private val authController: AuthController,
+    private val dreamCallbackController: DreamCallbackController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -276,22 +280,35 @@
             .distinctUntilChanged()
 
     override val isDreaming: Flow<Boolean> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : KeyguardUpdateMonitorCallback() {
-                        override fun onDreamingStateChanged(isDreaming: Boolean) {
-                            trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+        merge(
+                conflatedCallbackFlow {
+                    val callback =
+                        object : KeyguardUpdateMonitorCallback() {
+                            override fun onDreamingStateChanged(isDreaming: Boolean) {
+                                trySendWithFailureLogging(isDreaming, TAG, "updated isDreaming")
+                            }
                         }
-                    }
-                keyguardUpdateMonitor.registerCallback(callback)
-                trySendWithFailureLogging(
-                    keyguardUpdateMonitor.isDreaming,
-                    TAG,
-                    "initial isDreaming",
-                )
+                    keyguardUpdateMonitor.registerCallback(callback)
+                    trySendWithFailureLogging(
+                        keyguardUpdateMonitor.isDreaming,
+                        TAG,
+                        "initial isDreaming",
+                    )
 
-                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
-            }
+                    awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+                },
+                conflatedCallbackFlow {
+                    val callback =
+                        object : DreamCallback {
+                            override fun onWakeUp() {
+                                trySendWithFailureLogging(false, TAG, "updated isDreaming")
+                            }
+                        }
+                    dreamCallbackController.addCallback(callback)
+
+                    awaitClose { dreamCallbackController.removeCallback(callback) }
+                }
+            )
             .distinctUntilChanged()
 
     override val linearDozeAmount: Flow<Float> = conflatedCallbackFlow {
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..d72d7183b 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?
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
index b73ce9e..188930c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DreamingTransitionInteractor.kt
@@ -28,6 +28,8 @@
 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
@@ -110,7 +112,7 @@
                                 name,
                                 KeyguardState.DREAMING,
                                 KeyguardState.LOCKSCREEN,
-                                getAnimator(),
+                                getAnimator(TO_LOCKSCREEN_DURATION),
                             )
                         )
                     }
@@ -167,14 +169,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/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 8eace76..9772cb9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -40,13 +40,13 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
+import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
-import javax.inject.Inject
 
 @SysUISingleton
 class KeyguardQuickAffordanceInteractor
@@ -294,10 +294,7 @@
             SystemUIDialog.setShowForAllUsers(dialog, true)
             SystemUIDialog.registerDismissListener(dialog)
             SystemUIDialog.setDialogSize(dialog)
-            launchAnimator.show(
-                dialog,
-                controller
-            )
+            launchAnimator.show(dialog, controller)
         }
     }
 
@@ -355,6 +352,10 @@
     fun getPickerFlags(): List<KeyguardPickerFlag> {
         return listOf(
             KeyguardPickerFlag(
+                name = Contract.FlagsTable.FLAG_NAME_REVAMPED_WALLPAPER_UI,
+                value = featureFlags.isEnabled(Flags.REVAMPED_WALLPAPER_UI),
+            ),
+            KeyguardPickerFlag(
                 name = Contract.FlagsTable.FLAG_NAME_CUSTOM_LOCK_SCREEN_QUICK_AFFORDANCES_ENABLED,
                 value = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES),
             ),
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/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/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..402fac1
--- /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.DreamingTransitionInteractor.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/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
index 6db3c99..30f8124 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
@@ -237,7 +237,13 @@
             return
         }
 
-        buttonView.setBackgroundResource(model.background)
+        val backgroundResource =
+            when (model.backgroundColor) {
+                R.attr.offStateColor -> R.drawable.qs_footer_action_circle
+                com.android.internal.R.attr.colorAccent -> R.drawable.qs_footer_action_circle_color
+                else -> error("Unsupported icon background resource ${model.backgroundColor}")
+            }
+        buttonView.setBackgroundResource(backgroundResource)
         buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
 
         val icon = model.icon
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
index 8d819da..2670787 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsButtonViewModel.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.qs.footer.ui.viewmodel
 
-import android.annotation.DrawableRes
+import android.annotation.AttrRes
+import android.annotation.ColorInt
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 
@@ -27,7 +28,7 @@
 data class FooterActionsButtonViewModel(
     val id: Int,
     val icon: Icon,
-    val iconTint: Int?,
-    @DrawableRes val background: Int,
+    @ColorInt val iconTint: Int?,
+    @AttrRes val backgroundColor: Int,
     val onClick: (Expandable) -> Unit,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index dee6fad..fbf32b3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import android.view.ContextThemeWrapper
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
@@ -49,12 +50,15 @@
 
 /** A ViewModel for the footer actions. */
 class FooterActionsViewModel(
-    @Application private val context: Context,
+    @Application appContext: Context,
     private val footerActionsInteractor: FooterActionsInteractor,
     private val falsingManager: FalsingManager,
     private val globalActionsDialogLite: GlobalActionsDialogLite,
     showPowerButton: Boolean,
 ) {
+    /** The context themed with the Quick Settings colors. */
+    private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+
     /**
      * Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
      * the UI should still participate to the layout it is included in (i.e. in the View world it
@@ -142,7 +146,7 @@
                 ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
             ),
             iconTint = null,
-            R.drawable.qs_footer_action_circle,
+            backgroundColor = R.attr.offStateColor,
             this::onSettingsButtonClicked,
         )
 
@@ -160,7 +164,7 @@
                         context,
                         com.android.internal.R.attr.textColorOnAccent,
                     ),
-                R.drawable.qs_footer_action_circle_color,
+                backgroundColor = com.android.internal.R.attr.colorAccent,
                 this::onPowerButtonClicked,
             )
         } else {
@@ -260,7 +264,7 @@
                     ),
                 ),
             iconTint = null,
-            background = R.drawable.qs_footer_action_circle,
+            backgroundColor = R.attr.offStateColor,
             onClick = this::onUserSwitcherClicked,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index a4ce6b3..534155c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -144,7 +144,7 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
     private static final TelephonyDisplayInfo DEFAULT_TELEPHONY_DISPLAY_INFO =
             new TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE);
+                    TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false);
 
     static final int MAX_WIFI_ENTRY_COUNT = 3;
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 1709043..31543df 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,
@@ -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/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/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index f668528..3516037 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -463,7 +463,11 @@
         riv.getController().setRemoteInput(input);
         riv.getController().setRemoteInputs(inputs);
         riv.getController().setEditedSuggestionInfo(editedSuggestionInfo);
-        riv.focusAnimated();
+        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/connectivity/MobileState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
index 1fb6a98..c37b01f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt
@@ -49,7 +49,7 @@
 ) : ConnectivityState() {
 
     @JvmField var telephonyDisplayInfo = TelephonyDisplayInfo(TelephonyManager.NETWORK_TYPE_UNKNOWN,
-            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE)
+            TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE, false)
     @JvmField var serviceState: ServiceState? = null
     @JvmField var signalStrength: SignalStrength? = null
 
@@ -131,7 +131,7 @@
     }
 
     fun isRoaming(): Boolean {
-        return serviceState != null && serviceState!!.roaming
+        return telephonyDisplayInfo != null && telephonyDisplayInfo.isRoaming
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 47cdde4..56eb4b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.collection.inflation;
 
+import static com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -30,6 +31,7 @@
 
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -71,6 +73,7 @@
     private NotificationListContainer mListContainer;
     private BindRowCallback mBindRowCallback;
     private NotificationClicker mNotificationClicker;
+    private FeatureFlags mFeatureFlags;
 
     @Inject
     public NotificationRowBinderImpl(
@@ -82,7 +85,8 @@
             RowContentBindStage rowContentBindStage,
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             ExpandableNotificationRowComponent.Builder expandableNotificationRowComponentBuilder,
-            IconManager iconManager) {
+            IconManager iconManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mNotifBindPipeline = notifBindPipeline;
         mRowContentBindStage = rowContentBindStage;
@@ -92,6 +96,7 @@
         mRowInflaterTaskProvider = rowInflaterTaskProvider;
         mExpandableNotificationRowComponentBuilder = expandableNotificationRowComponentBuilder;
         mIconManager = iconManager;
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -176,6 +181,8 @@
         entry.setRow(row);
         mNotifBindPipeline.manageRow(entry, row);
         mBindRowCallback.onBindRow(row);
+        row.setInlineReplyAnimationFlagEnabled(
+                mFeatureFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION));
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
new file mode 100644
index 0000000..6e5fcf4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/fsi/FsiChromeView.kt
@@ -0,0 +1,83 @@
+package com.android.systemui.statusbar.notification.fsi
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Color.DKGRAY
+import android.graphics.Outline
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewOutlineProvider
+import android.widget.Button
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.fsi.FsiDebug.Companion.log
+
+@SysUISingleton
+class FsiChromeView
+@JvmOverloads
+constructor(
+    context: Context?,
+    attrs: AttributeSet? = null,
+    defStyleAttr: Int = 0,
+    defStyleRes: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
+
+    companion object {
+        private const val classTag = "FsiChromeView"
+    }
+
+    lateinit var chromeContainer: LinearLayout
+    lateinit var appIconImageView: ImageView
+    lateinit var appNameTextView: TextView
+    lateinit var dismissButton: Button
+    lateinit var fullscreenButton: Button
+
+    private val cornerRadius: Float =
+        resources.getDimensionPixelSize(R.dimen.notification_corner_radius).toFloat()
+    private val vertPadding: Int =
+        resources.getDimensionPixelSize(R.dimen.fsi_chrome_vertical_padding)
+    private val sidePadding: Int =
+        resources.getDimensionPixelSize(R.dimen.notification_side_paddings)
+
+    init {
+        log("$classTag init")
+    }
+
+    override fun onFinishInflate() {
+        log("$classTag onFinishInflate")
+        super.onFinishInflate()
+
+        setBackgroundColor(Color.TRANSPARENT)
+        setPadding(
+            sidePadding,
+            vertPadding,
+            sidePadding,
+            vertPadding
+        ) // Make smaller than fullscreen.
+
+        chromeContainer = findViewById(R.id.fsi_chrome)
+        chromeContainer.setBackgroundColor(DKGRAY)
+
+        appIconImageView = findViewById(R.id.fsi_app_icon)
+        appNameTextView = findViewById(R.id.fsi_app_name)
+        dismissButton = findViewById(R.id.fsi_dismiss_button)
+        fullscreenButton = findViewById(R.id.fsi_fullscreen_button)
+
+        outlineProvider =
+            object : ViewOutlineProvider() {
+                override fun getOutline(view: View, outline: Outline) {
+                    outline.setRoundRect(
+                        /* left */ sidePadding,
+                        /* top */ vertPadding,
+                        /* right */ view.width - sidePadding,
+                        /* bottom */ view.height - vertPadding,
+                        cornerRadius
+                    )
+                }
+            }
+        clipToOutline = true
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 44a231d..c1173e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -284,6 +284,7 @@
     private View.OnClickListener mOnAppClickListener;
     private View.OnClickListener mOnFeedbackClickListener;
     private Path mExpandingClipPath;
+    private boolean mIsInlineReplyAnimationFlagEnabled = false;
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -3079,6 +3080,10 @@
         return 0;
     }
 
+    public void setInlineReplyAnimationFlagEnabled(boolean isEnabled) {
+        mIsInlineReplyAnimationFlagEnabled = isEnabled;
+    }
+
     @Override
     public void setActualHeight(int height, boolean notifyListeners) {
         boolean changed = height != getActualHeight();
@@ -3098,7 +3103,11 @@
         }
         int contentHeight = Math.max(getMinHeight(), height);
         for (NotificationContentView l : mLayouts) {
-            l.setContentHeight(contentHeight);
+            if (mIsInlineReplyAnimationFlagEnabled) {
+                l.setContentHeight(height);
+            } else {
+                l.setContentHeight(contentHeight);
+            }
         }
         if (mIsSummaryWithChildren) {
             mChildrenContainer.setActualHeight(height);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 277ad8e..e46bf52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -70,7 +70,7 @@
 
 /**
  * A frame layout containing the actual payload of the notification, including the contracted,
- * expanded and heads up layout. This class is responsible for clipping the content and and
+ * expanded and heads up layout. This class is responsible for clipping the content and
  * switching between the expanded, contracted and the heads up view depending on its clipped size.
  */
 public class NotificationContentView extends FrameLayout implements NotificationFadeAware {
@@ -627,6 +627,13 @@
         int hint;
         if (mHeadsUpChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_HEADSUP)) {
             hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
+            if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isAnimatingAppearance()
+                    && mHeadsUpRemoteInputController.isFocusAnimationFlagActive()) {
+                // While the RemoteInputView is animating its appearance, it should be allowed
+                // to overlap the hint, therefore no space is reserved for the hint during the
+                // appearance animation of the RemoteInputView
+                hint = 0;
+            }
         } else if (mExpandedChild != null) {
             hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
         } else if (mContractedChild != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 5db95d7..ca1e397 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -146,7 +146,8 @@
     private static final int DELAY_BEFORE_SHADE_CLOSE = 200;
     private boolean mShadeNeedsToClose = false;
 
-    private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
+    @VisibleForTesting
+    static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
     private static final float RUBBER_BAND_FACTOR_ON_PANEL_EXPAND = 0.21f;
     /**
@@ -1326,8 +1327,11 @@
      * @param listenerNeedsAnimation does the listener need to animate?
      */
     private void updateStackPosition(boolean listenerNeedsAnimation) {
+        float topOverscrollAmount = mShouldUseSplitNotificationShade
+                ? getCurrentOverScrollAmount(true /* top */) : 0f;
         final float endTopPosition = mTopPadding + mExtraTopInsetForFullShadeTransition
                 + mAmbientState.getOverExpansion()
+                + topOverscrollAmount
                 - getCurrentOverScrollAmount(false /* top */);
         float fraction = mAmbientState.getExpansionFraction();
         // If we are on quick settings, we need to quickly hide it to show the bouncer to avoid an
@@ -2613,8 +2617,10 @@
             float bottomAmount = getCurrentOverScrollAmount(false);
             if (velocityY < 0 && topAmount > 0) {
                 setOwnScrollY(mOwnScrollY - (int) topAmount);
-                mDontReportNextOverScroll = true;
-                setOverScrollAmount(0, true, false);
+                if (!mShouldUseSplitNotificationShade) {
+                    mDontReportNextOverScroll = true;
+                    setOverScrollAmount(0, true, false);
+                }
                 mMaxOverScroll = Math.abs(velocityY) / 1000f * getRubberBandFactor(true /* onTop */)
                         * mOverflingDistance + topAmount;
             } else if (velocityY > 0 && bottomAmount > 0) {
@@ -2648,6 +2654,7 @@
         float topOverScroll = getCurrentOverScrollAmount(true);
         return mScrolledToTopOnFirstDown
                 && !mExpandedInThisMotion
+                && !mShouldUseSplitNotificationShade
                 && (initialVelocity > mMinimumVelocity
                 || (topOverScroll > mMinTopOverScrollToEscape && initialVelocity > 0));
     }
@@ -2713,7 +2720,7 @@
             return RUBBER_BAND_FACTOR_AFTER_EXPAND;
         } else if (mIsExpansionChanging || mPanelTracking) {
             return RUBBER_BAND_FACTOR_ON_PANEL_EXPAND;
-        } else if (mScrolledToTopOnFirstDown) {
+        } else if (mScrolledToTopOnFirstDown && !mShouldUseSplitNotificationShade) {
             return 1.0f;
         }
         return RUBBER_BAND_FACTOR_NORMAL;
@@ -5705,7 +5712,8 @@
         }
     }
 
-    private void updateSplitNotificationShade() {
+    @VisibleForTesting
+    void updateSplitNotificationShade() {
         boolean split = LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
         if (split != mShouldUseSplitNotificationShade) {
             mShouldUseSplitNotificationShade = split;
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/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/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/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
index 1d00c33..6c37f94 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
@@ -28,6 +27,7 @@
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
 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,28 +39,42 @@
  * 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
      */
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..a8cf35a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/NetworkNameModel.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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
+
+/**
+ * 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 {
+    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
+
+    /**
+     * This name has been derived from telephony intents. see
+     * [android.telephony.TelephonyManager.ACTION_SERVICE_PROVIDERS_UPDATED]
+     */
+    data class Derived(override val name: String) : NetworkNameModel
+}
+
+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..2fd415e6 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
@@ -21,6 +21,7 @@
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
 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
 
@@ -50,4 +51,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..d3ee85f 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,6 +18,7 @@
 
 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
@@ -26,6 +27,7 @@
 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 +36,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
@@ -185,7 +188,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 +234,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()
         )
@@ -260,4 +266,8 @@
     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..a1ae8ed 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
@@ -98,6 +98,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 +109,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..7e9a9ce 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,37 @@
 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.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
@@ -61,8 +69,11 @@
 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,
@@ -95,7 +106,12 @@
                         TelephonyCallback.CarrierNetworkListener,
                         TelephonyCallback.DisplayInfoListener {
                         override fun onServiceStateChanged(serviceState: ServiceState) {
-                            state = state.copy(isEmergencyOnly = serviceState.isEmergencyOnly)
+                            state =
+                                state.copy(
+                                    isEmergencyOnly = serviceState.isEmergencyOnly,
+                                    isRoaming = serviceState.roaming,
+                                    operatorAlphaShort = serviceState.operatorAlphaShort,
+                                )
                             trySend(state)
                         }
 
@@ -132,7 +148,10 @@
                         }
 
                         override fun onDataActivity(direction: Int) {
-                            state = state.copy(dataActivityDirection = direction)
+                            state =
+                                state.copy(
+                                    dataActivityDirection = direction.toMobileDataActivityModel()
+                                )
                             trySend(state)
                         }
 
@@ -208,6 +227,24 @@
             globalMobileDataSettingChangedEvent,
         )
 
+    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
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultNetworkName)
+
     override val dataEnabled: StateFlow<Boolean> =
         telephonyPollingEvent
             .mapLatest { dataConnectionAllowed() }
@@ -223,6 +260,7 @@
     class Factory
     @Inject
     constructor(
+        private val broadcastDispatcher: BroadcastDispatcher,
         private val context: Context,
         private val telephonyManager: TelephonyManager,
         private val logger: ConnectivityPipelineLogger,
@@ -233,14 +271,19 @@
     ) {
         fun build(
             subId: Int,
+            defaultNetworkName: NetworkNameModel,
+            networkNameSeparator: String,
             defaultDataSubId: StateFlow<Int>,
             globalMobileDataSettingChangedEvent: Flow<Unit>,
         ): MobileConnectionRepository {
             return MobileConnectionRepositoryImpl(
                 context,
                 subId,
+                defaultNetworkName,
+                networkNameSeparator,
                 telephonyManager.createForSubscriptionId(subId),
                 globalSettings,
+                broadcastDispatcher,
                 defaultDataSubId,
                 globalMobileDataSettingChangedEvent,
                 mobileMappingsProxy,
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..a9b3d18 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,12 +38,14 @@
 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
@@ -88,6 +90,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
@@ -243,6 +253,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..76e6a96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -20,10 +20,13 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
+import com.android.systemui.statusbar.pipeline.mobile.data.model.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 +35,9 @@
 import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
+    /** 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 +57,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 +97,28 @@
 ) : MobileIconInteractor {
     private val connectionInfo = connectionRepository.connectionInfo
 
+    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 +135,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/binder/MobileIconBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/binder/MobileIconBinder.kt
index 67ea139..545e624 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
@@ -29,7 +31,6 @@
 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 kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.launch
 
@@ -40,9 +41,14 @@
         view: ViewGroup,
         viewModel: MobileIconViewModel,
     ) {
+        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/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 8ebd718..961283f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -22,13 +22,16 @@
 import com.android.systemui.common.shared.model.Icon
 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.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 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
 
 /**
@@ -48,6 +51,7 @@
     val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     logger: ConnectivityPipelineLogger,
+    constants: ConnectivityConstants,
 ) {
     /** Whether or not to show the error state of [SignalDrawable] */
     private val showExclamationMark: Flow<Boolean> =
@@ -87,5 +91,19 @@
             }
         }
 
+    val roaming: Flow<Boolean> = iconInteractor.isRoaming
+
+    private val activity: Flow<DataActivityModel?> =
+        if (!constants.shouldShowActivityConfig) {
+            flowOf(null)
+        } else {
+            iconInteractor.activity
+        }
+
+    val activityInVisible: Flow<Boolean> = activity.map { it?.hasActivityIn ?: false }
+    val activityOutVisible: Flow<Boolean> = activity.map { it?.hasActivityOut ?: false }
+    val activityContainerVisible: Flow<Boolean> =
+        activity.map { it != null && (it.hasActivityIn || it.hasActivityOut) }
+
     val tint: Flow<Int> = flowOf(Color.CYAN)
 }
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..0b41d31 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
@@ -20,6 +20,7 @@
 
 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
@@ -36,13 +37,15 @@
     val subscriptionIdsFlow: StateFlow<List<Int>>,
     private val interactor: MobileIconsInteractor,
     private val logger: ConnectivityPipelineLogger,
+    private val constants: ConnectivityConstants,
 ) {
     /** TODO: do we need to cache these? */
     fun viewModelForSub(subId: Int): MobileIconViewModel =
         MobileIconViewModel(
             subId,
             interactor.createMobileConnectionInteractorForSubId(subId),
-            logger
+            logger,
+            constants,
         )
 
     class Factory
@@ -50,12 +53,14 @@
     constructor(
         private val interactor: MobileIconsInteractor,
         private val logger: ConnectivityPipelineLogger,
+        private val constants: ConnectivityConstants,
     ) {
         fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel {
             return MobileIconsViewModel(
                 subscriptionIdsFlow,
                 interactor,
                 logger,
+                constants,
             )
         }
     }
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/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/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/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..07a7595 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,7 +147,7 @@
             )
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
-    private val activity: Flow<WifiActivityModel?> =
+    private val activity: Flow<DataActivityModel?> =
         if (!wifiConstants.shouldShowActivityConfig) {
             flowOf(null)
         } else {
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 dd400b3..d8a8c5d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,8 +18,8 @@
 
 import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
@@ -57,6 +57,7 @@
 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;
@@ -67,6 +68,11 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.animation.Animator;
+import androidx.core.animation.AnimatorListenerAdapter;
+import androidx.core.animation.AnimatorSet;
+import androidx.core.animation.ObjectAnimator;
+import androidx.core.animation.ValueAnimator;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.graphics.ColorUtils;
@@ -74,6 +80,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.animation.InterpolatorsAndroidX;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
@@ -97,6 +104,12 @@
     // A marker object that let's us easily find views of this class.
     public static final Object VIEW_TAG = new Object();
 
+    private static final long FOCUS_ANIMATION_TOTAL_DURATION = ANIMATION_DURATION_STANDARD;
+    private static final long FOCUS_ANIMATION_CROSSFADE_DURATION = 50;
+    private static final long FOCUS_ANIMATION_FADE_IN_DELAY = 33;
+    private static final long FOCUS_ANIMATION_FADE_IN_DURATION = 83;
+    private static final float FOCUS_ANIMATION_MIN_SCALE = 0.5f;
+
     public final Object mToken = new Object();
 
     private final SendButtonTextWatcher mTextWatcher;
@@ -108,6 +121,7 @@
 
     private RemoteEditText mEditText;
     private ImageButton mSendButton;
+    private LinearLayout mContentView;
     private GradientDrawable mContentBackground;
     private ProgressBar mProgressBar;
     private ImageView mDelete;
@@ -115,7 +129,10 @@
     private boolean mColorized;
     private int mTint;
     private boolean mResetting;
-    @Nullable private RevealParams mRevealParams;
+    @Nullable
+    private RevealParams mRevealParams;
+    private Rect mContentBackgroundBounds;
+    private boolean mIsFocusAnimationFlagActive;
 
     // TODO(b/193539698): move these to a Controller
     private RemoteInputController mController;
@@ -125,6 +142,10 @@
     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;
@@ -255,8 +276,8 @@
         mDeleteBg.setImageTintBlendMode(BlendMode.SRC_IN);
         mDelete.setImageTintBlendMode(BlendMode.SRC_IN);
         mDelete.setOnClickListener(v -> setAttachment(null));
-        LinearLayout contentView = findViewById(R.id.remote_input_content);
-        contentView.setBackground(mContentBackground);
+        mContentView = findViewById(R.id.remote_input_content);
+        mContentView.setBackground(mContentBackground);
         mEditText = findViewById(R.id.remote_input_text);
         mEditText.setInnerFocusable(false);
         // TextView initializes the spell checked when the view is attached to a window.
@@ -398,20 +419,74 @@
         return true;
     }
 
-    private void onDefocus(boolean animate, boolean logClose) {
+    public boolean isAnimatingAppearance() {
+        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);
         mEntry.remoteInputText = mEditText.getText();
 
         // During removal, we get reattached and lose focus. Not hiding in that
         // case to prevent flicker.
         if (!mRemoved) {
-            if (animate && mRevealParams != null && mRevealParams.radius > 0) {
-                Animator reveal = mRevealParams.createCircularHideAnimator(this);
-                reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
-                reveal.addListener(new AnimatorListenerAdapter() {
+            if (animate && mIsFocusAnimationFlagActive) {
+                Animator animator = getDefocusAnimator();
+
+                // 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);
+                }
+
+                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);
+                        }
+                        setVisibility(GONE);
+                        if (mWrapper != null) {
+                            mWrapper.setRemoteInputVisible(false);
+                        }
+                    }
+                });
+                animator.start();
+
+            } else if (animate && mRevealParams != null && mRevealParams.radius > 0) {
+                android.animation.Animator reveal = mRevealParams.createCircularHideAnimator(this);
+                reveal.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+                reveal.setDuration(StackStateAnimator.ANIMATION_DURATION_CLOSE_REMOTE_INPUT);
+                reveal.addListener(new android.animation.AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(android.animation.Animator animation) {
                         setVisibility(GONE);
                         if (mWrapper != null) {
                             mWrapper.setRemoteInputVisible(false);
@@ -533,12 +608,37 @@
         mEditText.setText(editTextContent);
     }
 
-    public void focusAnimated() {
-        if (getVisibility() != VISIBLE && mRevealParams != null) {
-            Animator animator = mRevealParams.createCircularRevealAnimator(this);
+    /**
+     * Sets whether the feature flag for the updated inline reply animation is active or not.
+     * @param active
+     */
+    public void setIsFocusAnimationFlagActive(boolean active) {
+        mIsFocusAnimationFlagActive = active;
+    }
+
+    /**
+     * Focuses the RemoteInputView and animates its appearance
+     *
+     * @param crossFadeView view that will be crossfaded during the appearance animation
+     */
+    public void focusAnimated(View crossFadeView) {
+        if (!mIsFocusAnimationFlagActive && getVisibility() != VISIBLE
+                && mRevealParams != null) {
+            android.animation.Animator animator = mRevealParams.createCircularRevealAnimator(this);
             animator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
             animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
             animator.start();
+        } else if (mIsFocusAnimationFlagActive && getVisibility() != VISIBLE) {
+            mIsAnimatingAppearance = true;
+            setAlpha(0f);
+            Animator focusAnimator = getFocusAnimator(crossFadeView);
+            focusAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation, boolean isReverse) {
+                    mIsAnimatingAppearance = false;
+                }
+            });
+            focusAnimator.start();
         }
         focus();
     }
@@ -737,6 +837,81 @@
         mOnSendListeners.remove(listener);
     }
 
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (mIsFocusAnimationFlagActive) setPivotY(getMeasuredHeight());
+        if (mContentBackgroundBounds != null) {
+            mContentBackground.setBounds(mContentBackgroundBounds);
+        }
+    }
+
+    private Animator getFocusAnimator(View crossFadeView) {
+        final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 0f, 1f);
+        alphaAnimator.setStartDelay(FOCUS_ANIMATION_FADE_IN_DELAY);
+        alphaAnimator.setDuration(FOCUS_ANIMATION_FADE_IN_DURATION);
+        alphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+
+        ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
+        scaleAnimator.addUpdateListener(valueAnimator -> {
+            setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
+        });
+        scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
+        scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
+
+        final Animator crossFadeViewAlphaAnimator =
+                ObjectAnimator.ofFloat(crossFadeView, View.ALPHA, 1f, 0f);
+        crossFadeViewAlphaAnimator.setDuration(FOCUS_ANIMATION_CROSSFADE_DURATION);
+        crossFadeViewAlphaAnimator.setInterpolator(InterpolatorsAndroidX.LINEAR);
+        alphaAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation, boolean isReverse) {
+                crossFadeView.setAlpha(1f);
+            }
+        });
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(alphaAnimator, scaleAnimator, crossFadeViewAlphaAnimator);
+        return animatorSet;
+    }
+
+    private Animator getDefocusAnimator() {
+        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());
+        });
+        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);
+            }
+        });
+
+        final AnimatorSet animatorSet = new AnimatorSet();
+        animatorSet.playTogether(alphaAnimator, scaleAnimator);
+        return animatorSet;
+    }
+
+    /**
+     * Sets affected view properties for a vertical scale animation
+     *
+     * @param scaleY desired vertical view scale
+     */
+    private void setFocusAnimationScaleY(float scaleY) {
+        int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
+        mContentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
+                mContentView.getHeight() - verticalBoundOffset);
+        mContentBackground.setBounds(mContentBackgroundBounds);
+        mContentView.setBackground(mContentBackground);
+        setTranslationY(verticalBoundOffset);
+    }
+
     /** Handler for button click on send action in IME. */
     private class EditorActionHandler implements TextView.OnEditorActionListener {
 
@@ -991,11 +1166,11 @@
             this.radius = radius;
         }
 
-        Animator createCircularHideAnimator(View view) {
+        android.animation.Animator createCircularHideAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, radius, 0);
         }
 
-        Animator createCircularRevealAnimator(View view) {
+        android.animation.Animator createCircularRevealAnimator(View view) {
             return ViewAnimationUtils.createCircularReveal(view, centerX, centerY, 0, radius);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
index f845101..22b4c9d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt
@@ -30,6 +30,8 @@
 import android.view.View
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.NOTIFICATION_INLINE_REPLY_ANIMATION
 import com.android.systemui.statusbar.NotificationRemoteInputManager
 import com.android.systemui.statusbar.RemoteInputController
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
@@ -61,6 +63,8 @@
 
     var revealParams: RevealParams?
 
+    val isFocusAnimationFlagActive: Boolean
+
     /**
      * Sets the smart reply that should be inserted in the remote input, or `null` if the user is
      * not editing a smart reply.
@@ -117,7 +121,8 @@
     private val remoteInputQuickSettingsDisabler: RemoteInputQuickSettingsDisabler,
     private val remoteInputController: RemoteInputController,
     private val shortcutManager: ShortcutManager,
-    private val uiEventLogger: UiEventLogger
+    private val uiEventLogger: UiEventLogger,
+    private val mFlags: FeatureFlags
 ) : RemoteInputViewController {
 
     private val onSendListeners = ArraySet<OnSendRemoteInputListener>()
@@ -149,6 +154,9 @@
 
     override val isActive: Boolean get() = view.isActive
 
+    override val isFocusAnimationFlagActive: Boolean
+        get() = mFlags.isEnabled(NOTIFICATION_INLINE_REPLY_ANIMATION)
+
     override fun bind() {
         if (isBound) return
         isBound = true
@@ -159,6 +167,7 @@
             view.setSupportedMimeTypes(it.allowedDataTypes)
         }
         view.setRevealParameters(revealParams)
+        view.setIsFocusAnimationFlagActive(isFocusAnimationFlagActive)
 
         view.addOnEditTextFocusChangedListener(onFocusChangeListener)
         view.addOnSendRemoteInputListener(onSendRemoteInputListener)
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
new file mode 100644
index 0000000..154c6e2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusFirstUsageListener.kt
@@ -0,0 +1,136 @@
+/*
+ * 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.stylus
+
+import android.content.Context
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.util.Log
+import android.view.InputDevice
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * A listener that detects when a stylus has first been used, by detecting 1) the presence of an
+ * internal SOURCE_STYLUS with a battery, or 2) any added SOURCE_STYLUS device with a bluetooth
+ * address.
+ */
+@SysUISingleton
+class StylusFirstUsageListener
+@Inject
+constructor(
+    private val context: Context,
+    private val inputManager: InputManager,
+    private val stylusManager: StylusManager,
+    private val featureFlags: FeatureFlags,
+    @Background private val executor: Executor,
+    @Background private val handler: Handler,
+) : CoreStartable, StylusManager.StylusCallback, InputManager.InputDeviceBatteryListener {
+
+    // Set must be only accessed from the background handler, which is the same handler that
+    // runs the StylusManager callbacks.
+    private val internalStylusDeviceIds: MutableSet<Int> = mutableSetOf()
+    @VisibleForTesting var hasStarted = false
+
+    override fun start() {
+        if (true) return // TODO(b/261826950): remove on main
+        if (hasStarted) return
+        if (!featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)) return
+        if (inputManager.isStylusEverUsed(context)) return
+        if (!hostDeviceSupportsStylusInput()) return
+
+        hasStarted = true
+        inputManager.inputDeviceIds.forEach(this::onStylusAdded)
+        stylusManager.registerCallback(this)
+        stylusManager.startListener()
+    }
+
+    override fun onStylusAdded(deviceId: Int) {
+        if (!hasStarted) return
+
+        val device = inputManager.getInputDevice(deviceId) ?: return
+        if (device.isExternal || !device.supportsSource(InputDevice.SOURCE_STYLUS)) return
+
+        try {
+            inputManager.addInputDeviceBatteryListener(deviceId, executor, this)
+            internalStylusDeviceIds += deviceId
+        } catch (e: SecurityException) {
+            Log.e(TAG, "$e: Failed to register battery listener for $deviceId ${device.name}.")
+        }
+    }
+
+    override fun onStylusRemoved(deviceId: Int) {
+        if (!hasStarted) return
+
+        if (!internalStylusDeviceIds.contains(deviceId)) return
+        try {
+            inputManager.removeInputDeviceBatteryListener(deviceId, this)
+            internalStylusDeviceIds.remove(deviceId)
+        } catch (e: SecurityException) {
+            Log.e(TAG, "$e: Failed to remove registered battery listener for $deviceId.")
+        }
+    }
+
+    override fun onStylusBluetoothConnected(deviceId: Int, btAddress: String) {
+        if (!hasStarted) return
+
+        onRemoteDeviceFound()
+    }
+
+    override fun onBatteryStateChanged(
+        deviceId: Int,
+        eventTimeMillis: Long,
+        batteryState: BatteryState
+    ) {
+        if (!hasStarted) return
+
+        if (batteryState.isPresent) {
+            onRemoteDeviceFound()
+        }
+    }
+
+    private fun onRemoteDeviceFound() {
+        inputManager.setStylusEverUsed(context, true)
+        cleanupListeners()
+    }
+
+    private fun cleanupListeners() {
+        stylusManager.unregisterCallback(this)
+        handler.post {
+            internalStylusDeviceIds.forEach {
+                inputManager.removeInputDeviceBatteryListener(it, this)
+            }
+        }
+    }
+
+    private fun hostDeviceSupportsStylusInput(): Boolean {
+        return inputManager.inputDeviceIds
+            .asSequence()
+            .mapNotNull { inputManager.getInputDevice(it) }
+            .any { it.supportsSource(InputDevice.SOURCE_STYLUS) && !it.isExternal }
+    }
+
+    companion object {
+        private val TAG = StylusFirstUsageListener::class.simpleName.orEmpty()
+    }
+}
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/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 fa3c73a..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;
@@ -288,6 +292,8 @@
 
     private boolean mSeparateNotification;
 
+    private int mWindowGravity;
+
     @VisibleForTesting
     int mVolumeRingerIconDrawableId;
     @VisibleForTesting
@@ -514,7 +520,12 @@
         lp.format = PixelFormat.TRANSLUCENT;
         lp.setTitle(VolumeDialogImpl.class.getSimpleName());
         lp.windowAnimations = -1;
-        lp.gravity = mContext.getResources().getInteger(R.integer.volume_dialog_gravity);
+
+        mWindowGravity = Gravity.getAbsoluteGravity(
+                mContext.getResources().getInteger(R.integer.volume_dialog_gravity),
+                mContext.getResources().getConfiguration().getLayoutDirection());
+        lp.gravity = mWindowGravity;
+
         mWindow.setAttributes(lp);
         mWindow.setLayout(WRAP_CONTENT, WRAP_CONTENT);
 
@@ -525,7 +536,8 @@
         mDialog.setOnShowListener(dialog -> {
             mDialogView.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
             if (!shouldSlideInVolumeTray()) {
-                mDialogView.setTranslationX(mDialogView.getWidth() / 2.0f);
+                mDialogView.setTranslationX(
+                        (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
             }
             mDialogView.setAlpha(0);
             mDialogView.animate()
@@ -696,6 +708,10 @@
         initODICaptionsH();
     }
 
+    private boolean isWindowGravityLeft() {
+        return (mWindowGravity & Gravity.LEFT) == Gravity.LEFT;
+    }
+
     private void initDimens() {
         mDialogWidth = mContext.getResources().getDimensionPixelSize(
                 R.dimen.volume_dialog_panel_width);
@@ -1464,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");
 
@@ -1495,7 +1528,10 @@
 
                     hideRingerDrawer();
                 }, 50));
-        if (!shouldSlideInVolumeTray()) animator.translationX(mDialogView.getWidth() / 2.0f);
+        if (!shouldSlideInVolumeTray()) {
+            animator.translationX(
+                    (isWindowGravityLeft() ? -1 : 1) * mDialogView.getWidth() / 2.0f);
+        }
         animator.setListener(getJankListener(getDialogView(), TYPE_DISMISS,
                 mDialogHideAnimationDurationMs)).start();
         checkODICaptionsTooltip(true);
@@ -2045,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 "";
@@ -2224,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();
@@ -2250,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());
@@ -2266,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/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/DreamCallbackControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
new file mode 100644
index 0000000..003efbf
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamCallbackControllerTest.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.dreams
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+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 DreamCallbackControllerTest : SysuiTestCase() {
+
+    @Mock private lateinit var callback: DreamCallbackController.DreamCallback
+
+    private lateinit var underTest: DreamCallbackController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = DreamCallbackController()
+    }
+
+    @Test
+    fun testOnWakeUpInvokesCallback() {
+        underTest.addCallback(callback)
+        underTest.onWakeUp()
+        verify(callback).onWakeUp()
+
+        // 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()
+    }
+}
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/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..52663ce 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
+    DreamCallbackController mDreamCallbackController;
+
     @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,
+                mDreamCallbackController);
     }
 
     @Test
@@ -394,6 +398,7 @@
         mService.onWakeUp(callback);
         mMainExecutor.runAllReady();
         verify(mDreamOverlayContainerViewController).wakeUp(callback, mMainExecutor);
+        verify(mDreamCallbackController).onWakeUp();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cf9c91a..5e4a43f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -170,6 +170,7 @@
                     FakeFeatureFlags().apply {
                         set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
                         set(Flags.LOCKSCREEN_CUSTOM_CLOCKS, true)
+                        set(Flags.REVAMPED_WALLPAPER_UI, true)
                     },
                 repository = { quickAffordanceRepository },
                 launchAnimator = launchAnimator,
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..563d44d3 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,8 @@
 import com.android.systemui.doze.DozeMachine
 import com.android.systemui.doze.DozeTransitionCallback
 import com.android.systemui.doze.DozeTransitionListener
+import com.android.systemui.dreams.DreamCallbackController
+import com.android.systemui.dreams.DreamCallbackController.DreamCallback
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
@@ -66,6 +68,7 @@
     @Mock private lateinit var dozeTransitionListener: DozeTransitionListener
     @Mock private lateinit var authController: AuthController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var dreamCallbackController: DreamCallbackController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -83,6 +86,7 @@
                 keyguardUpdateMonitor,
                 dozeTransitionListener,
                 authController,
+                dreamCallbackController,
             )
     }
 
@@ -318,7 +322,7 @@
         }
 
     @Test
-    fun isDreaming() =
+    fun isDreamingFromKeyguardUpdateMonitor() =
         runTest(UnconfinedTestDispatcher()) {
             whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(false)
             var latest: Boolean? = null
@@ -339,6 +343,26 @@
         }
 
     @Test
+    fun isDreamingFromDreamCallbackController() =
+        runTest(UnconfinedTestDispatcher()) {
+            whenever(keyguardUpdateMonitor.isDreaming()).thenReturn(true)
+            var latest: Boolean? = null
+            val job = underTest.isDreaming.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            val listener =
+                withArgCaptor<DreamCallbackController.DreamCallback> {
+                    verify(dreamCallbackController).addCallback(capture())
+                }
+
+            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/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
new file mode 100644
index 0000000..c54e456
--- /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.DreamingTransitionInteractor.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/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 01411c9..0b9fbd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -84,7 +84,7 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
                 )
             )
-        assertThat(settings.background).isEqualTo(R.drawable.qs_footer_action_circle)
+        assertThat(settings.backgroundColor).isEqualTo(R.attr.offStateColor)
         assertThat(settings.iconTint).isNull()
     }
 
@@ -105,7 +105,7 @@
                     ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
                 )
             )
-        assertThat(power.background).isEqualTo(R.drawable.qs_footer_action_circle_color)
+        assertThat(power.backgroundColor).isEqualTo(com.android.internal.R.attr.colorAccent)
         assertThat(power.iconTint)
             .isEqualTo(
                 Utils.getColorAttrDefaultColor(
@@ -170,7 +170,7 @@
         assertThat(userSwitcher).isNotNull()
         assertThat(userSwitcher!!.icon)
             .isEqualTo(Icon.Loaded(picture, ContentDescription.Loaded("Signed in as foo")))
-        assertThat(userSwitcher.background).isEqualTo(R.drawable.qs_footer_action_circle)
+        assertThat(userSwitcher.backgroundColor).isEqualTo(R.attr.offStateColor)
 
         // Change the current user name.
         userSwitcherControllerWrapper.currentUserName = "bar"
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/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/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 07ea630..7622549 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -20,6 +20,7 @@
 
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.RUBBER_BAND_FACTOR_NORMAL;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -47,6 +48,7 @@
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.testing.TestableResources;
 import android.util.MathUtils;
 import android.view.MotionEvent;
 import android.view.View;
@@ -99,6 +101,7 @@
     private NotificationStackScrollLayout mStackScroller;  // Normally test this
     private NotificationStackScrollLayout mStackScrollerInternal;  // See explanation below
     private AmbientState mAmbientState;
+    private TestableResources mTestableResources;
 
     @Rule public MockitoRule mockito = MockitoJUnit.rule();
     @Mock private CentralSurfaces mCentralSurfaces;
@@ -123,6 +126,7 @@
     @UiThreadTest
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
+        mTestableResources = mContext.getOrCreateTestableResources();
 
         // Interact with real instance of AmbientState.
         mAmbientState = spy(new AmbientState(
@@ -394,7 +398,7 @@
     @Test
     @UiThreadTest
     public void testSetExpandedHeight_withSplitShade_doesntInterpolateStackHeight() {
-        mContext.getOrCreateTestableResources()
+        mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
         final int[] expectedStackHeight = {0};
 
@@ -405,12 +409,12 @@
                     .isEqualTo(expectedStackHeight[0]);
         });
 
-        mContext.getOrCreateTestableResources()
+        mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false);
         expectedStackHeight[0] = 0;
         mStackScroller.setExpandedHeight(100f);
 
-        mContext.getOrCreateTestableResources()
+        mTestableResources
                 .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
         expectedStackHeight[0] = 100;
         mStackScroller.setExpandedHeight(100f);
@@ -781,6 +785,39 @@
         assertEquals(mAmbientState.getScrollY(), 0);
     }
 
+    @Test
+    public void testSplitShade_hasTopOverscroll() {
+        mTestableResources
+                .addOverride(R.bool.config_use_split_notification_shade, /* value= */ true);
+        mStackScroller.updateSplitNotificationShade();
+        mAmbientState.setExpansionFraction(1f);
+
+        int topOverscrollPixels = 100;
+        mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false);
+
+        float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL;
+        assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true));
+        assertEquals(expectedTopOverscrollAmount, mAmbientState.getStackY());
+    }
+
+    @Test
+    public void testNormalShade_hasNoTopOverscroll() {
+        mTestableResources
+                .addOverride(R.bool.config_use_split_notification_shade, /* value= */ false);
+        mStackScroller.updateSplitNotificationShade();
+        mAmbientState.setExpansionFraction(1f);
+
+        int topOverscrollPixels = 100;
+        mStackScroller.setOverScrolledPixels(topOverscrollPixels, true, false);
+
+        float expectedTopOverscrollAmount = topOverscrollPixels * RUBBER_BAND_FACTOR_NORMAL;
+        assertEquals(expectedTopOverscrollAmount, mStackScroller.getCurrentOverScrollAmount(true));
+        // When not in split shade mode, then the overscroll effect is handled in
+        // NotificationPanelViewController and not in NotificationStackScrollLayout. Therefore
+        // mAmbientState must have stackY set to 0
+        assertEquals(0f, mAmbientState.getStackY());
+    }
+
     private void setBarStateForTest(int state) {
         // Can't inject this through the listener or we end up on the actual implementation
         // rather than the mock because the spy just coppied the anonymous inner /shruggie.
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..59eec53 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
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 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
@@ -30,6 +31,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/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index e943de2..3d5316d 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,13 +18,16 @@
 
 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.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.google.common.truth.Truth.assertThat
@@ -95,6 +98,8 @@
                     inflateStrength = testCase.inflateStrength,
                     activity = testCase.activity,
                     carrierNetworkChange = testCase.carrierNetworkChange,
+                    roaming = testCase.roaming,
+                    name = "demo name",
                 )
 
             fakeNetworkEventFlow.value = networkModel
@@ -113,9 +118,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 +146,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 +156,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 +170,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 +180,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 +209,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 +245,9 @@
                     carrierIds.first(),
                     inflateStrength.first(),
                     activity.first(),
-                    carrierNetworkChange.first()
+                    carrierNetworkChange.first(),
+                    roaming.first(),
+                    names.first(),
                 )
 
             val tail =
@@ -237,6 +258,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..34f30eb 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,6 +17,7 @@
 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
@@ -24,9 +25,11 @@
 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.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.google.common.truth.Truth.assertThat
@@ -289,9 +292,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 +319,7 @@
     inflateStrength: Boolean? = false,
     activity: Int? = null,
     carrierNetworkChange: Boolean = false,
+    roaming: Boolean = false,
 ): FakeNetworkEventModel =
     FakeNetworkEventModel.Mobile(
         level = level,
@@ -322,4 +329,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..7fa8065 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,44 @@
 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.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
@@ -88,8 +106,11 @@
             MobileConnectionRepositoryImpl(
                 context,
                 SUB_1_ID,
+                DEFAULT_NAME,
+                SEP,
                 telephonyManager,
                 globalSettings,
+                fakeBroadcastDispatcher,
                 connectionsRepo.defaultDataSubId,
                 connectionsRepo.globalMobileDataSettingChangedEvent,
                 mobileMappings,
@@ -247,10 +268,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 +424,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 +610,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..3cc1e8b 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
@@ -91,6 +91,7 @@
 
         connectionFactory =
             MobileConnectionRepositoryImpl.Factory(
+                fakeBroadcastDispatcher,
                 context = context,
                 telephonyManager = telephonyManager,
                 bgDispatcher = IMMEDIATE,
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..c3519b7 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,31 @@
 import android.telephony.CellSignalStrength
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.TelephonyIcons
+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 {
     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/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index 2281e89b..4dca780 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
@@ -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/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index f2533a9..415ce75 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
@@ -23,7 +23,10 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 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.flow.launchIn
@@ -40,6 +43,7 @@
     private lateinit var underTest: MobileIconViewModel
     private val interactor = FakeMobileIconInteractor()
     @Mock private lateinit var logger: ConnectivityPipelineLogger
+    @Mock private lateinit var constants: ConnectivityConstants
 
     @Before
     fun setUp() {
@@ -53,7 +57,7 @@
             setNumberOfLevels(4)
             isDataConnected.value = true
         }
-        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
+        underTest = MobileIconViewModel(SUB_1_ID, interactor, logger, constants)
     }
 
     @Test
@@ -234,6 +238,108 @@
             job.cancel()
         }
 
+    @Test
+    fun roaming() =
+        runBlocking(IMMEDIATE) {
+            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`() =
+        runBlocking(IMMEDIATE) {
+            // 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)
+
+            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`() =
+        runBlocking(IMMEDIATE) {
+            // 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)
+
+            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()
+        }
+
     /** Convenience constructor for these tests */
     private fun defaultSignal(
         level: Int = 1,
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/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..b47f177 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
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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()
 
@@ -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 915e999..2c47204 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
@@ -16,10 +16,14 @@
 
 import static android.view.ContentInfo.SOURCE_CLIPBOARD;
 
+import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -57,6 +61,7 @@
 import android.window.WindowOnBackInvokedDispatcher;
 
 import androidx.annotation.NonNull;
+import androidx.core.animation.AnimatorTestRule;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.UiEventLogger;
@@ -64,15 +69,19 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 import com.android.systemui.statusbar.phone.LightBarController;
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -99,6 +108,9 @@
     private BlockingQueueIntentReceiver mReceiver;
     private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
 
+    @ClassRule
+    public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule();
+
     @Before
     public void setUp() throws Exception {
         allowTestableLooperAsMainThread();
@@ -294,6 +306,9 @@
         /* invoke the captured callback */
         onBackInvokedCallbackCaptor.getValue().onBackInvoked();
 
+        /* wait for RemoteInputView disappear animation to finish */
+        mAnimatorTestRule.advanceTimeBy(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+
         /* verify that the RemoteInputView goes away */
         assertEquals(view.getVisibility(), View.GONE);
     }
@@ -363,19 +378,73 @@
                 mUiEventLoggerFake.eventId(1));
     }
 
+    @Test
+    public void testFocusAnimation() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        bindController(view, row.getEntry());
+        view.setVisibility(View.GONE);
+
+        View crossFadeView = new View(mContext);
+
+        // Start focus animation
+        view.focusAnimated(crossFadeView);
+
+        assertTrue(view.isAnimatingAppearance());
+
+        // fast forward to end of animation
+        mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+        // assert that crossFadeView's alpha is reset to 1f after the animation (hidden behind
+        // RemoteInputView)
+        assertEquals(1f, crossFadeView.getAlpha());
+        assertFalse(view.isAnimatingAppearance());
+        assertEquals(View.VISIBLE, view.getVisibility());
+        assertEquals(1f, view.getAlpha());
+    }
+
+    @Test
+    public void testDefocusAnimation() throws Exception {
+        NotificationTestHelper helper = new NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this));
+        ExpandableNotificationRow row = helper.createRow();
+        RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
+        bindController(view, row.getEntry());
+
+        // Start defocus animation
+        view.onDefocus(true, false);
+        assertEquals(View.VISIBLE, view.getVisibility());
+
+        // fast forward to end of animation
+        mAnimatorTestRule.advanceTimeBy(ANIMATION_DURATION_STANDARD);
+
+        // assert that RemoteInputView is no longer visible
+        assertEquals(View.GONE, view.getVisibility());
+    }
+
     // NOTE: because we're refactoring the RemoteInputView and moving logic into the
-    //  RemoteInputViewController, it's easiest to just test the system of the two classes together.
+    // RemoteInputViewController, it's easiest to just test the system of the two classes together.
     @NonNull
     private RemoteInputViewController bindController(
             RemoteInputView view,
             NotificationEntry entry) {
+        FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
+        fakeFeatureFlags.set(Flags.NOTIFICATION_INLINE_REPLY_ANIMATION, true);
         RemoteInputViewControllerImpl viewController = new RemoteInputViewControllerImpl(
                 view,
                 entry,
                 mRemoteInputQuickSettingsDisabler,
                 mController,
                 mShortcutManager,
-                mUiEventLoggerFake);
+                mUiEventLoggerFake,
+                fakeFeatureFlags
+                );
         viewController.bind();
         return viewController;
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
new file mode 100644
index 0000000..8dd088f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusFirstUsageListenerTest.kt
@@ -0,0 +1,289 @@
+/*
+ * 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.stylus
+
+import android.content.Context
+import android.hardware.BatteryState
+import android.hardware.input.InputManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.view.InputDevice
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@Ignore("TODO(b/20579491): unignore on main")
+class StylusFirstUsageListenerTest : SysuiTestCase() {
+    @Mock lateinit var context: Context
+    @Mock lateinit var inputManager: InputManager
+    @Mock lateinit var stylusManager: StylusManager
+    @Mock lateinit var featureFlags: FeatureFlags
+    @Mock lateinit var internalStylusDevice: InputDevice
+    @Mock lateinit var otherDevice: InputDevice
+    @Mock lateinit var externalStylusDevice: InputDevice
+    @Mock lateinit var batteryState: BatteryState
+    @Mock lateinit var handler: Handler
+
+    private lateinit var stylusListener: StylusFirstUsageListener
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)
+        whenever(inputManager.isStylusEverUsed(context)).thenReturn(false)
+
+        stylusListener =
+            StylusFirstUsageListener(
+                context,
+                inputManager,
+                stylusManager,
+                featureFlags,
+                EXECUTOR,
+                handler
+            )
+        stylusListener.hasStarted = false
+
+        whenever(handler.post(any())).thenAnswer {
+            (it.arguments[0] as Runnable).run()
+            true
+        }
+
+        whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
+        whenever(internalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+        whenever(internalStylusDevice.isExternal).thenReturn(false)
+        whenever(externalStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
+        whenever(externalStylusDevice.isExternal).thenReturn(true)
+
+        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf())
+        whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
+        whenever(inputManager.getInputDevice(INTERNAL_STYLUS_DEVICE_ID))
+            .thenReturn(internalStylusDevice)
+        whenever(inputManager.getInputDevice(EXTERNAL_STYLUS_DEVICE_ID))
+            .thenReturn(externalStylusDevice)
+    }
+
+    @Test
+    fun start_flagDisabled_doesNotRegister() {
+        whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(false)
+
+        stylusListener.start()
+
+        verify(stylusManager, never()).registerCallback(any())
+        verify(inputManager, never()).setStylusEverUsed(context, true)
+    }
+
+    @Test
+    fun start_toggleHasStarted() {
+        stylusListener.start()
+
+        assert(stylusListener.hasStarted)
+    }
+
+    @Test
+    fun start_hasStarted_doesNotRegister() {
+        stylusListener.hasStarted = true
+
+        stylusListener.start()
+
+        verify(stylusManager, never()).registerCallback(any())
+    }
+
+    @Test
+    fun start_hostDeviceDoesNotSupportStylus_doesNotRegister() {
+        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(OTHER_DEVICE_ID))
+
+        stylusListener.start()
+
+        verify(stylusManager, never()).registerCallback(any())
+        verify(inputManager, never()).setStylusEverUsed(context, true)
+    }
+
+    @Test
+    fun start_stylusEverUsed_doesNotRegister() {
+        whenever(inputManager.inputDeviceIds)
+            .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
+        whenever(inputManager.isStylusEverUsed(context)).thenReturn(true)
+
+        stylusListener.start()
+
+        verify(stylusManager, never()).registerCallback(any())
+        verify(inputManager, never()).setStylusEverUsed(context, true)
+    }
+
+    @Test
+    fun start_hostDeviceSupportsStylus_registersListener() {
+        whenever(inputManager.inputDeviceIds)
+            .thenReturn(intArrayOf(OTHER_DEVICE_ID, INTERNAL_STYLUS_DEVICE_ID))
+
+        stylusListener.start()
+
+        verify(stylusManager).registerCallback(any())
+        verify(inputManager, never()).setStylusEverUsed(context, true)
+    }
+
+    @Test
+    fun onStylusAdded_hasNotStarted_doesNotRegisterListener() {
+        stylusListener.hasStarted = false
+
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        verifyZeroInteractions(inputManager)
+    }
+
+    @Test
+    fun onStylusAdded_internalStylus_registersListener() {
+        stylusListener.hasStarted = true
+
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        verify(inputManager, times(1))
+            .addInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, EXECUTOR, stylusListener)
+    }
+
+    @Test
+    fun onStylusAdded_externalStylus_doesNotRegisterListener() {
+        stylusListener.hasStarted = true
+
+        stylusListener.onStylusAdded(EXTERNAL_STYLUS_DEVICE_ID)
+
+        verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
+    }
+
+    @Test
+    fun onStylusAdded_otherDevice_doesNotRegisterListener() {
+        stylusListener.onStylusAdded(OTHER_DEVICE_ID)
+
+        verify(inputManager, never()).addInputDeviceBatteryListener(any(), any(), any())
+    }
+
+    @Test
+    fun onStylusRemoved_registeredDevice_unregistersListener() {
+        stylusListener.hasStarted = true
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+        verify(inputManager, times(1))
+            .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+    }
+
+    @Test
+    fun onStylusRemoved_hasNotStarted_doesNotUnregisterListener() {
+        stylusListener.hasStarted = false
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+        verifyZeroInteractions(inputManager)
+    }
+
+    @Test
+    fun onStylusRemoved_unregisteredDevice_doesNotUnregisterListener() {
+        stylusListener.hasStarted = true
+
+        stylusListener.onStylusRemoved(INTERNAL_STYLUS_DEVICE_ID)
+
+        verifyNoMoreInteractions(inputManager)
+    }
+
+    @Test
+    fun onStylusBluetoothConnected_updateStylusFlagAndUnregisters() {
+        stylusListener.hasStarted = true
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
+
+        verify(inputManager).setStylusEverUsed(context, true)
+        verify(inputManager, times(1))
+            .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+        verify(stylusManager).unregisterCallback(stylusListener)
+    }
+
+    @Test
+    fun onStylusBluetoothConnected_hasNotStarted_doesNoting() {
+        stylusListener.hasStarted = false
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+
+        stylusListener.onStylusBluetoothConnected(EXTERNAL_STYLUS_DEVICE_ID, "ANY")
+
+        verifyZeroInteractions(inputManager)
+        verifyZeroInteractions(stylusManager)
+    }
+
+    @Test
+    fun onBatteryStateChanged_batteryPresent_updateStylusFlagAndUnregisters() {
+        stylusListener.hasStarted = true
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+        whenever(batteryState.isPresent).thenReturn(true)
+
+        stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+        verify(inputManager).setStylusEverUsed(context, true)
+        verify(inputManager, times(1))
+            .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+        verify(stylusManager).unregisterCallback(stylusListener)
+    }
+
+    @Test
+    fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateFlagOrUnregister() {
+        stylusListener.hasStarted = true
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+        whenever(batteryState.isPresent).thenReturn(false)
+
+        stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+        verifyZeroInteractions(stylusManager)
+        verify(inputManager, never())
+            .removeInputDeviceBatteryListener(INTERNAL_STYLUS_DEVICE_ID, stylusListener)
+    }
+
+    @Test
+    fun onBatteryStateChanged_hasNotStarted_doesNothing() {
+        stylusListener.hasStarted = false
+        stylusListener.onStylusAdded(INTERNAL_STYLUS_DEVICE_ID)
+        whenever(batteryState.isPresent).thenReturn(false)
+
+        stylusListener.onBatteryStateChanged(0, 1, batteryState)
+
+        verifyZeroInteractions(inputManager)
+        verifyZeroInteractions(stylusManager)
+    }
+
+    companion object {
+        private const val OTHER_DEVICE_ID = 0
+        private const val INTERNAL_STYLUS_DEVICE_ID = 1
+        private const val EXTERNAL_STYLUS_DEVICE_ID = 2
+        private val EXECUTOR = FakeExecutor(FakeSystemClock())
+    }
+}
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/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..b5798d5 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -169,3 +169,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/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 b6cd160..4d173d6 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -60,6 +60,7 @@
 import android.hardware.input.VirtualTouchscreenConfig;
 import android.os.Binder;
 import android.os.IBinder;
+import android.os.LocaleList;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -123,6 +124,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() {
@@ -247,12 +251,29 @@
         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) {
         return mParams.getDevicePolicy(policyType);
     }
 
+    /** Returns device-specific audio session id for playback. */
+    public int getAudioPlaybackSessionId() {
+        return mParams.getAudioPlaybackSessionId();
+    }
+
+    /** Returns device-specific audio session id for recording. */
+    public int getAudioRecordingSessionId() {
+        return mParams.getAudioRecordingSessionId();
+    }
+
     /** Returns the unique device ID of this device. */
     @Override // Binder call
     public int getDeviceId() {
@@ -334,6 +355,7 @@
                 mVirtualAudioController.stopListening();
                 mVirtualAudioController = null;
             }
+            mLocaleList = null;
         }
         mOnDeviceCloseListener.onClose(mDeviceId);
         mAppToken.unlinkToDeath(this, 0);
@@ -435,6 +457,7 @@
                         "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 {
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 da2c516..d317298 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.virtual;
 
+import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
+import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
+
 import static com.android.server.wm.ActivityInterceptorCallback.VIRTUAL_DEVICE_SERVICE_ORDERED_ID;
 
 import android.annotation.NonNull;
@@ -39,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;
@@ -134,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));
         }
     };
 
@@ -361,14 +366,10 @@
         @VirtualDeviceParams.DevicePolicy
         public int getDevicePolicy(int deviceId, @VirtualDeviceParams.PolicyType int policyType) {
             synchronized (mVirtualDeviceManagerLock) {
-                for (int i = 0; i < mVirtualDevices.size(); i++) {
-                    final VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return device.getDevicePolicy(policyType);
-                    }
-                }
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice != null
+                        ? virtualDevice.getDevicePolicy(policyType) : DEVICE_POLICY_DEFAULT;
             }
-            return VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
         }
 
 
@@ -388,6 +389,24 @@
             return VirtualDeviceManager.DEVICE_ID_DEFAULT;
         }
 
+        @Override // Binder call
+        public int getAudioPlaybackSessionId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice != null
+                        ? virtualDevice.getAudioPlaybackSessionId() : AUDIO_SESSION_ID_GENERATE;
+            }
+        }
+
+        @Override // Binder call
+        public int getAudioRecordingSessionId(int deviceId) {
+            synchronized (mVirtualDeviceManagerLock) {
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice != null
+                        ? virtualDevice.getAudioRecordingSessionId() : AUDIO_SESSION_ID_GENERATE;
+            }
+        }
+
         @Nullable
         private AssociationInfo getAssociationInfo(String packageName, int associationId) {
             final int callingUserId = getCallingUserHandle().getIdentifier();
@@ -493,15 +512,9 @@
         @Override
         public int getDeviceOwnerUid(int deviceId) {
             synchronized (mVirtualDeviceManagerLock) {
-                int size = mVirtualDevices.size();
-                for (int i = 0; i < size; i++) {
-                    VirtualDeviceImpl device = mVirtualDevices.valueAt(i);
-                    if (device.getDeviceId() == deviceId) {
-                        return device.getOwnerUid();
-                    }
-                }
+                VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId);
+                return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID;
             }
-            return Process.INVALID_UID;
         }
 
         @Override
@@ -580,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 ee922f9..9bedbd0 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -84,11 +84,13 @@
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsCallSession;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.LocalLog;
 import android.util.Pair;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
@@ -357,6 +359,8 @@
 
     private CallQuality[] mCallQuality;
 
+    private List<SparseArray<MediaQualityStatus>> mMediaQualityStatus;
+
     private ArrayList<List<CallState>> mCallStateLists;
 
     // network type of the call associated with the mCallStateLists and mCallQuality
@@ -483,13 +487,13 @@
                 TelephonyCallback.EVENT_DATA_ENABLED_CHANGED);
         REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
                 TelephonyCallback.EVENT_LINK_CAPACITY_ESTIMATE_CHANGED);
+        REQUIRE_PRECISE_PHONE_STATE_PERMISSION.add(
+                TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED);
     }
 
     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,
@@ -715,6 +719,7 @@
                 cutListToSize(mCarrierPrivilegeStates, mNumPhones);
                 cutListToSize(mCarrierServiceStates, mNumPhones);
                 cutListToSize(mCallStateLists, mNumPhones);
+                cutListToSize(mMediaQualityStatus, mNumPhones);
                 return;
             }
 
@@ -738,6 +743,7 @@
                 mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
                 mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
                 mCallQuality[i] = createCallQuality();
+                mMediaQualityStatus.add(i, new SparseArray<>());
                 mCallStateLists.add(i, new ArrayList<>());
                 mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
                 mPreciseCallState[i] = createPreciseCallState();
@@ -805,6 +811,7 @@
         mCallDisconnectCause = new int[numPhones];
         mCallPreciseDisconnectCause = new int[numPhones];
         mCallQuality = new CallQuality[numPhones];
+        mMediaQualityStatus = new ArrayList<>();
         mCallNetworkType = new int[numPhones];
         mCallStateLists = new ArrayList<>();
         mPreciseDataConnectionStates = new ArrayList<>();
@@ -844,6 +851,7 @@
             mCallDisconnectCause[i] = DisconnectCause.NOT_VALID;
             mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID;
             mCallQuality[i] = createCallQuality();
+            mMediaQualityStatus.add(i, new SparseArray<>());
             mCallStateLists.add(i, new ArrayList<>());
             mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN;
             mPreciseCallState[i] = createPreciseCallState();
@@ -1349,15 +1357,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(
@@ -1392,6 +1404,32 @@
                         remove(r.binder);
                     }
                 }
+                if (events.contains(TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED)) {
+                    CallState callState = null;
+                    for (CallState cs : mCallStateLists.get(r.phoneId)) {
+                        if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+                            callState = cs;
+                            break;
+                        }
+                    }
+                    if (callState != null) {
+                        String callId = callState.getImsCallSessionId();
+                        try {
+                            MediaQualityStatus status = mMediaQualityStatus.get(r.phoneId).get(
+                                    MediaQualityStatus.MEDIA_SESSION_TYPE_AUDIO);
+                            if (status != null && status.getCallSessionId().equals(callId)) {
+                                r.callback.onMediaQualityStatusChanged(status);
+                            }
+                            status = mMediaQualityStatus.get(r.phoneId).get(
+                                    MediaQualityStatus.MEDIA_SESSION_TYPE_VIDEO);
+                            if (status != null && status.getCallSessionId().equals(callId)) {
+                                r.callback.onMediaQualityStatusChanged(status);
+                            }
+                        } catch (RemoteException ex) {
+                            remove(r.binder);
+                        }
+                    }
+                }
             }
         }
     }
@@ -1956,7 +1994,8 @@
                 && overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED) {
             overrideNetworkType = TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
         }
-        return new TelephonyDisplayInfo(networkType, overrideNetworkType);
+        boolean isRoaming = telephonyDisplayInfo.isRoaming();
+        return new TelephonyDisplayInfo(networkType, overrideNetworkType, isRoaming);
     }
 
     public void notifyCallForwardingChanged(boolean cfi) {
@@ -2283,6 +2322,17 @@
                         }
                         mCallStateLists.get(phoneId).add(builder.build());
                     }
+                    boolean hasOngoingCall = false;
+                    for (CallState cs : mCallStateLists.get(phoneId)) {
+                        if (cs.getCallState() != PreciseCallState.PRECISE_CALL_STATE_DISCONNECTED) {
+                            hasOngoingCall = true;
+                            break;
+                        }
+                    }
+                    if (!hasOngoingCall) {
+                        //no ongoing call. clear media quality status cached.
+                        mMediaQualityStatus.get(phoneId).clear();
+                    }
                 }
 
                 for (Record r : mRecords) {
@@ -2626,7 +2676,6 @@
                     }
                 }
             }
-
             handleRemoveListLocked();
         }
     }
@@ -3127,6 +3176,55 @@
         }
     }
 
+    @Override
+    public void notifyMediaQualityStatusChanged(int phoneId, int subId, MediaQualityStatus status) {
+        if (!checkNotifyPermission("notifyMediaQualityStatusChanged()")) {
+            return;
+        }
+
+        synchronized (mRecords) {
+            if (validatePhoneId(phoneId)) {
+                if (mCallStateLists.get(phoneId).size() > 0) {
+                    CallState callState = null;
+                    for (CallState cs : mCallStateLists.get(phoneId)) {
+                        if (cs.getCallState() == PreciseCallState.PRECISE_CALL_STATE_ACTIVE) {
+                            callState = cs;
+                            break;
+                        }
+                    }
+                    if (callState != null) {
+                        String callSessionId = callState.getImsCallSessionId();
+                        if (callSessionId != null
+                                && callSessionId.equals(status.getCallSessionId())) {
+                            mMediaQualityStatus.get(phoneId)
+                                    .put(status.getMediaSessionType(), status);
+                        } else {
+                            log("SessionId mismatch active call:" + callSessionId
+                                    + " media quality:" + status.getCallSessionId());
+                            return;
+                        }
+                    } else {
+                        log("There is no active call to report CallQaulity");
+                        return;
+                    }
+                }
+
+                for (Record r : mRecords) {
+                    if (r.matchTelephonyCallbackEvent(
+                            TelephonyCallback.EVENT_MEDIA_QUALITY_STATUS_CHANGED)
+                            && idMatch(r, subId, phoneId)) {
+                        try {
+                            r.callback.onMediaQualityStatusChanged(status);
+                        } catch (RemoteException ex) {
+                            mRemoveList.add(r.binder);
+                        }
+                    }
+                }
+            }
+            handleRemoveListLocked();
+        }
+    }
+
     @NeverCompile // Avoid size overhead of debugging code.
     @Override
     public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3522,29 +3620,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 ccaf353..191460c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -87,7 +87,6 @@
 import static android.os.Process.isSdkSandboxUid;
 import static android.os.Process.isThreadInProcess;
 import static android.os.Process.killProcess;
-import static android.os.Process.killProcessGroup;
 import static android.os.Process.killProcessQuiet;
 import static android.os.Process.myPid;
 import static android.os.Process.myUid;
@@ -198,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;
@@ -696,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;
@@ -961,6 +965,13 @@
             }
             return false;
         }
+
+        boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
+            if (app == null || app.getThread() != null) {
+                return false;
+            }
+            return doRemoveInternal(pid, app);
+        }
     }
 
     private final PendingStartActivityUids mPendingStartActivityUids;
@@ -992,7 +1003,7 @@
      * method.
      */
     @GuardedBy("this")
-    boolean removePidLocked(int pid, ProcessRecord app) {
+    void removePidLocked(int pid, ProcessRecord app) {
         final boolean removed;
         synchronized (mPidsSelfLocked) {
             removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1003,6 +1014,26 @@
             }
             mAtmInternal.onProcessUnMapped(pid);
         }
+    }
+
+    /**
+     * Removes the process record from the map if it doesn't have a thread.
+     * <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
+     * method.
+     */
+    @GuardedBy("this")
+    private boolean removePidIfNoThreadLocked(ProcessRecord app) {
+        final boolean removed;
+        final int pid = app.getPid();
+        synchronized (mPidsSelfLocked) {
+            removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
+        }
+        if (removed) {
+            synchronized (sActiveProcessInfoSelfLocked) {
+                sActiveProcessInfoSelfLocked.remove(pid);
+            }
+            mAtmInternal.onProcessUnMapped(pid);
+        }
         return removed;
     }
 
@@ -2350,7 +2381,7 @@
         mAppErrors = null;
         mPackageWatchdog = null;
         mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
-        mBatteryStatsService = mInjector.getBatteryStatsService();
+        mBatteryStatsService = null;
         mHandler = new MainHandler(handlerThread.getLooper());
         mHandlerThread = handlerThread;
         mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2365,7 +2396,7 @@
         mIntentFirewall = null;
         mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
         mCpHelper = new ContentProviderHelper(this, false);
-        mServices = mInjector.getActiveServices(this);
+        mServices = null;
         mSystemThread = null;
         mUiHandler = injector.getUiHandler(null /* service */);
         mUidObserverController = new UidObserverController(mUiHandler);
@@ -4757,7 +4788,7 @@
     @GuardedBy("this")
     void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
         final int pid = app.getPid();
-        boolean gone = isKillTimeout || removePidLocked(pid, app);
+        boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
 
         if (gone) {
             if (isKillTimeout) {
@@ -4838,7 +4869,7 @@
     }
 
     @GuardedBy("this")
-    private void attachApplicationLocked(@NonNull IApplicationThread thread,
+    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
             int pid, int callingUid, long startSeq) {
 
         // Find the application record that is being attached...  either via
@@ -4903,7 +4934,7 @@
                     // Ignore exceptions.
                 }
             }
-            return;
+            return false;
         }
 
         // If this application record is still attached to a previous
@@ -4928,7 +4959,7 @@
             mProcessList.startProcessLocked(app,
                     new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
                     ZYGOTE_POLICY_FLAG_EMPTY);
-            return;
+            return false;
         }
 
         EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4951,6 +4982,8 @@
             app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
         }
 
+        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
+
         boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
         List<ProviderInfo> providers = normalMode
                                             ? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5116,7 +5149,7 @@
             app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
                     true);
             handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-            return;
+            return false;
         }
 
         // Remove this record from the list of starting applications.
@@ -5124,6 +5157,91 @@
         if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
                 "Attach application locked removing on hold: " + app);
         mProcessesOnHold.remove(app);
+
+        boolean badApp = false;
+        boolean didSomething = false;
+
+        // See if the top visible activity is waiting to run in this process...
+        if (normalMode) {
+            try {
+                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
+                badApp = true;
+            }
+        }
+
+        // Find any services that should be running in this process...
+        if (!badApp) {
+            try {
+                didSomething |= mServices.attachApplicationLocked(app, processName);
+                checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
+                badApp = true;
+            }
+        }
+
+        // Check if a next-broadcast receiver is in this process...
+        if (!badApp) {
+            try {
+                for (BroadcastQueue queue : mBroadcastQueues) {
+                    didSomething |= queue.onApplicationAttachedLocked(app);
+                }
+                checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
+            } catch (Exception e) {
+                // If the app died trying to launch the receiver we declare it 'bad'
+                Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
+                badApp = true;
+            }
+        }
+
+        // Check whether the next backup agent is in this process...
+        if (!badApp && backupTarget != null && backupTarget.app == app) {
+            if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
+                    "New app is backup target, launching agent for " + app);
+            notifyPackageUse(backupTarget.appInfo.packageName,
+                             PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
+            try {
+                thread.scheduleCreateBackupAgent(backupTarget.appInfo,
+                        backupTarget.backupMode, backupTarget.userId,
+                        backupTarget.backupDestination);
+            } catch (Exception e) {
+                Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
+                badApp = true;
+            }
+        }
+
+        if (badApp) {
+            app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
+                    true);
+            handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
+            return false;
+        }
+
+        if (!didSomething) {
+            updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
+            checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
+        }
+
+
+        final HostingRecord hostingRecord = app.getHostingRecord();
+        String shortAction = getShortAction(hostingRecord.getAction());
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.PROCESS_START_TIME,
+                app.info.uid,
+                pid,
+                app.info.packageName,
+                FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
+                app.getStartElapsedTime(),
+                (int) (bindApplicationTimeMillis - app.getStartUptime()),
+                (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
+                hostingRecord.getType(),
+                hostingRecord.getName(),
+                shortAction,
+                HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
+                HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
+        return true;
     }
 
     @Override
@@ -5140,145 +5258,6 @@
         }
     }
 
-    private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
-        final long startTime = SystemClock.uptimeMillis();
-        // Find the application record that is being attached...  either via
-        // the pid if we are running in multiple processes, or just pull the
-        // next app record if we are emulating process with anonymous threads.
-        final ProcessRecord app;
-        synchronized (mPidsSelfLocked) {
-            app = mPidsSelfLocked.get(pid);
-        }
-
-        if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
-            mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
-        } else {
-            Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
-                    + ". Uid: " + uid);
-            killProcess(pid);
-            killProcessGroup(uid, pid);
-            mProcessList.noteAppKill(pid, uid,
-                    ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
-                    ApplicationExitInfo.SUBREASON_UNKNOWN,
-                    "wrong startSeq");
-            synchronized (this) {
-                app.killLocked("unexpected process record",
-                        ApplicationExitInfo.REASON_OTHER, true);
-            }
-            return;
-        }
-
-        synchronized (this) {
-            final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
-            final String processName = app.processName;
-            boolean badApp = false;
-            boolean didSomething = false;
-
-            // See if the top visible activity is waiting to run in this process...
-            if (normalMode) {
-                try {
-                    didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
-                } catch (Exception e) {
-                    Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
-                    badApp = true;
-                }
-            }
-
-            // Find any services that should be running in this process...
-            if (!badApp) {
-                try {
-                    didSomething |= mServices.attachApplicationLocked(app, processName);
-                    checkTime(startTime, "finishAttachApplicationInner: "
-                            + "after mServices.attachApplicationLocked");
-                } catch (Exception e) {
-                    Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
-                    badApp = true;
-                }
-            }
-
-            // Check if a next-broadcast receiver is in this process...
-            if (!badApp) {
-                try {
-                    for (BroadcastQueue queue : mBroadcastQueues) {
-                        didSomething |= queue.onApplicationAttachedLocked(app);
-                    }
-                    checkTime(startTime, "finishAttachApplicationInner: "
-                            + "after dispatching broadcasts");
-                } catch (Exception e) {
-                    // If the app died trying to launch the receiver we declare it 'bad'
-                    Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
-                    badApp = true;
-                }
-            }
-
-            // Check whether the next backup agent is in this process...
-            final BackupRecord backupTarget = mBackupTargets.get(app.userId);
-            if (!badApp && backupTarget != null && backupTarget.app == app) {
-                if (DEBUG_BACKUP) {
-                    Slog.v(TAG_BACKUP,
-                            "New app is backup target, launching agent for " + app);
-                }
-
-                notifyPackageUse(backupTarget.appInfo.packageName,
-                        PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
-                try {
-                    app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
-                            backupTarget.backupMode, backupTarget.userId,
-                            backupTarget.backupDestination);
-                } catch (Exception e) {
-                    Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
-                    badApp = true;
-                }
-            }
-
-            if (badApp) {
-                app.killLocked("error during init",
-                        ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
-                handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
-                return;
-            }
-
-            if (!didSomething) {
-                updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
-                checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
-            }
-
-            final HostingRecord hostingRecord = app.getHostingRecord();
-            final String shortAction = getShortAction(hostingRecord.getAction());
-            FrameworkStatsLog.write(
-                    FrameworkStatsLog.PROCESS_START_TIME,
-                    app.info.uid,
-                    pid,
-                    app.info.packageName,
-                    FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
-                    app.getStartElapsedTime(),
-                    (int) (app.getBindApplicationTime() - app.getStartUptime()),
-                    (int) (SystemClock.uptimeMillis() - app.getStartUptime()),
-                    hostingRecord.getType(),
-                    hostingRecord.getName(),
-                    shortAction,
-                    HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
-                    HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
-        }
-    }
-
-    @Override
-    public final void finishAttachApplication(long startSeq) {
-        final int pid = Binder.getCallingPid();
-        final int uid = Binder.getCallingUid();
-
-        if (pid == MY_PID && uid == SYSTEM_UID) {
-            return;
-        }
-
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            finishAttachApplicationInner(startSeq, uid, pid);
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
-    }
-
     /**
      * @return The last part of the string of an intent's action.
      */
@@ -7438,10 +7417,11 @@
         if (shareDescription != null) {
             triggerShellBugreport.putExtra(EXTRA_DESCRIPTION, shareDescription);
         }
+        UserHandle callingUser = Binder.getCallingUserHandle();
         final long identity = Binder.clearCallingIdentity();
         try {
             // Send broadcast to shell to trigger bugreport using Bugreport API
-            mContext.sendBroadcastAsUser(triggerShellBugreport, UserHandle.SYSTEM);
+            mContext.sendBroadcastAsUser(triggerShellBugreport, callingUser);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -8821,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
@@ -12682,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
@@ -12690,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;
@@ -12710,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);
@@ -13079,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,
@@ -14641,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) {
@@ -14747,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,
@@ -16599,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++) {
@@ -16620,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} */
@@ -18220,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) {
@@ -18816,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,
@@ -18826,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;
             }
 
@@ -18874,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 "
@@ -18882,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;
             }
 
@@ -18891,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));
                 }
@@ -18899,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);
         }
 
         /**
@@ -18919,21 +18940,6 @@
             return new ProcessList();
         }
 
-        /**
-         * Returns the {@link BatteryStatsService} instance
-         */
-        public BatteryStatsService getBatteryStatsService() {
-            return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
-                BackgroundThread.get().getHandler());
-        }
-
-        /**
-         * Returns the {@link ActiveServices} instance
-         */
-        public ActiveServices getActiveServices(ActivityManagerService service) {
-            return new ActiveServices(service);
-        }
-
         private boolean ensureHasNetworkManagementInternal() {
             if (mNmi == null) {
                 mNmi = LocalServices.getService(NetworkManagementInternal.class);
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/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 4d559b0..80f1321 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2508,7 +2508,7 @@
     }
 
     @GuardedBy("mService")
-    String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
+    private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
         StringBuilder sb = null;
         if (app.isKilledByAm()) {
             if (sb == null) sb = new StringBuilder();
@@ -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/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 33d7f9d..cf91429 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -200,11 +200,6 @@
     private volatile long mStartElapsedTime;
 
     /**
-     * When the process was sent the bindApplication request
-     */
-    private volatile long mBindApplicationTime;
-
-    /**
      * This will be same as {@link #uid} usually except for some apps used during factory testing.
      */
     private volatile int mStartUid;
@@ -744,10 +739,6 @@
         return mStartElapsedTime;
     }
 
-    long getBindApplicationTime() {
-        return mBindApplicationTime;
-    }
-
     int getStartUid() {
         return mStartUid;
     }
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/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/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/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/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/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/incident/IncidentCompanionService.java b/services/core/java/com/android/server/incident/IncidentCompanionService.java
index 87fe785..b8e7d49 100644
--- a/services/core/java/com/android/server/incident/IncidentCompanionService.java
+++ b/services/core/java/com/android/server/incident/IncidentCompanionService.java
@@ -34,7 +34,6 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.util.Log;
 
 import com.android.internal.util.DumpUtils;
@@ -128,21 +127,21 @@
             try {
                 final Context context = getContext();
 
-                final int primaryUser = getAndValidateUser(context);
-                if (primaryUser == UserHandle.USER_NULL) {
+                // Get the current admin user. Only they can do incident reports.
+                final int currentAdminUser = getCurrentUserIfAdmin();
+                if (currentAdminUser == UserHandle.USER_NULL) {
                     return;
                 }
 
                 final Intent intent = new Intent(Intent.ACTION_INCIDENT_REPORT_READY);
                 intent.setComponent(new ComponentName(pkg, cls));
 
-                Log.d(TAG, "sendReportReadyBroadcast sending primaryUser=" + primaryUser
-                        + " userHandle=" + UserHandle.getUserHandleForUid(primaryUser)
+                Log.d(TAG, "sendReportReadyBroadcast sending currentUser=" + currentAdminUser
+                        + " userHandle=" + UserHandle.of(currentAdminUser)
                         + " intent=" + intent);
 
-                // Send it to the primary user.  Only they can do incident reports.
                 context.sendBroadcastAsUserMultiplePermissions(intent,
-                        UserHandle.getUserHandleForUid(primaryUser),
+                        UserHandle.of(currentAdminUser),
                         DUMP_AND_USAGE_STATS_PERMISSIONS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -414,10 +413,10 @@
     }
 
     /**
-     * Check whether the current user is the primary user, and return the user id if they are.
+     * Check whether the current user is an admin user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    public static int getAndValidateUser(Context context) {
+    public static int getCurrentUserIfAdmin() {
         // Current user
         UserInfo currentUser;
         try {
@@ -427,28 +426,21 @@
             throw new RuntimeException(ex);
         }
 
-        // Primary user
-        final UserManager um = UserManager.get(context);
-        final UserInfo primaryUser = um.getPrimaryUser();
-
         // Check that we're using the right user.
         if (currentUser == null) {
             Log.w(TAG, "No current user.  Nobody to approve the report."
                     + " The report will be denied.");
             return UserHandle.USER_NULL;
         }
-        if (primaryUser == null) {
-            Log.w(TAG, "No primary user.  Nobody to approve the report."
-                    + " The report will be denied.");
-            return UserHandle.USER_NULL;
-        }
-        if (primaryUser.id != currentUser.id) {
-            Log.w(TAG, "Only the primary user can approve bugreports, but they are not"
-                    + " the current user. The report will be denied.");
+
+        if (!currentUser.isAdmin()) {
+            Log.w(TAG, "Only an admin user running in foreground can approve "
+                    + "bugreports, but the current foreground user is not an admin user. "
+                    + "The report will be denied.");
             return UserHandle.USER_NULL;
         }
 
-        return primaryUser.id;
+        return currentUser.id;
     }
 }
 
diff --git a/services/core/java/com/android/server/incident/PendingReports.java b/services/core/java/com/android/server/incident/PendingReports.java
index f39bebf..684b5f1 100644
--- a/services/core/java/com/android/server/incident/PendingReports.java
+++ b/services/core/java/com/android/server/incident/PendingReports.java
@@ -16,6 +16,7 @@
 
 package com.android.server.incident;
 
+import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.ComponentName;
@@ -30,6 +31,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.util.Log;
 
 import java.io.FileDescriptor;
@@ -272,15 +274,22 @@
             return;
         }
 
-        // Find the primary user of this device.
-        final int primaryUser = getAndValidateUser();
-        if (primaryUser == UserHandle.USER_NULL) {
+        // Find the current user of the device and check if they are an admin.
+        final int currentAdminUser = getCurrentUserIfAdmin();
+        final int callingUser = UserHandle.getUserId(callingUid);
+
+        // Deny the report if the current admin user is null
+        // or the calling user is not from the same profile group of current user.
+        if (currentAdminUser == UserHandle.USER_NULL
+                || !isSameProfileGroupUser(callingUser, currentAdminUser)) {
+            Log.w(TAG, "Calling user " + callingUser + " doesn't belong to the same profile "
+                    + "group of the current admin user " + currentAdminUser);
             denyReportBeforeAddingRec(listener, callingPackage);
             return;
         }
 
         // Find the approver app (hint: it's PermissionController).
-        final ComponentName receiver = getApproverComponent(primaryUser);
+        final ComponentName receiver = getApproverComponent(currentAdminUser);
         if (receiver == null) {
             // We couldn't find an approver... so deny the request here and now, before we
             // do anything else.
@@ -298,26 +307,26 @@
         try {
             listener.asBinder().linkToDeath(() -> {
                 Log.i(TAG, "Got death notification listener=" + listener);
-                cancelReportImpl(listener, receiver, primaryUser);
+                cancelReportImpl(listener, receiver, currentAdminUser);
             }, 0);
         } catch (RemoteException ex) {
             Log.e(TAG, "Remote died while trying to register death listener: " + rec.getUri());
             // First, remove from our list.
-            cancelReportImpl(listener, receiver, primaryUser);
+            cancelReportImpl(listener, receiver, currentAdminUser);
         }
 
         // Go tell Permission controller to start asking the user.
-        sendBroadcast(receiver, primaryUser);
+        sendBroadcast(receiver, currentAdminUser);
     }
 
     /**
      * Cancel a pending report request (because of an explicit call to cancel)
      */
     private void cancelReportImpl(IIncidentAuthListener listener) {
-        final int primaryUser = getAndValidateUser();
-        final ComponentName receiver = getApproverComponent(primaryUser);
-        if (primaryUser != UserHandle.USER_NULL && receiver != null) {
-            cancelReportImpl(listener, receiver, primaryUser);
+        final int currentAdminUser = getCurrentUserIfAdmin();
+        final ComponentName receiver = getApproverComponent(currentAdminUser);
+        if (currentAdminUser != UserHandle.USER_NULL && receiver != null) {
+            cancelReportImpl(listener, receiver, currentAdminUser);
         }
     }
 
@@ -326,13 +335,13 @@
      * by the calling app, or because of a binder death).
      */
     private void cancelReportImpl(IIncidentAuthListener listener, ComponentName receiver,
-            int primaryUser) {
+            @UserIdInt int user) {
         // First, remove from our list.
         synchronized (mLock) {
             removePendingReportRecLocked(listener);
         }
         // Second, call back to PermissionController to say it's canceled.
-        sendBroadcast(receiver, primaryUser);
+        sendBroadcast(receiver, user);
     }
 
     /**
@@ -342,21 +351,21 @@
      * cleanup cases to keep the apps' list in sync with ours.
      */
     private void sendBroadcast() {
-        final int primaryUser = getAndValidateUser();
-        if (primaryUser == UserHandle.USER_NULL) {
+        final int currentAdminUser = getCurrentUserIfAdmin();
+        if (currentAdminUser == UserHandle.USER_NULL) {
             return;
         }
-        final ComponentName receiver = getApproverComponent(primaryUser);
+        final ComponentName receiver = getApproverComponent(currentAdminUser);
         if (receiver == null) {
             return;
         }
-        sendBroadcast(receiver, primaryUser);
+        sendBroadcast(receiver, currentAdminUser);
     }
 
     /**
      * Send the confirmation broadcast.
      */
-    private void sendBroadcast(ComponentName receiver, int primaryUser) {
+    private void sendBroadcast(ComponentName receiver, int currentUser) {
         final Intent intent = new Intent(Intent.ACTION_PENDING_INCIDENT_REPORTS_CHANGED);
         intent.setComponent(receiver);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
@@ -364,8 +373,8 @@
         final BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setBackgroundActivityStartsAllowed(true);
 
-        // Send it to the primary user.
-        mContext.sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(primaryUser),
+        // Send it to the current user.
+        mContext.sendBroadcastAsUser(intent, UserHandle.of(currentUser),
                 android.Manifest.permission.APPROVE_INCIDENT_REPORTS, options.toBundle());
     }
 
@@ -420,11 +429,11 @@
     }
 
     /**
-     * Check whether the current user is the primary user, and return the user id if they are.
+     * Check whether the current user is an admin user, and return the user id if they are.
      * Returns UserHandle.USER_NULL if not valid.
      */
-    private int getAndValidateUser() {
-        return IncidentCompanionService.getAndValidateUser(mContext);
+    private int getCurrentUserIfAdmin() {
+        return IncidentCompanionService.getCurrentUserIfAdmin();
     }
 
     /**
@@ -461,5 +470,15 @@
             return false;
         }
     }
+
+    /**
+     * Checks if the 2 provided user ids belong to the same profile group
+     * using {@link UserManager#isSameProfileGroup(int, int)}
+     */
+    private boolean isSameProfileGroupUser(@UserIdInt int currentAdminUser,
+            @UserIdInt int callingUser) {
+        return UserManager.get(mContext)
+                .isSameProfileGroup(currentAdminUser, callingUser);
+    }
 }
 
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/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 97c8305..5840acf 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -183,6 +183,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;
@@ -304,6 +305,8 @@
      */
     @Nullable
     private AudioManagerInternal mAudioManagerInternal = null;
+    @Nullable
+    private VirtualDeviceManagerInternal mVdmInternal = null;
 
     // All known input methods.
     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
@@ -2533,6 +2536,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
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..89bc495a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -267,8 +267,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";
 
@@ -375,7 +374,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
@@ -713,7 +712,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 +725,7 @@
         }
     }
 
-    public void onStartUser(final int userId) {
+    private void onStartUser(final int userId) {
         maybeShowEncryptionNotificationForUser(userId, "user started");
     }
 
@@ -779,7 +779,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 +836,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 +1225,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 +1245,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 +1691,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);
         }
@@ -1768,24 +1768,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 +1784,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 +2536,7 @@
         }
     }
 
-    protected synchronized IGateKeeperService getGateKeeperService() {
+    private synchronized IGateKeeperService getGateKeeperService() {
         if (mGateKeeperService != null) {
             return mGateKeeperService;
         }
@@ -2604,25 +2580,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);
             }
         }
     }
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/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..ad2fa22 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -112,7 +112,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";
@@ -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;
@@ -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);
             }
@@ -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
@@ -1623,7 +1623,7 @@
         SyntheticPasswordCrypto.destroyProtectorKey(keyAlias);
     }
 
-    public static long generateProtectorId() {
+    private static long generateProtectorId() {
         SecureRandom rng = new SecureRandom();
         long result;
         do {
@@ -1685,15 +1685,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/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/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
index 58428ca..2fdc4cd 100644
--- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
+++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java
@@ -33,8 +33,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Slog;
 
@@ -189,10 +189,10 @@
     }
 
     /**
-     * Validates that the current user is the primary user or when bugreport is requested remotely
-     * and current user is affiliated user.
+     * Validates that the current user is an admin user or, when bugreport is requested remotely
+     * that the current user is an affiliated user.
      *
-     * @throws IllegalArgumentException if the current user is not the primary user
+     * @throws IllegalArgumentException if the current user is not an admin user
      */
     private void ensureUserCanTakeBugReport(int bugreportMode) {
         UserInfo currentUser = null;
@@ -202,20 +202,17 @@
             // Impossible to get RemoteException for an in-process call.
         }
 
-        UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
         if (currentUser == null) {
-            logAndThrow("No current user. Only primary user is allowed to take bugreports.");
+            logAndThrow("There is no current user, so no bugreport can be requested.");
         }
-        if (primaryUser == null) {
-            logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
-        }
-        if (primaryUser.id != currentUser.id) {
+
+        if (!currentUser.isAdmin()) {
             if (bugreportMode == BugreportParams.BUGREPORT_MODE_REMOTE
                     && isCurrentUserAffiliated(currentUser.id)) {
                 return;
             }
-            logAndThrow("Current user not primary user. Only primary user"
-                    + " is allowed to take bugreports.");
+            logAndThrow(TextUtils.formatSimple("Current user %s is not an admin user."
+                    + " Only admin users are allowed to take bugreport.", currentUser.id));
         }
     }
 
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/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..d873736 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 =
@@ -2566,8 +2568,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 +3619,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 +5419,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 +5427,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 +5526,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 +5546,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..9ea1807 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -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..7049f9a 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 {
@@ -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");
@@ -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);
             }
@@ -3845,7 +3848,7 @@
 
         boolean isUpdatedSystemApp;
         if (installedPkgSetting != null) {
-            isUpdatedSystemApp = installedPkgSetting.getPkgState().isUpdatedSystemApp();
+            isUpdatedSystemApp = installedPkgSetting.isUpdatedSystemApp();
         } else {
             isUpdatedSystemApp = disabledPkgSetting != null;
         }
@@ -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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index f65a65d..ed4c849 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();
     }
 
     /**
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..a52ed8b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1574,7 +1574,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 +1905,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 +2242,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);
@@ -3685,7 +3685,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 +3887,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 +6408,7 @@
             }
 
             AndroidPackage pkg = packageState.getPkg();
-            return pkg != null && pkg.isSystem() && pkg.isPersistent();
+            return pkg != null && packageState.isSystem() && pkg.isPersistent();
         }
 
         @Override
@@ -6967,8 +6967,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/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 41985e3..9b6bfd9 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++) {
@@ -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..53be787 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;
@@ -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..8c2b212 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;
                 }
 
@@ -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..a7a4c4e 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -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..87805e0 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -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));
     }
@@ -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();
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..4fee84f 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
@@ -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/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 f9c0deb..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;
@@ -683,7 +684,7 @@
             if (bp == null) {
                 return;
             }
-            if (bp.isDynamic()) {
+            if (!bp.isDynamic()) {
                 // TODO: switch this back to SecurityException
                 Slog.wtf(TAG, "Not allowed to modify non-dynamic permission "
                         + permName);
@@ -1299,13 +1300,13 @@
                     }
                 }
             }
+        }
 
-            if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
-                if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
-                    throw new SecurityException("Modifying installer allowlist requires"
-                            + " being installer on record or "
-                            + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
-                }
+        if ((flags & PackageManager.FLAG_PERMISSION_WHITELIST_INSTALLER) != 0) {
+            if (!isCallerPrivileged && !isCallerInstallerOnRecord) {
+                throw new SecurityException("Modifying installer allowlist requires"
+                        + " being installer on record or "
+                        + Manifest.permission.WHITELIST_RESTRICTED_PERMISSIONS);
             }
         }
 
@@ -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..075173d 100644
--- a/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/services/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -249,8 +249,12 @@
     /**
      * @see ApplicationInfo#sourceDir
      * @see ApplicationInfo#getBaseCodePath
+     *
+     * @deprecated Use {@link #getSplits()}[0].{@link AndroidPackageSplit#getPath() getPath()}
+     *
      * @hide
      */
+    @Deprecated
     @NonNull
     String getBaseApkPath();
 
@@ -1337,18 +1341,6 @@
     boolean isNativeLibraryRootRequiresIsa();
 
     /**
-     * @see ApplicationInfo#PRIVATE_FLAG_ODM
-     * @hide
-     */
-    boolean isOdm();
-
-    /**
-     * @see ApplicationInfo#PRIVATE_FLAG_OEM
-     * @hide
-     */
-    boolean isOem();
-
-    /**
      * @see R.styleable#AndroidManifestApplication_enableOnBackInvokedCallback
      * @hide
      */
@@ -1386,18 +1378,6 @@
     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
@@ -1525,18 +1505,6 @@
     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
@@ -1561,12 +1529,6 @@
     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/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 2626bb4..16f5d16 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
@@ -452,6 +452,9 @@
     List<ParsedProvider> getProviders();
 
     @NonNull
+    List<ParsedActivity> getReceivers();
+
+    @NonNull
     List<String> getRequestedPermissions();
 
     @Nullable
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 952adda..c6e1793 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
@@ -824,8 +824,8 @@
     }
 
     private static boolean hasTooManyComponents(ParsingPackage pkg) {
-        return pkg.getActivities().size() + pkg.getServices().size() + pkg.getProviders().size()
-                > MAX_NUM_COMPONENTS;
+        return (pkg.getActivities().size() + pkg.getServices().size() + pkg.getProviders().size()
+                + pkg.getReceivers().size()) > MAX_NUM_COMPONENTS;
     }
 
     /**
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/AbsAppSnapshotCache.java b/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java
new file mode 100644
index 0000000..c8adc8f
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotCache.java
@@ -0,0 +1,107 @@
+/*
+ * 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.Nullable;
+import android.util.ArrayMap;
+import android.window.TaskSnapshot;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class for an app snapshot cache
+ * @param <TYPE> The basic type, either Task or ActivityRecord
+ */
+abstract class AbsAppSnapshotCache<TYPE extends WindowContainer> {
+    protected final WindowManagerService mService;
+    protected final String mName;
+    protected final ArrayMap<ActivityRecord, Integer> mAppIdMap = new ArrayMap<>();
+    protected final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+
+    AbsAppSnapshotCache(WindowManagerService service, String name) {
+        mService = service;
+        mName = name;
+    }
+
+    abstract void putSnapshot(TYPE window, TaskSnapshot snapshot);
+
+    void clearRunningCache() {
+        mRunningCache.clear();
+    }
+
+    @Nullable
+    final TaskSnapshot getSnapshot(Integer id) {
+        synchronized (mService.mGlobalLock) {
+            // Try the running cache.
+            final CacheEntry entry = mRunningCache.get(id);
+            if (entry != null) {
+                return entry.snapshot;
+            }
+        }
+        return null;
+    }
+
+    /** Called when an app token has been removed. */
+    void onAppRemoved(ActivityRecord activity) {
+        final Integer id = mAppIdMap.get(activity);
+        if (id != null) {
+            removeRunningEntry(id);
+        }
+    }
+
+    /** Called when an app window token's process died. */
+    void onAppDied(ActivityRecord activity) {
+        final Integer id = mAppIdMap.get(activity);
+        if (id != null) {
+            removeRunningEntry(id);
+        }
+    }
+
+    void onIdRemoved(Integer index) {
+        removeRunningEntry(index);
+    }
+
+    void removeRunningEntry(Integer id) {
+        final CacheEntry entry = mRunningCache.get(id);
+        if (entry != null) {
+            mAppIdMap.remove(entry.topApp);
+            mRunningCache.remove(id);
+        }
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        final String doublePrefix = prefix + "  ";
+        final String triplePrefix = doublePrefix + "  ";
+        pw.println(prefix + "SnapshotCache " + mName);
+        for (int i = mRunningCache.size() - 1; i >= 0; i--) {
+            final CacheEntry entry = mRunningCache.valueAt(i);
+            pw.println(doublePrefix + "Entry token=" + mRunningCache.keyAt(i));
+            pw.println(triplePrefix + "topApp=" + entry.topApp);
+            pw.println(triplePrefix + "snapshot=" + entry.snapshot);
+        }
+    }
+
+    static final class CacheEntry {
+        /** The snapshot. */
+        final TaskSnapshot snapshot;
+        /** The app token that was on top of the task when the snapshot was taken */
+        final ActivityRecord topApp;
+        CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp) {
+            this.snapshot = snapshot;
+            this.topApp = topApp;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
new file mode 100644
index 0000000..5e89fa6
--- /dev/null
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -0,0 +1,457 @@
+/*
+ * 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.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.RecordingCanvas;
+import android.graphics.Rect;
+import android.graphics.RenderNode;
+import android.hardware.HardwareBuffer;
+import android.os.Trace;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.ThreadedRenderer;
+import android.view.WindowInsets;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.window.ScreenCapture;
+import android.window.SnapshotDrawerUtils;
+import android.window.TaskSnapshot;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+import com.android.server.wm.utils.InsetUtils;
+
+import java.io.PrintWriter;
+
+/**
+ * Base class for a Snapshot controller
+ * @param <TYPE> The basic type, either Task or ActivityRecord
+ * @param <CACHE> The basic cache for either Task or ActivityRecord
+ */
+abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
+        CACHE extends AbsAppSnapshotCache<TYPE>> {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "SnapshotController" : TAG_WM;
+    /**
+     * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
+     * used as the snapshot.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_REAL = 0;
+    /**
+     * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
+     * we should try to use the app theme to create a fake representation of the app.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_APP_THEME = 1;
+    /**
+     * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
+     */
+    @VisibleForTesting
+    static final int SNAPSHOT_MODE_NONE = 2;
+
+    protected final WindowManagerService mService;
+    protected final float mHighResTaskSnapshotScale;
+    private final Rect mTmpRect = new Rect();
+    /**
+     * Flag indicating whether we are running on an Android TV device.
+     */
+    protected final boolean mIsRunningOnTv;
+    /**
+     * Flag indicating whether we are running on an IoT device.
+     */
+    protected final boolean mIsRunningOnIoT;
+
+    protected CACHE mCache;
+    /**
+     * Flag indicating if task snapshot is enabled on this device.
+     */
+    private boolean mSnapshotEnabled;
+
+    AbsAppSnapshotController(WindowManagerService service) {
+        mService = service;
+        mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LEANBACK);
+        mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_EMBEDDED);
+        mHighResTaskSnapshotScale = initSnapshotScale();
+    }
+
+    protected float initSnapshotScale() {
+        return mService.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+    }
+
+    /**
+     * Set basic cache to the controller.
+     */
+    protected void initialize(CACHE cache) {
+        mCache = cache;
+    }
+
+    void setSnapshotEnabled(boolean enabled) {
+        mSnapshotEnabled = enabled;
+    }
+
+    boolean shouldDisableSnapshots() {
+        return mIsRunningOnTv || mIsRunningOnIoT || !mSnapshotEnabled;
+    }
+
+    abstract ActivityRecord getTopActivity(TYPE source);
+    abstract ActivityRecord getTopFullscreenActivity(TYPE source);
+    abstract ActivityManager.TaskDescription getTaskDescription(TYPE source);
+    /**
+     * Find the window for a given task to take a snapshot. Top child of the task is usually the one
+     * we're looking for, but during app transitions, trampoline activities can appear in the
+     * children, which should be ignored.
+     */
+    @Nullable
+    protected abstract ActivityRecord findAppTokenForSnapshot(TYPE source);
+    protected abstract boolean use16BitFormat();
+
+    /**
+     * This is different than {@link #recordSnapshotInner(TYPE, boolean)} because it doesn't store
+     * the snapshot to the cache and returns the TaskSnapshot immediately.
+     *
+     * This is only used for testing so the snapshot content can be verified.
+     */
+    @VisibleForTesting
+    TaskSnapshot captureSnapshot(TYPE source, boolean snapshotHome) {
+        final TaskSnapshot snapshot;
+        if (snapshotHome) {
+            snapshot = snapshot(source);
+        } else {
+            switch (getSnapshotMode(source)) {
+                case SNAPSHOT_MODE_NONE:
+                    return null;
+                case SNAPSHOT_MODE_APP_THEME:
+                    snapshot = drawAppThemeSnapshot(source);
+                    break;
+                case SNAPSHOT_MODE_REAL:
+                    snapshot = snapshot(source);
+                    break;
+                default:
+                    snapshot = null;
+                    break;
+            }
+        }
+        return snapshot;
+    }
+
+    final TaskSnapshot recordSnapshotInner(TYPE source, boolean allowSnapshotHome) {
+        final boolean snapshotHome = allowSnapshotHome && source.isActivityTypeHome();
+        final TaskSnapshot snapshot = captureSnapshot(source, snapshotHome);
+        if (snapshot == null) {
+            return null;
+        }
+        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
+        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
+            buffer.close();
+            Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
+                    + buffer.getHeight());
+            return null;
+        } else {
+            mCache.putSnapshot(source, snapshot);
+            return snapshot;
+        }
+    }
+
+    @VisibleForTesting
+    int getSnapshotMode(TYPE source) {
+        final ActivityRecord topChild = getTopActivity(source);
+        if (!source.isActivityTypeStandardOrUndefined() && !source.isActivityTypeAssistant()) {
+            return SNAPSHOT_MODE_NONE;
+        } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
+            return SNAPSHOT_MODE_APP_THEME;
+        } else {
+            return SNAPSHOT_MODE_REAL;
+        }
+    }
+
+    @Nullable
+    TaskSnapshot snapshot(TYPE source) {
+        return snapshot(source, PixelFormat.UNKNOWN);
+    }
+
+    @Nullable
+    TaskSnapshot snapshot(TYPE source, int pixelFormat) {
+        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
+        if (!prepareTaskSnapshot(source, pixelFormat, builder)) {
+            // Failed some pre-req. Has been logged.
+            return null;
+        }
+        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+                createSnapshot(source, builder);
+        if (screenshotBuffer == null) {
+            // Failed to acquire image. Has been logged.
+            return null;
+        }
+        builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
+        builder.setColorSpace(screenshotBuffer.getColorSpace());
+        return builder.build();
+    }
+
+    @Nullable
+    ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
+            TaskSnapshot.Builder builder) {
+        Point taskSize = new Point();
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createSnapshot");
+        final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createSnapshot(source,
+                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+        builder.setTaskSize(taskSize);
+        return taskSnapshot;
+    }
+
+    @Nullable
+    ScreenCapture.ScreenshotHardwareBuffer createSnapshot(@NonNull TYPE source,
+            float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
+        if (source.getSurfaceControl() == null) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + source);
+            }
+            return null;
+        }
+        source.getBounds(mTmpRect);
+        mTmpRect.offsetTo(0, 0);
+        SurfaceControl[] excludeLayers;
+        final WindowState imeWindow = source.getDisplayContent().mInputMethodWindow;
+        // Exclude IME window snapshot when IME isn't proper to attach to app.
+        final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
+                && !source.getDisplayContent().shouldImeAttachedToApp();
+        final WindowState navWindow =
+                source.getDisplayContent().getDisplayPolicy().getNavigationBar();
+        // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
+        // the swiped app when entering recent app, therefore the task will contain the navigation
+        // bar and we should exclude it from snapshot.
+        final boolean excludeNavBar = navWindow != null;
+        if (excludeIme && excludeNavBar) {
+            excludeLayers = new SurfaceControl[2];
+            excludeLayers[0] = imeWindow.getSurfaceControl();
+            excludeLayers[1] = navWindow.getSurfaceControl();
+        } else if (excludeIme || excludeNavBar) {
+            excludeLayers = new SurfaceControl[1];
+            excludeLayers[0] =
+                    excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
+        } else {
+            excludeLayers = new SurfaceControl[0];
+        }
+        builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
+        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
+                ScreenCapture.captureLayersExcluding(
+                        source.getSurfaceControl(), mTmpRect, scaleFraction,
+                        pixelFormat, excludeLayers);
+        if (outTaskSize != null) {
+            outTaskSize.x = mTmpRect.width();
+            outTaskSize.y = mTmpRect.height();
+        }
+        final HardwareBuffer buffer = screenshotBuffer == null ? null
+                : screenshotBuffer.getHardwareBuffer();
+        if (isInvalidHardwareBuffer(buffer)) {
+            return null;
+        }
+        return screenshotBuffer;
+    }
+
+    static boolean isInvalidHardwareBuffer(HardwareBuffer buffer) {
+        return buffer == null || buffer.isClosed() // This must be checked before getting size.
+                || buffer.getWidth() <= 1 || buffer.getHeight() <= 1;
+    }
+
+    /**
+     * Validates the state of the Task is appropriate to capture a snapshot, collects
+     * information from the task and populates the builder.
+     *
+     * @param source the window to capture
+     * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
+     *                    automatically select
+     * @param builder the snapshot builder to populate
+     *
+     * @return true if the state of the task is ok to proceed
+     */
+    @VisibleForTesting
+    boolean prepareTaskSnapshot(TYPE source, int pixelFormat, TaskSnapshot.Builder builder) {
+        final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(source);
+        if (result == null) {
+            return false;
+        }
+        final ActivityRecord activity = result.first;
+        final WindowState mainWindow = result.second;
+        final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
+                mainWindow.getInsetsStateWithVisibilityOverride());
+        final Rect letterboxInsets = activity.getLetterboxInsets();
+        InsetUtils.addInsets(contentInsets, letterboxInsets);
+        builder.setIsRealSnapshot(true);
+        builder.setId(System.currentTimeMillis());
+        builder.setContentInsets(contentInsets);
+        builder.setLetterboxInsets(letterboxInsets);
+        final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
+        final boolean isShowWallpaper = mainWindow.hasWallpaper();
+        if (pixelFormat == PixelFormat.UNKNOWN) {
+            pixelFormat = use16BitFormat() && activity.fillsParent()
+                    && !(isWindowTranslucent && isShowWallpaper)
+                    ? PixelFormat.RGB_565
+                    : PixelFormat.RGBA_8888;
+        }
+        final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
+                && (!activity.fillsParent() || isWindowTranslucent);
+        builder.setTopActivityComponent(activity.mActivityComponent);
+        builder.setPixelFormat(pixelFormat);
+        builder.setIsTranslucent(isTranslucent);
+        builder.setOrientation(activity.getTask().getConfiguration().orientation);
+        builder.setRotation(activity.getTask().getDisplayContent().getRotation());
+        builder.setWindowingMode(source.getWindowingMode());
+        builder.setAppearance(getAppearance(source));
+        return true;
+    }
+
+    /**
+     * Check if the state of the Task is appropriate to capture a snapshot, such like the task
+     * snapshot or the associated IME surface snapshot.
+     *
+     * @param source the target object to capture the snapshot
+     * @return Pair of (the top activity of the task, the main window of the task) if passed the
+     * state checking. Returns {@code null} if the task state isn't ready to snapshot.
+     */
+    Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(TYPE source) {
+        if (!mService.mPolicy.isScreenOn()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+            }
+            return null;
+        }
+        final ActivityRecord activity = findAppTokenForSnapshot(source);
+        if (activity == null) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + source);
+            }
+            return null;
+        }
+        if (activity.hasCommittedReparentToAnimationLeash()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
+            }
+            return null;
+        }
+        final WindowState mainWindow = activity.findMainWindow();
+        if (mainWindow == null) {
+            Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + source);
+            return null;
+        }
+        if (activity.hasFixedRotationTransform()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity);
+            }
+            // The activity is in a temporal state that it has different rotation than the task.
+            return null;
+        }
+        return new Pair<>(activity, mainWindow);
+    }
+
+    /**
+     * If we are not allowed to take a real screenshot, this attempts to represent the app as best
+     * as possible by using the theme's window background.
+     */
+    private TaskSnapshot drawAppThemeSnapshot(TYPE source) {
+        final ActivityRecord topActivity = getTopActivity(source);
+        if (topActivity == null) {
+            return null;
+        }
+        final WindowState mainWindow = topActivity.findMainWindow();
+        if (mainWindow == null) {
+            return null;
+        }
+        final ActivityManager.TaskDescription taskDescription = getTaskDescription(source);
+        final int color = ColorUtils.setAlphaComponent(
+                taskDescription.getBackgroundColor(), 255);
+        final WindowManager.LayoutParams attrs = mainWindow.getAttrs();
+        final Rect taskBounds = source.getBounds();
+        final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
+        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
+        final SnapshotDrawerUtils.SystemBarBackgroundPainter
+                decorPainter = new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
+                attrs.privateFlags, attrs.insetsFlags.appearance, taskDescription,
+                mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
+        final int taskWidth = taskBounds.width();
+        final int taskHeight = taskBounds.height();
+        final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
+        final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
+        final RenderNode node = RenderNode.create("SnapshotController", null);
+        node.setLeftTopRightBottom(0, 0, width, height);
+        node.setClipToBounds(false);
+        final RecordingCanvas c = node.start(width, height);
+        c.drawColor(color);
+        decorPainter.setInsets(systemBarInsets);
+        decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawFrame */);
+        node.end(c);
+        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
+        if (hwBitmap == null) {
+            return null;
+        }
+        final Rect contentInsets = new Rect(systemBarInsets);
+        final Rect letterboxInsets = topActivity.getLetterboxInsets();
+        InsetUtils.addInsets(contentInsets, letterboxInsets);
+        // Note, the app theme snapshot is never translucent because we enforce a non-translucent
+        // color above
+        return new TaskSnapshot(
+                System.currentTimeMillis() /* id */,
+                topActivity.mActivityComponent, hwBitmap.getHardwareBuffer(),
+                hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
+                mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
+                contentInsets, letterboxInsets, false /* isLowResolution */,
+                false /* isRealSnapshot */, source.getWindowingMode(),
+                getAppearance(source), false /* isTranslucent */, false /* hasImeSurface */);
+    }
+
+    static Rect getSystemBarInsets(Rect frame, InsetsState state) {
+        return state.calculateInsets(
+                frame, WindowInsets.Type.systemBars(), false /* ignoreVisibility */).toRect();
+    }
+
+    /**
+     * @return The {@link WindowInsetsController.Appearance} flags for the top fullscreen opaque
+     * window in the given {@param TYPE}.
+     */
+    @WindowInsetsController.Appearance
+    private int getAppearance(TYPE source) {
+        final ActivityRecord topFullscreenActivity = getTopFullscreenActivity(source);
+        final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
+                ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
+                : null;
+        if (topFullscreenOpaqueWindow != null) {
+            return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance;
+        }
+        return 0;
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale);
+        pw.println(prefix + "mTaskSnapshotEnabled=" + mSnapshotEnabled);
+        mCache.dump(pw, prefix);
+    }
+}
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..3fa5efc 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -191,6 +191,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) {
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..c63bd52 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -680,12 +680,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 937f2f1..1180df7 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -94,8 +94,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 +1878,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 +1890,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);
     }
 
@@ -3628,19 +3630,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;
     }
@@ -3754,7 +3743,7 @@
                     Slog.w(TAG, "takeTaskSnapshot: taskId=" + taskId + " not found or not visible");
                     return null;
                 }
-                return mWindowManager.mTaskSnapshotController.captureTaskSnapshot(
+                return mWindowManager.mTaskSnapshotController.captureSnapshot(
                         task, false /* snapshotHome */);
             }
         } finally {
@@ -6812,16 +6801,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..42da2a5 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -207,9 +207,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<>();
@@ -718,7 +715,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 +742,7 @@
             try {
                 return mService.getPackageManagerInternalLocked().resolveIntentExported(
                         intent, resolvedType, modifiedFlags, privateResolveFlags, userId, true,
-                        filterCallingUid);
+                        filterCallingUid, callingPid);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -755,8 +752,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);
     }
 
@@ -1526,15 +1524,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
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
similarity index 88%
rename from services/core/java/com/android/server/wm/TaskSnapshotLoader.java
rename to services/core/java/com/android/server/wm/AppSnapshotLoader.java
index 9189e51..88c4752 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotLoader.java
+++ b/services/core/java/com/android/server/wm/AppSnapshotLoader.java
@@ -11,7 +11,7 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
  */
 
 package com.android.server.wm;
@@ -31,6 +31,7 @@
 import android.util.Slog;
 import android.window.TaskSnapshot;
 
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
 
 import java.io.File;
@@ -44,14 +45,14 @@
  * <p>
  * Test class: {@link TaskSnapshotPersisterLoaderTest}
  */
-class TaskSnapshotLoader {
+class AppSnapshotLoader {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotLoader" : TAG_WM;
 
-    private final TaskSnapshotPersister mPersister;
+    private final PersistInfoProvider mPersistInfoProvider;
 
-    TaskSnapshotLoader(TaskSnapshotPersister persister) {
-        mPersister = persister;
+    AppSnapshotLoader(PersistInfoProvider persistInfoProvider) {
+        mPersistInfoProvider = persistInfoProvider;
     }
 
     static class PreRLegacySnapshotConfig {
@@ -130,21 +131,22 @@
      * Do not hold the window manager lock when calling this method, as we directly read data from
      * disk here, which might be slow.
      *
-     * @param taskId                  The id of the task to load.
+     * @param id                      The id of the snapshot to load.
      * @param userId                  The id of the user the task belonged to.
      * @param loadLowResolutionBitmap Whether to load a low resolution resolution version of the
      *                                snapshot.
      * @return The loaded {@link TaskSnapshot} or {@code null} if it couldn't be loaded.
      */
-    TaskSnapshot loadTask(int taskId, int userId, boolean loadLowResolutionBitmap) {
-        final File protoFile = mPersister.getProtoFile(taskId, userId);
+    TaskSnapshot loadTask(int id, int userId, boolean loadLowResolutionBitmap) {
+        final File protoFile = mPersistInfoProvider.getProtoFile(id, userId);
         if (!protoFile.exists()) {
             return null;
         }
         try {
             final byte[] bytes = Files.readAllBytes(protoFile.toPath());
             final TaskSnapshotProto proto = TaskSnapshotProto.parseFrom(bytes);
-            final File highResBitmap = mPersister.getHighResolutionBitmapFile(taskId, userId);
+            final File highResBitmap = mPersistInfoProvider
+                    .getHighResolutionBitmapFile(id, userId);
 
             PreRLegacySnapshotConfig legacyConfig = getLegacySnapshotConfig(proto.taskWidth,
                     proto.legacyScale, highResBitmap.exists(), loadLowResolutionBitmap);
@@ -152,16 +154,16 @@
             boolean forceLoadReducedJpeg =
                     legacyConfig != null && legacyConfig.mForceLoadReducedJpeg;
             File bitmapFile = (loadLowResolutionBitmap || forceLoadReducedJpeg)
-                    ? mPersister.getLowResolutionBitmapFile(taskId, userId) : highResBitmap;
+                    ? mPersistInfoProvider.getLowResolutionBitmapFile(id, userId)
+                    : highResBitmap;
 
             if (!bitmapFile.exists()) {
                 return null;
             }
 
             final Options options = new Options();
-            options.inPreferredConfig = mPersister.use16BitFormat() && !proto.isTranslucent
-                    ? Config.RGB_565
-                    : Config.ARGB_8888;
+            options.inPreferredConfig = mPersistInfoProvider.use16BitFormat()
+                    && !proto.isTranslucent ? Config.RGB_565 : Config.ARGB_8888;
             final Bitmap bitmap = BitmapFactory.decodeFile(bitmapFile.getPath(), options);
             if (bitmap == null) {
                 Slog.w(TAG, "Failed to load bitmap: " + bitmapFile.getPath());
@@ -201,7 +203,7 @@
                     loadLowResolutionBitmap, proto.isRealSnapshot, proto.windowingMode,
                     proto.appearance, proto.isTranslucent, false /* hasImeSurface */);
         } catch (IOException e) {
-            Slog.w(TAG, "Unable to load task snapshot data for taskId=" + taskId);
+            Slog.w(TAG, "Unable to load task snapshot data for Id=" + id);
             return null;
         }
     }
diff --git a/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
new file mode 100644
index 0000000..d604402
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BaseAppSnapshotPersister.java
@@ -0,0 +1,131 @@
+/*
+ * 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.window.TaskSnapshot;
+
+import java.io.File;
+
+class BaseAppSnapshotPersister {
+    static final String LOW_RES_FILE_POSTFIX = "_reduced";
+    static final String PROTO_EXTENSION = ".proto";
+    static final String BITMAP_EXTENSION = ".jpg";
+
+    // Shared with SnapshotPersistQueue
+    protected final Object mLock;
+    protected final SnapshotPersistQueue mSnapshotPersistQueue;
+    protected final PersistInfoProvider mPersistInfoProvider;
+
+    BaseAppSnapshotPersister(SnapshotPersistQueue persistQueue,
+            PersistInfoProvider persistInfoProvider) {
+        mSnapshotPersistQueue = persistQueue;
+        mPersistInfoProvider = persistInfoProvider;
+        mLock = persistQueue.getLock();
+    }
+
+    /**
+     * Persists a snapshot of a task to disk.
+     *
+     * @param id The id of the object that needs to be persisted.
+     * @param userId The id of the user this tasks belongs to.
+     * @param snapshot The snapshot to persist.
+     */
+    void persistSnapshot(int id, int userId, TaskSnapshot snapshot) {
+        synchronized (mLock) {
+            mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
+                    .createStoreWriteQueueItem(id, userId, snapshot, mPersistInfoProvider));
+        }
+    }
+
+    /**
+     * Called to remove the persisted file
+     *
+     * @param id The id of task that has been removed.
+     * @param userId The id of the user the task belonged to.
+     */
+    void removeSnap(int id, int userId) {
+        synchronized (mLock) {
+            mSnapshotPersistQueue.sendToQueueLocked(mSnapshotPersistQueue
+                    .createDeleteWriteQueueItem(id, userId, mPersistInfoProvider));
+        }
+    }
+
+    interface DirectoryResolver {
+        File getSystemDirectoryForUser(int userId);
+    }
+
+    /**
+     * Persist information provider, the snapshot persister and loader can know where the file is,
+     * and the scale of a snapshot, etc.
+     */
+    static class PersistInfoProvider {
+        protected final DirectoryResolver mDirectoryResolver;
+        private final String mDirName;
+        private final boolean mEnableLowResSnapshots;
+        private final float mLowResScaleFactor;
+        private final boolean mUse16BitFormat;
+
+        PersistInfoProvider(DirectoryResolver directoryResolver, String dirName,
+                boolean enableLowResSnapshots, float lowResScaleFactor, boolean use16BitFormat) {
+            mDirectoryResolver = directoryResolver;
+            mDirName = dirName;
+            mEnableLowResSnapshots = enableLowResSnapshots;
+            mLowResScaleFactor = lowResScaleFactor;
+            mUse16BitFormat = use16BitFormat;
+        }
+
+        @NonNull
+        File getDirectory(int userId) {
+            return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), mDirName);
+        }
+
+        /**
+         * Return if task snapshots are stored in 16 bit pixel format.
+         *
+         * @return true if task snapshots are stored in 16 bit pixel format.
+         */
+        boolean use16BitFormat() {
+            return mUse16BitFormat;
+        }
+
+        boolean createDirectory(int userId) {
+            final File dir = getDirectory(userId);
+            return dir.exists() || dir.mkdir();
+        }
+
+        File getProtoFile(int index, int userId) {
+            return new File(getDirectory(userId), index + PROTO_EXTENSION);
+        }
+
+        File getLowResolutionBitmapFile(int index, int userId) {
+            return new File(getDirectory(userId), index + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
+        }
+
+        File getHighResolutionBitmapFile(int index, int userId) {
+            return new File(getDirectory(userId), index + BITMAP_EXTENSION);
+        }
+
+        boolean enableLowResSnapshots() {
+            return mEnableLowResSnapshots;
+        }
+
+        float lowResScaleFactor() {
+            return mLowResScaleFactor;
+        }
+    }
+}
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/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 7fd093f..90d0f16 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -35,7 +35,6 @@
 import android.view.InsetsSource;
 import android.view.InsetsSourceConsumer;
 import android.view.InsetsSourceControl;
-import android.view.InsetsState;
 import android.view.WindowInsets;
 import android.view.inputmethod.ImeTracker;
 import android.window.TaskSnapshot;
@@ -58,7 +57,7 @@
     private Runnable mShowImeRunner;
     private boolean mIsImeLayoutDrawn;
     private boolean mImeShowing;
-    private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME);
+    private final InsetsSource mLastSource = new InsetsSource(ITYPE_IME, WindowInsets.Type.ime());
 
     /** @see #setFrozen(boolean) */
     private boolean mFrozen;
@@ -142,7 +141,7 @@
     @Override
     protected boolean updateClientVisibility(InsetsControlTarget caller) {
         boolean changed = super.updateClientVisibility(caller);
-        if (changed && caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()))) {
+        if (changed && caller.isRequestedVisible(mSource.getType())) {
             reportImeDrawnForOrganizer(caller);
         }
         return changed;
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 67cab10..35e1fbb 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,11 +26,7 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.SyncRtSurfaceTransactionApplier.applyParams;
@@ -38,8 +34,6 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -66,6 +60,7 @@
 import android.view.SyncRtSurfaceTransactionApplier;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowInsetsAnimationControlListener;
@@ -81,6 +76,10 @@
  */
 class InsetsPolicy {
 
+    public static final int CONTROLLABLE_TYPES = WindowInsets.Type.statusBars()
+            | WindowInsets.Type.navigationBars()
+            | WindowInsets.Type.ime();
+
     private final InsetsStateController mStateController;
     private final DisplayContent mDisplayContent;
     private final DisplayPolicy mPolicy;
@@ -98,8 +97,7 @@
                 return;
             }
             for (InsetsSourceControl control : controls) {
-                final @InternalInsetsType int type = control.getType();
-                if (mShowingTransientTypes.indexOf(type) != -1) {
+                if (mShowingTransientTypes.indexOf(control.getId()) != -1) {
                     // The visibilities of transient bars will be handled with animations.
                     continue;
                 }
@@ -109,8 +107,9 @@
 
                     // We use alpha to control the visibility here which aligns the logic at
                     // SurfaceAnimator.createAnimationLeash
-                    mDisplayContent.getPendingTransaction().setAlpha(
-                            leash, InsetsState.getDefaultVisibility(type) ? 1f : 0f);
+                    final boolean visible =
+                            (control.getType() & WindowInsets.Type.defaultVisible()) != 0;
+                    mDisplayContent.getPendingTransaction().setAlpha(leash, visible ? 1f : 0f);
                 }
             }
             if (hasLeash) {
@@ -278,7 +277,6 @@
      * @see WindowState#getInsetsState()
      */
     InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) {
-        final @InternalInsetsType int type = getInsetsTypeForLayoutParams(attrs);
         final WindowToken token = mDisplayContent.getWindowToken(attrs.token);
         if (token != null) {
             final InsetsState rotatedState = token.getFixedRotationTransformInsetsState();
@@ -289,109 +287,62 @@
         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
         // Always use windowing mode fullscreen when get insets for window metrics to make sure it
         // contains all insets types.
-        final InsetsState originalState = mDisplayContent.getInsetsPolicy()
-                .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
-                        attrs.type, mStateController.getRawInsetsState());
+        final InsetsState originalState = enforceInsetsPolicyForTarget(attrs,
+                WINDOWING_MODE_FULLSCREEN, alwaysOnTop, mStateController.getRawInsetsState());
         InsetsState state = adjustVisibilityForTransientTypes(originalState);
         return adjustInsetsForRoundedCorners(token, state, state == originalState);
     }
 
     /**
-     * @param type the internal type of the insets.
-     * @return {@code true} if the given type is controllable, {@code false} otherwise.
-     */
-    static boolean isInsetsTypeControllable(@InternalInsetsType int type) {
-        switch (type) {
-            case ITYPE_STATUS_BAR:
-            case ITYPE_NAVIGATION_BAR:
-            case ITYPE_IME:
-            case ITYPE_CLIMATE_BAR:
-            case ITYPE_EXTRA_NAVIGATION_BAR:
-                return true;
-            default:
-                return false;
-        }
-    }
-
-    private static @InternalInsetsType int getInsetsTypeForLayoutParams(
-            WindowManager.LayoutParams attrs) {
-        @WindowManager.LayoutParams.WindowType int type = attrs.type;
-        switch (type) {
-            case TYPE_STATUS_BAR:
-                return ITYPE_STATUS_BAR;
-            case TYPE_NAVIGATION_BAR:
-                return ITYPE_NAVIGATION_BAR;
-            case TYPE_INPUT_METHOD:
-                return ITYPE_IME;
-        }
-
-        // If not one of the types above, check whether an internal inset mapping is specified.
-        if (attrs.providedInsets != null) {
-            for (InsetsFrameProvider provider : attrs.providedInsets) {
-                switch (provider.type) {
-                    case ITYPE_STATUS_BAR:
-                    case ITYPE_NAVIGATION_BAR:
-                    case ITYPE_CLIMATE_BAR:
-                    case ITYPE_EXTRA_NAVIGATION_BAR:
-                        return provider.type;
-                }
-            }
-        }
-
-        return ITYPE_INVALID;
-    }
-
-
-    /**
-     * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
-     * the target.
-     * When performing layout of the target or dispatching insets to the target, we need to exclude
-     * sources which should not be visible to the target. e.g., the source which represents the
-     * target window itself, and the IME source when the target is above IME. We also need to
-     * exclude certain types of insets source for client within specific windowing modes.
+     * Modifies the given {@code state} according to insets provided by the target. When performing
+     * layout of the target or dispatching insets to the target, we need to exclude sources which
+     * should not be received by the target. e.g., the visible (non-gesture-wise) source provided by
+     * the target window itself.
      *
-     * @param type the inset type provided by the target
+     * We also need to exclude certain types of insets source for client within specific windowing
+     * modes.
+     *
+     * @param attrs the LayoutParams of the target
      * @param windowingMode the windowing mode of the target
      * @param isAlwaysOnTop is the target always on top
-     * @param windowType the type of the target
      * @param state the input inset state containing all the sources
      * @return The state stripped of the necessary information.
      */
-    InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
+    InsetsState enforceInsetsPolicyForTarget(WindowManager.LayoutParams attrs,
             @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
-            int windowType, InsetsState state) {
-        boolean stateCopied = false;
+            InsetsState state) {
+        final InsetsState originalState = state;
 
-        if (type != ITYPE_INVALID) {
+        // The caller should not receive the visible insets provided by itself.
+        if (attrs.type == TYPE_INPUT_METHOD) {
             state = new InsetsState(state);
-            stateCopied = true;
-            state.removeSource(type);
-
-            // Navigation bar doesn't get influenced by anything else
-            if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
-                state.removeSource(ITYPE_STATUS_BAR);
-                state.removeSource(ITYPE_CLIMATE_BAR);
-                state.removeSource(ITYPE_CAPTION_BAR);
-                state.removeSource(ITYPE_NAVIGATION_BAR);
-                state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
-            }
-
-            // Status bar doesn't get influenced by caption bar
-            if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
-                state.removeSource(ITYPE_CAPTION_BAR);
+            state.removeSource(ITYPE_IME);
+        } else if (attrs.providedInsets != null) {
+            for (InsetsFrameProvider provider : attrs.providedInsets) {
+                // TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
+                final int sourceId = provider.type;
+                final @InsetsType int type = InsetsState.toPublicType(sourceId);
+                if ((type & WindowInsets.Type.systemBars()) == 0) {
+                    continue;
+                }
+                if (state == originalState) {
+                    state = new InsetsState(state);
+                }
+                state.removeSource(sourceId);
             }
         }
-        ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
+
+        final ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
                 .getSourceProviders();
+        final int windowType = attrs.type;
         for (int i = providers.size() - 1; i >= 0; i--) {
-            WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+            final WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
             if (otherProvider.overridesFrame(windowType)) {
-                if (!stateCopied) {
+                if (state == originalState) {
                     state = new InsetsState(state);
-                    stateCopied = true;
                 }
-                InsetsSource override =
-                        new InsetsSource(state.getSource(otherProvider.getSource().getType()));
+                final InsetsSource override =
+                        new InsetsSource(state.getSource(otherProvider.getSource().getId()));
                 override.setFrame(otherProvider.getOverriddenFrame(windowType));
                 state.addSource(override);
             }
@@ -404,10 +355,9 @@
             if (windowingMode != WINDOWING_MODE_PINNED) {
                 types |= WindowInsets.Type.ime();
             }
-            InsetsState newState = new InsetsState();
+            final InsetsState newState = new InsetsState();
             newState.set(state, types);
             state = newState;
-            stateCopied = true;
         }
 
         return state;
@@ -628,7 +578,7 @@
         return focusedWin;
     }
 
-    private boolean isShowingTransientTypes(@Type.InsetsType int types) {
+    private boolean isShowingTransientTypes(@InsetsType int types) {
         final IntArray showingTransientTypes = mShowingTransientTypes;
         for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
             if ((InsetsState.toPublicType(showingTransientTypes.get(i)) & types) != 0) {
@@ -677,14 +627,15 @@
         final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
         final IntArray showingTransientTypes = mShowingTransientTypes;
         for (int i = showingTransientTypes.size() - 1; i >= 0; i--) {
-            final @InternalInsetsType int type = showingTransientTypes.get(i);
-            WindowContainerInsetsSourceProvider provider = mStateController.getSourceProvider(type);
-            InsetsSourceControl control = provider.getControl(mDummyControlTarget);
+            final int sourceId = showingTransientTypes.get(i);
+            final WindowContainerInsetsSourceProvider provider =
+                    mStateController.getSourceProvider(sourceId);
+            final InsetsSourceControl control = provider.getControl(mDummyControlTarget);
             if (control == null || control.getLeash() == null) {
                 continue;
             }
-            typesReady |= InsetsState.toPublicType(type);
-            controls.put(control.getType(), new InsetsSourceControl(control));
+            typesReady |= control.getType();
+            controls.put(sourceId, new InsetsSourceControl(control));
         }
         controlAnimationUnchecked(typesReady, controls, show, callback);
     }
@@ -733,7 +684,7 @@
         }
 
         private void updateVisibility(@Nullable InsetsControlTarget controlTarget,
-                @Type.InsetsType int type) {
+                @InsetsType int type) {
             setVisible(controlTarget == null || controlTarget.isRequestedVisible(type));
         }
 
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 5b205f0..5171f5b 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -45,7 +45,6 @@
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
-import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.WindowInsets;
@@ -121,14 +120,14 @@
 
     InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
             DisplayContent displayContent) {
-        mClientVisible = InsetsState.getDefaultVisibility(source.getType());
+        mClientVisible = (WindowInsets.Type.defaultVisible() & source.getType()) != 0;
         mSource = source;
         mDisplayContent = displayContent;
         mStateController = stateController;
         mFakeControl = new InsetsSourceControl(
-                source.getType(), null /* leash */, false /* initialVisible */, new Point(),
-                Insets.NONE);
-        mControllable = InsetsPolicy.isInsetsTypeControllable(source.getType());
+                source.getId(), source.getType(), null /* leash */, false /* initialVisible */,
+                new Point(), Insets.NONE);
+        mControllable = (InsetsPolicy.CONTROLLABLE_TYPES & source.getType()) != 0;
     }
 
     InsetsSource getSource() {
@@ -166,11 +165,11 @@
             // TODO: Ideally, we should wait for the animation to finish so previous window can
             // animate-out as new one animates-in.
             mWindowContainer.cancelAnimation();
-            mWindowContainer.getProvidedInsetsSources().remove(mSource.getType());
+            mWindowContainer.getProvidedInsetsSources().remove(mSource.getId());
             mSeamlessRotating = false;
         }
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
-                windowContainer, InsetsState.typeToString(mSource.getType()));
+                windowContainer, WindowInsets.Type.toString(mSource.getType()));
         mWindowContainer = windowContainer;
         // TODO: remove the frame provider for non-WindowState container.
         mFrameProvider = frameProvider;
@@ -182,7 +181,7 @@
             mSource.setInsetsRoundedCornerFrame(false);
             mSourceFrame.setEmpty();
         } else {
-            mWindowContainer.getProvidedInsetsSources().put(mSource.getType(), mSource);
+            mWindowContainer.getProvidedInsetsSources().put(mSource.getId(), mSource);
             if (mControllable) {
                 mWindowContainer.setControllableInsetProvider(this);
                 if (mPendingControlTarget != null) {
@@ -284,7 +283,7 @@
     InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
         // Don't copy visible frame because it might not be calculated in the provided display
         // frames and it is not significant for this usage.
-        final InsetsSource source = new InsetsSource(mSource.getType());
+        final InsetsSource source = new InsetsSource(mSource.getId(), mSource.getType());
         source.setVisible(mSource.isVisible());
         mTmpRect.set(frame);
         if (mFrameProvider != null) {
@@ -454,13 +453,12 @@
         if (target == null) {
             // Cancelling the animation will invoke onAnimationCancelled, resetting all the fields.
             mWindowContainer.cancelAnimation();
-            setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
+            setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0);
             return;
         }
         final Point surfacePosition = getWindowFrameSurfacePosition();
         mAdapter = new ControlAdapter(surfacePosition);
-        final int type = getSource().getType();
-        if (type == ITYPE_IME) {
+        if (mSource.getType() == WindowInsets.Type.ime()) {
             setClientVisible(target.isRequestedVisible(WindowInsets.Type.ime()));
         }
         final Transaction t = mDisplayContent.getSyncTransaction();
@@ -474,8 +472,8 @@
         final SurfaceControl leash = mAdapter.mCapturedLeash;
         mControlTarget = target;
         updateVisibility();
-        mControl = new InsetsSourceControl(type, leash, mClientVisible, surfacePosition,
-                mInsetsHint);
+        mControl = new InsetsSourceControl(mSource.getId(), mSource.getType(), leash,
+                mClientVisible, surfacePosition, mInsetsHint);
 
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
                 "InsetsSource Control %s for target %s", mControl, mControlTarget);
@@ -493,8 +491,7 @@
     }
 
     boolean updateClientVisibility(InsetsControlTarget caller) {
-        final boolean requestedVisible =
-                caller.isRequestedVisible(InsetsState.toPublicType(mSource.getType()));
+        final boolean requestedVisible = caller.isRequestedVisible(mSource.getType());
         if (caller != mControlTarget || requestedVisible == mClientVisible) {
             return false;
         }
@@ -530,7 +527,7 @@
         mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
                 "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
-                InsetsState.typeToString(mSource.getType()),
+                WindowInsets.Type.toString(mSource.getType()),
                 mServerVisible, mClientVisible);
     }
 
@@ -560,9 +557,9 @@
                 // The surface transaction of preparing leash is not applied yet. We don't send it
                 // to the client in case that the client applies its transaction sooner than ours
                 // that we could unexpectedly overwrite the surface state.
-                return new InsetsSourceControl(mControl.getType(), null /* leash */,
-                        mControl.isInitiallyVisible(), mControl.getSurfacePosition(),
-                        mControl.getInsetsHint());
+                return new InsetsSourceControl(mControl.getId(), mControl.getType(),
+                        null /* leash */, mControl.isInitiallyVisible(),
+                        mControl.getSurfacePosition(), mControl.getInsetsHint());
             }
             return mControl;
         }
@@ -673,7 +670,7 @@
         public void startAnimation(SurfaceControl animationLeash, Transaction t,
                 @AnimationType int type, @NonNull OnAnimationFinishedCallback finishCallback) {
             // TODO(b/166736352): Check if we still need to control the IME visibility here.
-            if (mSource.getType() == ITYPE_IME) {
+            if (mSource.getType() == WindowInsets.Type.ime()) {
                 // TODO: use 0 alpha and remove t.hide() once b/138459974 is fixed.
                 t.setAlpha(animationLeash, 1 /* alpha */);
                 t.hide(animationLeash);
@@ -698,7 +695,7 @@
                 mControl = null;
                 mControlTarget = null;
                 mAdapter = null;
-                setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
+                setClientVisible((WindowInsets.Type.defaultVisible() & mSource.getType()) != 0);
                 ProtoLog.i(WM_DEBUG_WINDOW_INSETS,
                         "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s",
                         mSource, mControlTarget);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index ac1fbc3..455cd48 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -38,6 +38,7 @@
 import android.view.InsetsSourceControl;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
+import android.view.WindowInsets;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -80,7 +81,7 @@
                 return;
             }
             for (InsetsSourceControl control : controls) {
-                if (control.getType() == ITYPE_IME) {
+                if (control.getType() == WindowInsets.Type.ime()) {
                     mDisplayContent.mWmService.mH.post(() ->
                             InputMethodManagerInternal.get().removeImeSurface());
                 }
@@ -246,7 +247,7 @@
 
     void notifyControlRevoked(@NonNull InsetsControlTarget previousControlTarget,
             InsetsSourceProvider provider) {
-        removeFromControlMaps(previousControlTarget, provider.getSource().getType(),
+        removeFromControlMaps(previousControlTarget, provider.getSource().getId(),
                 false /* fake */);
     }
 
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/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index bffab7a..57fca3ae 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -904,7 +904,7 @@
         for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
             final TaskAnimationAdapter adapter = mPendingAnimations.get(i);
             final Task task = adapter.mTask;
-            snapshotController.recordTaskSnapshot(task, false /* allowSnapshotHome */);
+            snapshotController.recordSnapshot(task, false /* allowSnapshotHome */);
             final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId,
                     false /* restoreFromDisk */, false /* isLowResolution */);
             if (snapshot != null) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 585109b..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;
                 }
@@ -2092,7 +2096,7 @@
                 // Record the snapshot now, it will be later fetched for content-pip animation.
                 // We do this early in the process to make sure the right snapshot is used for
                 // entering content-pip animation.
-                mWindowManager.mTaskSnapshotController.recordTaskSnapshot(
+                mWindowManager.mTaskSnapshotController.recordSnapshot(
                         task, false /* allowSnapshotHome */);
                 rootTask.setBounds(r.pictureInPictureArgs.getSourceRectHint());
             }
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/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
new file mode 100644
index 0000000..fdc3616
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -0,0 +1,374 @@
+/*
+ * 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.graphics.Bitmap.CompressFormat.JPEG;
+
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.graphics.Bitmap;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.AtomicFile;
+import android.util.Slog;
+import android.window.TaskSnapshot;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
+import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayDeque;
+
+/**
+ * Singleton worker thread to queue up persist or delete tasks of {@link TaskSnapshot}s to disk.
+ */
+class SnapshotPersistQueue {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
+    private static final long DELAY_MS = 100;
+    private static final int MAX_STORE_QUEUE_DEPTH = 2;
+    private static final int COMPRESS_QUALITY = 95;
+
+    @GuardedBy("mLock")
+    private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
+    @GuardedBy("mLock")
+    private boolean mQueueIdling;
+    @GuardedBy("mLock")
+    private boolean mPaused;
+    private boolean mStarted;
+    private final Object mLock = new Object();
+    private final UserManagerInternal mUserManagerInternal;
+
+    SnapshotPersistQueue() {
+        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
+    }
+
+    Object getLock() {
+        return mLock;
+    }
+
+    void systemReady() {
+        start();
+    }
+
+    /**
+     * Starts persisting.
+     */
+    void start() {
+        if (!mStarted) {
+            mStarted = true;
+            mPersister.start();
+        }
+    }
+
+    /**
+     * Temporarily pauses/unpauses persisting of task snapshots.
+     *
+     * @param paused Whether task snapshot persisting should be paused.
+     */
+    void setPaused(boolean paused) {
+        synchronized (mLock) {
+            mPaused = paused;
+            if (!paused) {
+                mLock.notifyAll();
+            }
+        }
+    }
+
+    @TestApi
+    void waitForQueueEmpty() {
+        while (true) {
+            synchronized (mLock) {
+                if (mWriteQueue.isEmpty() && mQueueIdling) {
+                    return;
+                }
+            }
+            SystemClock.sleep(DELAY_MS);
+        }
+    }
+
+    @GuardedBy("mLock")
+    void sendToQueueLocked(WriteQueueItem item) {
+        mWriteQueue.offer(item);
+        item.onQueuedLocked();
+        ensureStoreQueueDepthLocked();
+        if (!mPaused) {
+            mLock.notifyAll();
+        }
+    }
+
+    @GuardedBy("mLock")
+    private void ensureStoreQueueDepthLocked() {
+        while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
+            final StoreWriteQueueItem item = mStoreQueueItems.poll();
+            mWriteQueue.remove(item);
+            Slog.i(TAG, "Queue is too deep! Purged item with index=" + item.mId);
+        }
+    }
+
+    private void deleteSnapshot(int index, int userId, PersistInfoProvider provider) {
+        final File protoFile = provider.getProtoFile(index, userId);
+        final File bitmapLowResFile = provider.getLowResolutionBitmapFile(index, userId);
+        protoFile.delete();
+        if (bitmapLowResFile.exists()) {
+            bitmapLowResFile.delete();
+        }
+        final File bitmapFile = provider.getHighResolutionBitmapFile(index, userId);
+        if (bitmapFile.exists()) {
+            bitmapFile.delete();
+        }
+    }
+
+    private final Thread mPersister = new Thread("TaskSnapshotPersister") {
+        public void run() {
+            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+            while (true) {
+                WriteQueueItem next;
+                boolean isReadyToWrite = false;
+                synchronized (mLock) {
+                    if (mPaused) {
+                        next = null;
+                    } else {
+                        next = mWriteQueue.poll();
+                        if (next != null) {
+                            if (next.isReady()) {
+                                isReadyToWrite = true;
+                                next.onDequeuedLocked();
+                            } else {
+                                mWriteQueue.addLast(next);
+                            }
+                        }
+                    }
+                }
+                if (next != null) {
+                    if (isReadyToWrite) {
+                        next.write();
+                    }
+                    SystemClock.sleep(DELAY_MS);
+                }
+                synchronized (mLock) {
+                    final boolean writeQueueEmpty = mWriteQueue.isEmpty();
+                    if (!writeQueueEmpty && !mPaused) {
+                        continue;
+                    }
+                    try {
+                        mQueueIdling = writeQueueEmpty;
+                        mLock.wait();
+                        mQueueIdling = false;
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }
+        }
+    };
+
+    abstract static class WriteQueueItem {
+        protected final PersistInfoProvider mPersistInfoProvider;
+        WriteQueueItem(@NonNull PersistInfoProvider persistInfoProvider) {
+            mPersistInfoProvider = persistInfoProvider;
+        }
+        /**
+         * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
+         */
+        boolean isReady() {
+            return true;
+        }
+
+        abstract void write();
+
+        /**
+         * Called when this queue item has been put into the queue.
+         */
+        void onQueuedLocked() {
+        }
+
+        /**
+         * Called when this queue item has been taken out of the queue.
+         */
+        void onDequeuedLocked() {
+        }
+    }
+
+    StoreWriteQueueItem createStoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
+            PersistInfoProvider provider) {
+        return new StoreWriteQueueItem(id, userId, snapshot, provider);
+    }
+
+    class StoreWriteQueueItem extends WriteQueueItem {
+        private final int mId;
+        private final int mUserId;
+        private final TaskSnapshot mSnapshot;
+
+        StoreWriteQueueItem(int id, int userId, TaskSnapshot snapshot,
+                PersistInfoProvider provider) {
+            super(provider);
+            mId = id;
+            mUserId = userId;
+            mSnapshot = snapshot;
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        void onQueuedLocked() {
+            mStoreQueueItems.offer(this);
+        }
+
+        @GuardedBy("mLock")
+        @Override
+        void onDequeuedLocked() {
+            mStoreQueueItems.remove(this);
+        }
+
+        @Override
+        boolean isReady() {
+            return mUserManagerInternal.isUserUnlocked(mUserId);
+        }
+
+        @Override
+        void write() {
+            if (!mPersistInfoProvider.createDirectory(mUserId)) {
+                Slog.e(TAG, "Unable to create snapshot directory for user dir="
+                        + mPersistInfoProvider.getDirectory(mUserId));
+            }
+            boolean failed = false;
+            if (!writeProto()) {
+                failed = true;
+            }
+            if (!writeBuffer()) {
+                failed = true;
+            }
+            if (failed) {
+                deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+            }
+        }
+
+        boolean writeProto() {
+            final TaskSnapshotProto proto = new TaskSnapshotProto();
+            proto.orientation = mSnapshot.getOrientation();
+            proto.rotation = mSnapshot.getRotation();
+            proto.taskWidth = mSnapshot.getTaskSize().x;
+            proto.taskHeight = mSnapshot.getTaskSize().y;
+            proto.insetLeft = mSnapshot.getContentInsets().left;
+            proto.insetTop = mSnapshot.getContentInsets().top;
+            proto.insetRight = mSnapshot.getContentInsets().right;
+            proto.insetBottom = mSnapshot.getContentInsets().bottom;
+            proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
+            proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
+            proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
+            proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
+            proto.isRealSnapshot = mSnapshot.isRealSnapshot();
+            proto.windowingMode = mSnapshot.getWindowingMode();
+            proto.appearance = mSnapshot.getAppearance();
+            proto.isTranslucent = mSnapshot.isTranslucent();
+            proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
+            proto.id = mSnapshot.getId();
+            final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
+            final File file = mPersistInfoProvider.getProtoFile(mId, mUserId);
+            final AtomicFile atomicFile = new AtomicFile(file);
+            FileOutputStream fos = null;
+            try {
+                fos = atomicFile.startWrite();
+                fos.write(bytes);
+                atomicFile.finishWrite(fos);
+            } catch (IOException e) {
+                atomicFile.failWrite(fos);
+                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
+                return false;
+            }
+            return true;
+        }
+
+        boolean writeBuffer() {
+            if (AbsAppSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) {
+                Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mId);
+                return false;
+            }
+            final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
+                    mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
+            if (bitmap == null) {
+                Slog.e(TAG, "Invalid task snapshot hw bitmap");
+                return false;
+            }
+
+            final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+
+            final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
+            try {
+                FileOutputStream fos = new FileOutputStream(file);
+                swBitmap.compress(JPEG, COMPRESS_QUALITY, fos);
+                fos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
+                return false;
+            }
+
+            if (!mPersistInfoProvider.enableLowResSnapshots()) {
+                swBitmap.recycle();
+                return true;
+            }
+
+            final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
+                    (int) (bitmap.getWidth() * mPersistInfoProvider.lowResScaleFactor()),
+                    (int) (bitmap.getHeight() * mPersistInfoProvider.lowResScaleFactor()),
+                    true /* filter */);
+            swBitmap.recycle();
+
+            final File lowResFile = mPersistInfoProvider.getLowResolutionBitmapFile(mId, mUserId);
+            try {
+                FileOutputStream lowResFos = new FileOutputStream(lowResFile);
+                lowResBitmap.compress(JPEG, COMPRESS_QUALITY, lowResFos);
+                lowResFos.close();
+            } catch (IOException e) {
+                Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
+                return false;
+            }
+            lowResBitmap.recycle();
+
+            return true;
+        }
+    }
+
+    DeleteWriteQueueItem createDeleteWriteQueueItem(int id, int userId,
+            PersistInfoProvider provider) {
+        return new DeleteWriteQueueItem(id, userId, provider);
+    }
+
+    private class DeleteWriteQueueItem extends WriteQueueItem {
+        private final int mId;
+        private final int mUserId;
+
+        DeleteWriteQueueItem(int id, int userId, PersistInfoProvider provider) {
+            super(provider);
+            mId = id;
+            mUserId = userId;
+        }
+
+        @Override
+        void write() {
+            deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+        }
+    }
+}
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/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index 66b7342..0457408 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -1074,10 +1074,14 @@
         // Use launch-adjacent-flag-root if launching with launch-adjacent flag.
         if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
                 && mLaunchAdjacentFlagRootTask != null) {
-            // If the adjacent launch is coming from the same root, launch to adjacent root instead.
-            if (sourceTask != null && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
+            if (sourceTask != null && sourceTask == candidateTask) {
+                // Do nothing when task that is getting opened is same as the source.
+            } else if (sourceTask != null
+                    && mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment() != null
                     && (sourceTask == mLaunchAdjacentFlagRootTask
                     || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) {
+                // If the adjacent launch is coming from the same root, launch to
+                // adjacent root instead.
                 return mLaunchAdjacentFlagRootTask.getAdjacentTaskFragment().asTask();
             } else {
                 return mLaunchAdjacentFlagRootTask;
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/TaskSnapshotCache.java b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
index 3c437ba..55e863e 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotCache.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotCache.java
@@ -18,38 +18,28 @@
 
 import android.annotation.Nullable;
 import android.window.TaskSnapshot;
-import android.util.ArrayMap;
-
-import java.io.PrintWriter;
 
 /**
  * Caches snapshots. See {@link TaskSnapshotController}.
  * <p>
  * Access to this class should be guarded by the global window manager lock.
  */
-class TaskSnapshotCache {
+class TaskSnapshotCache extends AbsAppSnapshotCache<Task> {
 
-    private final WindowManagerService mService;
-    private final TaskSnapshotLoader mLoader;
-    private final ArrayMap<ActivityRecord, Integer> mAppTaskMap = new ArrayMap<>();
-    private final ArrayMap<Integer, CacheEntry> mRunningCache = new ArrayMap<>();
+    private final AppSnapshotLoader mLoader;
 
-    TaskSnapshotCache(WindowManagerService service, TaskSnapshotLoader loader) {
-        mService = service;
+    TaskSnapshotCache(WindowManagerService service, AppSnapshotLoader loader) {
+        super(service, "Task");
         mLoader = loader;
     }
 
-    void clearRunningCache() {
-        mRunningCache.clear();
-    }
-
     void putSnapshot(Task task, TaskSnapshot snapshot) {
         final CacheEntry entry = mRunningCache.get(task.mTaskId);
         if (entry != null) {
-            mAppTaskMap.remove(entry.topApp);
+            mAppIdMap.remove(entry.topApp);
         }
         final ActivityRecord top = task.getTopMostActivity();
-        mAppTaskMap.put(top, task.mTaskId);
+        mAppIdMap.put(top, task.mTaskId);
         mRunningCache.put(task.mTaskId, new CacheEntry(snapshot, top));
     }
 
@@ -58,13 +48,9 @@
      */
     @Nullable TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
             boolean isLowResolution) {
-
-        synchronized (mService.mGlobalLock) {
-            // Try the running cache.
-            final CacheEntry entry = mRunningCache.get(taskId);
-            if (entry != null) {
-                return entry.snapshot;
-            }
+        final TaskSnapshot snapshot = getSnapshot(taskId);
+        if (snapshot != null) {
+            return snapshot;
         }
 
         // Try to restore from disk if asked.
@@ -78,68 +64,6 @@
      * DO NOT HOLD THE WINDOW MANAGER LOCK WHEN CALLING THIS METHOD!
      */
     private TaskSnapshot tryRestoreFromDisk(int taskId, int userId, boolean isLowResolution) {
-        final TaskSnapshot snapshot = mLoader.loadTask(taskId, userId, isLowResolution);
-        if (snapshot == null) {
-            return null;
-        }
-        return snapshot;
-    }
-
-    /**
-     * Called when an app token has been removed
-     */
-    void onAppRemoved(ActivityRecord activity) {
-        final Integer taskId = mAppTaskMap.get(activity);
-        if (taskId != null) {
-            removeRunningEntry(taskId);
-        }
-    }
-
-    /**
-     * Callend when an app window token's process died.
-     */
-    void onAppDied(ActivityRecord activity) {
-        final Integer taskId = mAppTaskMap.get(activity);
-        if (taskId != null) {
-            removeRunningEntry(taskId);
-        }
-    }
-
-    void onTaskRemoved(int taskId) {
-        removeRunningEntry(taskId);
-    }
-
-    void removeRunningEntry(int taskId) {
-        final CacheEntry entry = mRunningCache.get(taskId);
-        if (entry != null) {
-            mAppTaskMap.remove(entry.topApp);
-            mRunningCache.remove(taskId);
-        }
-    }
-
-    void dump(PrintWriter pw, String prefix) {
-        final String doublePrefix = prefix + "  ";
-        final String triplePrefix = doublePrefix + "  ";
-        pw.println(prefix + "SnapshotCache");
-        for (int i = mRunningCache.size() - 1; i >= 0; i--) {
-            final CacheEntry entry = mRunningCache.valueAt(i);
-            pw.println(doublePrefix + "Entry taskId=" + mRunningCache.keyAt(i));
-            pw.println(triplePrefix + "topApp=" + entry.topApp);
-            pw.println(triplePrefix + "snapshot=" + entry.snapshot);
-        }
-    }
-
-    private static final class CacheEntry {
-
-        /** The snapshot. */
-        final TaskSnapshot snapshot;
-
-        /** The app token that was on top of the task when the snapshot was taken */
-        final ActivityRecord topApp;
-
-        CacheEntry(TaskSnapshot snapshot, ActivityRecord topApp) {
-            this.snapshot = snapshot;
-            this.topApp = topApp;
-        }
+        return mLoader.loadTask(taskId, userId, isLowResolution);
     }
 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c1b9e662..2037fdc 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -17,44 +17,27 @@
 package com.android.server.wm;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.pm.PackageManager;
-import android.graphics.Bitmap;
+import android.app.ActivityManager;
 import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.RecordingCanvas;
 import android.graphics.Rect;
-import android.graphics.RenderNode;
-import android.hardware.HardwareBuffer;
 import android.os.Environment;
 import android.os.Handler;
-import android.os.Trace;
 import android.util.ArraySet;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.ThreadedRenderer;
-import android.view.WindowInsets.Type;
-import android.view.WindowInsetsController.Appearance;
-import android.view.WindowManager.LayoutParams;
 import android.window.ScreenCapture;
-import android.window.SnapshotDrawerUtils;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
-import com.android.server.wm.utils.InsetUtils;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 
 import com.google.android.collect.Sets;
 
-import java.io.PrintWriter;
 import java.util.Set;
 
 /**
@@ -70,75 +53,60 @@
  * <p>
  * To access this class, acquire the global window manager lock.
  */
-class TaskSnapshotController {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotController" : TAG_WM;
+class TaskSnapshotController extends AbsAppSnapshotController<Task, TaskSnapshotCache> {
+    static final String SNAPSHOTS_DIRNAME = "snapshots";
 
-    /**
-     * Return value for {@link #getSnapshotMode}: We are allowed to take a real screenshot to be
-     * used as the snapshot.
-     */
-    @VisibleForTesting
-    static final int SNAPSHOT_MODE_REAL = 0;
-
-    /**
-     * Return value for {@link #getSnapshotMode}: We are not allowed to take a real screenshot but
-     * we should try to use the app theme to create a fake representation of the app.
-     */
-    @VisibleForTesting
-    static final int SNAPSHOT_MODE_APP_THEME = 1;
-
-    /**
-     * Return value for {@link #getSnapshotMode}: We aren't allowed to take any snapshot.
-     */
-    @VisibleForTesting
-    static final int SNAPSHOT_MODE_NONE = 2;
-
-    private final WindowManagerService mService;
-
-    private final TaskSnapshotCache mCache;
     private final TaskSnapshotPersister mPersister;
-    private final TaskSnapshotLoader mLoader;
     private final ArraySet<Task> mSkipClosingAppSnapshotTasks = new ArraySet<>();
     private final ArraySet<Task> mTmpTasks = new ArraySet<>();
     private final Handler mHandler = new Handler();
-    private final float mHighResTaskSnapshotScale;
 
-    private final Rect mTmpRect = new Rect();
+    private final PersistInfoProvider mPersistInfoProvider;
 
-    /**
-     * Flag indicating whether we are running on an Android TV device.
-     */
-    private final boolean mIsRunningOnTv;
+    TaskSnapshotController(WindowManagerService service, SnapshotPersistQueue persistQueue) {
+        super(service);
+        mPersistInfoProvider = createPersistInfoProvider(service,
+                Environment::getDataSystemCeDirectory);
+        mPersister = new TaskSnapshotPersister(persistQueue, mPersistInfoProvider);
 
-    /**
-     * Flag indicating whether we are running on an IoT device.
-     */
-    private final boolean mIsRunningOnIoT;
-
-    /**
-     * Flag indicating if task snapshot is enabled on this device.
-     */
-    private boolean mTaskSnapshotEnabled;
-
-    TaskSnapshotController(WindowManagerService service) {
-        mService = service;
-        mPersister = new TaskSnapshotPersister(mService, Environment::getDataSystemCeDirectory);
-        mLoader = new TaskSnapshotLoader(mPersister);
-        mCache = new TaskSnapshotCache(mService, mLoader);
-        mIsRunningOnTv = mService.mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LEANBACK);
-        mIsRunningOnIoT = mService.mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_EMBEDDED);
-        mHighResTaskSnapshotScale = mService.mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
-        mTaskSnapshotEnabled =
-                !mService.mContext
+        initialize(new TaskSnapshotCache(service, new AppSnapshotLoader(mPersistInfoProvider)));
+        final boolean snapshotEnabled =
+                !service.mContext
                         .getResources()
                         .getBoolean(com.android.internal.R.bool.config_disableTaskSnapshots);
+        setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        mPersister.start();
+    static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
+            BaseAppSnapshotPersister.DirectoryResolver resolver) {
+        final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
+        final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
+                com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
+
+        if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("Low-res scale must be between 0 and 1");
+        }
+        if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be between 0 and 1");
+        }
+        if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
+            throw new RuntimeException("High-res scale must be greater than low-res scale");
+        }
+
+        final float lowResScaleFactor;
+        final boolean enableLowResSnapshots;
+        if (lowResTaskSnapshotScale > 0) {
+            lowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
+            enableLowResSnapshots = true;
+        } else {
+            lowResScaleFactor = 0;
+            enableLowResSnapshots = false;
+        }
+        final boolean use16BitFormat = service.mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
+        return new PersistInfoProvider(resolver, SNAPSHOTS_DIRNAME,
+                enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
     }
 
     void onTransitionStarting(DisplayContent displayContent) {
@@ -182,60 +150,18 @@
         snapshotTasks(tasks, false /* allowSnapshotHome */);
     }
 
-    /**
-     * This is different than {@link #recordTaskSnapshot(Task, boolean)} because it doesn't store
-     * the snapshot to the cache and returns the TaskSnapshot immediately.
-     *
-     * This is only used for testing so the snapshot content can be verified.
-     */
-    @VisibleForTesting
-    TaskSnapshot captureTaskSnapshot(Task task, boolean snapshotHome) {
-        final TaskSnapshot snapshot;
-        if (snapshotHome) {
-            snapshot = snapshotTask(task);
-        } else {
-            switch (getSnapshotMode(task)) {
-                case SNAPSHOT_MODE_NONE:
-                    return null;
-                case SNAPSHOT_MODE_APP_THEME:
-                    snapshot = drawAppThemeSnapshot(task);
-                    break;
-                case SNAPSHOT_MODE_REAL:
-                    snapshot = snapshotTask(task);
-                    break;
-                default:
-                    snapshot = null;
-                    break;
-            }
-        }
-        return snapshot;
-    }
-
-    void recordTaskSnapshot(Task task, boolean allowSnapshotHome) {
+    void recordSnapshot(Task task, boolean allowSnapshotHome) {
         final boolean snapshotHome = allowSnapshotHome && task.isActivityTypeHome();
-        final TaskSnapshot snapshot = captureTaskSnapshot(task, snapshotHome);
-        if (snapshot == null) {
-            return;
-        }
-
-        final HardwareBuffer buffer = snapshot.getHardwareBuffer();
-        if (buffer.getWidth() == 0 || buffer.getHeight() == 0) {
-            buffer.close();
-            Slog.e(TAG, "Invalid task snapshot dimensions " + buffer.getWidth() + "x"
-                    + buffer.getHeight());
-        } else {
-            mCache.putSnapshot(task, snapshot);
-            // Don't persist or notify the change for the temporal snapshot.
-            if (!snapshotHome) {
-                mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
-                task.onSnapshotChanged(snapshot);
-            }
+        final TaskSnapshot snapshot = recordSnapshotInner(task, allowSnapshotHome);
+        if (!snapshotHome && snapshot != null) {
+            mPersister.persistSnapshot(task.mTaskId, task.mUserId, snapshot);
+            task.onSnapshotChanged(snapshot);
         }
     }
 
     private void snapshotTasks(ArraySet<Task> tasks, boolean allowSnapshotHome) {
         for (int i = tasks.size() - 1; i >= 0; i--) {
-            recordTaskSnapshot(tasks.valueAt(i), allowSnapshotHome);
+            recordSnapshot(tasks.valueAt(i), allowSnapshotHome);
         }
     }
 
@@ -247,7 +173,7 @@
     TaskSnapshot getSnapshot(int taskId, int userId, boolean restoreFromDisk,
             boolean isLowResolution) {
         return mCache.getSnapshot(taskId, userId, restoreFromDisk, isLowResolution
-                && mPersister.enableLowResSnapshots());
+                && mPersistInfoProvider.enableLowResSnapshots());
     }
 
     /**
@@ -262,7 +188,7 @@
      * we're looking for, but during app transitions, trampoline activities can appear in the
      * children, which should be ignored.
      */
-    @Nullable private ActivityRecord findAppTokenForSnapshot(Task task) {
+    @Nullable protected ActivityRecord findAppTokenForSnapshot(Task task) {
         return task.getActivity((r) -> {
             if (r == null || !r.isSurfaceShowing() || r.findMainWindow() == null) {
                 return false;
@@ -277,112 +203,10 @@
         });
     }
 
-    /**
-     * Validates the state of the Task is appropriate to capture a snapshot, collects
-     * information from the task and populates the builder.
-     *
-     * @param task the task to capture
-     * @param pixelFormat the desired pixel format, or {@link PixelFormat#UNKNOWN} to
-     *                    automatically select
-     * @param builder the snapshot builder to populate
-     *
-     * @return true if the state of the task is ok to proceed
-     */
-    @VisibleForTesting
-    boolean prepareTaskSnapshot(Task task, int pixelFormat, TaskSnapshot.Builder builder) {
-        final Pair<ActivityRecord, WindowState> result = checkIfReadyToSnapshot(task);
-        if (result == null) {
-            return false;
-        }
-        final ActivityRecord activity = result.first;
-        final WindowState mainWindow = result.second;
-        final Rect contentInsets = getSystemBarInsets(mainWindow.getFrame(),
-                mainWindow.getInsetsStateWithVisibilityOverride());
-        final Rect letterboxInsets = activity.getLetterboxInsets();
-        InsetUtils.addInsets(contentInsets, letterboxInsets);
 
-        builder.setIsRealSnapshot(true);
-        builder.setId(System.currentTimeMillis());
-        builder.setContentInsets(contentInsets);
-        builder.setLetterboxInsets(letterboxInsets);
-
-        final boolean isWindowTranslucent = mainWindow.getAttrs().format != PixelFormat.OPAQUE;
-        final boolean isShowWallpaper = mainWindow.hasWallpaper();
-
-        if (pixelFormat == PixelFormat.UNKNOWN) {
-            pixelFormat = mPersister.use16BitFormat() && activity.fillsParent()
-                    && !(isWindowTranslucent && isShowWallpaper)
-                    ? PixelFormat.RGB_565
-                    : PixelFormat.RGBA_8888;
-        }
-
-        final boolean isTranslucent = PixelFormat.formatHasAlpha(pixelFormat)
-                && (!activity.fillsParent() || isWindowTranslucent);
-
-        builder.setTopActivityComponent(activity.mActivityComponent);
-        builder.setPixelFormat(pixelFormat);
-        builder.setIsTranslucent(isTranslucent);
-        builder.setOrientation(activity.getTask().getConfiguration().orientation);
-        builder.setRotation(activity.getTask().getDisplayContent().getRotation());
-        builder.setWindowingMode(task.getWindowingMode());
-        builder.setAppearance(getAppearance(task));
-        return true;
-    }
-
-    /**
-     * Check if the state of the Task is appropriate to capture a snapshot, such like the task
-     * snapshot or the associated IME surface snapshot.
-     *
-     * @param task the target task to capture the snapshot
-     * @return Pair of (the top activity of the task, the main window of the task) if passed the
-     * state checking. Returns {@code null} if the task state isn't ready to snapshot.
-     */
-    Pair<ActivityRecord, WindowState> checkIfReadyToSnapshot(Task task) {
-        if (!mService.mPolicy.isScreenOn()) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
-            }
-            return null;
-        }
-        final ActivityRecord activity = findAppTokenForSnapshot(task);
-        if (activity == null) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot. No visible windows for " + task);
-            }
-            return null;
-        }
-        if (activity.hasCommittedReparentToAnimationLeash()) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot. App is animating " + activity);
-            }
-            return null;
-        }
-
-        final WindowState mainWindow = activity.findMainWindow();
-        if (mainWindow == null) {
-            Slog.w(TAG_WM, "Failed to take screenshot. No main window for " + task);
-            return null;
-        }
-        if (activity.hasFixedRotationTransform()) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Skip taking screenshot. App has fixed rotation " + activity);
-            }
-            // The activity is in a temporal state that it has different rotation than the task.
-            return null;
-        }
-        return new Pair<>(activity, mainWindow);
-    }
-
-    @Nullable
-    ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
-            TaskSnapshot.Builder builder) {
-        Point taskSize = new Point();
-        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createTaskSnapshot");
-        final ScreenCapture.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
-                mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
-        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
-        builder.setTaskSize(taskSize);
-        return taskSnapshot;
+    @Override
+    protected boolean use16BitFormat() {
+        return mPersistInfoProvider.use16BitFormat();
     }
 
     @Nullable
@@ -416,101 +240,25 @@
         if (checkIfReadyToSnapshot(task) == null) {
             return null;
         }
-        final int pixelFormat = mPersister.use16BitFormat()
+        final int pixelFormat = mPersistInfoProvider.use16BitFormat()
                     ? PixelFormat.RGB_565
                     : PixelFormat.RGBA_8888;
         return createImeSnapshot(task, pixelFormat);
     }
 
-    @Nullable
-    ScreenCapture.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
-            float scaleFraction, int pixelFormat, Point outTaskSize, TaskSnapshot.Builder builder) {
-        if (task.getSurfaceControl() == null) {
-            if (DEBUG_SCREENSHOT) {
-                Slog.w(TAG_WM, "Failed to take screenshot. No surface control for " + task);
-            }
-            return null;
-        }
-        task.getBounds(mTmpRect);
-        mTmpRect.offsetTo(0, 0);
-
-        SurfaceControl[] excludeLayers;
-        final WindowState imeWindow = task.getDisplayContent().mInputMethodWindow;
-        // Exclude IME window snapshot when IME isn't proper to attach to app.
-        final boolean excludeIme = imeWindow != null && imeWindow.getSurfaceControl() != null
-                && !task.getDisplayContent().shouldImeAttachedToApp();
-        final WindowState navWindow =
-                task.getDisplayContent().getDisplayPolicy().getNavigationBar();
-        // If config_attachNavBarToAppDuringTransition is true, the nav bar will be reparent to the
-        // the swiped app when entering recent app, therefore the task will contain the navigation
-        // bar and we should exclude it from snapshot.
-        final boolean excludeNavBar = navWindow != null;
-        if (excludeIme && excludeNavBar) {
-            excludeLayers = new SurfaceControl[2];
-            excludeLayers[0] = imeWindow.getSurfaceControl();
-            excludeLayers[1] = navWindow.getSurfaceControl();
-        } else if (excludeIme || excludeNavBar) {
-            excludeLayers = new SurfaceControl[1];
-            excludeLayers[0] =
-                    excludeIme ? imeWindow.getSurfaceControl() : navWindow.getSurfaceControl();
-        } else {
-            excludeLayers = new SurfaceControl[0];
-        }
-        builder.setHasImeSurface(!excludeIme && imeWindow != null && imeWindow.isVisible());
-
-        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                ScreenCapture.captureLayersExcluding(
-                        task.getSurfaceControl(), mTmpRect, scaleFraction,
-                        pixelFormat, excludeLayers);
-        if (outTaskSize != null) {
-            outTaskSize.x = mTmpRect.width();
-            outTaskSize.y = mTmpRect.height();
-        }
-        final HardwareBuffer buffer = screenshotBuffer == null ? null
-                : screenshotBuffer.getHardwareBuffer();
-        if (isInvalidHardwareBuffer(buffer)) {
-            return null;
-        }
-        return screenshotBuffer;
+    @Override
+    ActivityRecord getTopActivity(Task source) {
+        return source.getTopMostActivity();
     }
 
-    static boolean isInvalidHardwareBuffer(HardwareBuffer buffer) {
-        return buffer == null || buffer.isClosed() // This must be checked before getting size.
-                || buffer.getWidth() <= 1 || buffer.getHeight() <= 1;
+    @Override
+    ActivityRecord getTopFullscreenActivity(Task source) {
+        return source.getTopFullscreenActivity();
     }
 
-    @Nullable
-    TaskSnapshot snapshotTask(Task task) {
-        return snapshotTask(task, PixelFormat.UNKNOWN);
-    }
-
-    @Nullable
-    TaskSnapshot snapshotTask(Task task, int pixelFormat) {
-        TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
-
-        if (!prepareTaskSnapshot(task, pixelFormat, builder)) {
-            // Failed some pre-req. Has been logged.
-            return null;
-        }
-
-        final ScreenCapture.ScreenshotHardwareBuffer screenshotBuffer =
-                createTaskSnapshot(task, builder);
-
-        if (screenshotBuffer == null) {
-            // Failed to acquire image. Has been logged.
-            return null;
-        }
-        builder.setSnapshot(screenshotBuffer.getHardwareBuffer());
-        builder.setColorSpace(screenshotBuffer.getColorSpace());
-        return builder.build();
-    }
-
-    void setTaskSnapshotEnabled(boolean enabled) {
-        mTaskSnapshotEnabled = enabled;
-    }
-
-    boolean shouldDisableSnapshots() {
-        return mIsRunningOnTv || mIsRunningOnIoT || !mTaskSnapshotEnabled;
+    @Override
+    ActivityManager.TaskDescription getTaskDescription(Task source) {
+        return source.getTaskDescription();
     }
 
     /**
@@ -538,74 +286,6 @@
         }
     }
 
-    @VisibleForTesting
-    int getSnapshotMode(Task task) {
-        final ActivityRecord topChild = task.getTopMostActivity();
-        if (!task.isActivityTypeStandardOrUndefined() && !task.isActivityTypeAssistant()) {
-            return SNAPSHOT_MODE_NONE;
-        } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
-            return SNAPSHOT_MODE_APP_THEME;
-        } else {
-            return SNAPSHOT_MODE_REAL;
-        }
-    }
-
-    /**
-     * If we are not allowed to take a real screenshot, this attempts to represent the app as best
-     * as possible by using the theme's window background.
-     */
-    private TaskSnapshot drawAppThemeSnapshot(Task task) {
-        final ActivityRecord topChild = task.getTopMostActivity();
-        if (topChild == null) {
-            return null;
-        }
-        final WindowState mainWindow = topChild.findMainWindow();
-        if (mainWindow == null) {
-            return null;
-        }
-        final int color = ColorUtils.setAlphaComponent(
-                task.getTaskDescription().getBackgroundColor(), 255);
-        final LayoutParams attrs = mainWindow.getAttrs();
-        final Rect taskBounds = task.getBounds();
-        final InsetsState insetsState = mainWindow.getInsetsStateWithVisibilityOverride();
-        final Rect systemBarInsets = getSystemBarInsets(mainWindow.getFrame(), insetsState);
-        final SnapshotDrawerUtils.SystemBarBackgroundPainter decorPainter =
-                new SnapshotDrawerUtils.SystemBarBackgroundPainter(attrs.flags,
-                attrs.privateFlags, attrs.insetsFlags.appearance, task.getTaskDescription(),
-                mHighResTaskSnapshotScale, mainWindow.getRequestedVisibleTypes());
-        final int taskWidth = taskBounds.width();
-        final int taskHeight = taskBounds.height();
-        final int width = (int) (taskWidth * mHighResTaskSnapshotScale);
-        final int height = (int) (taskHeight * mHighResTaskSnapshotScale);
-
-        final RenderNode node = RenderNode.create("TaskSnapshotController", null);
-        node.setLeftTopRightBottom(0, 0, width, height);
-        node.setClipToBounds(false);
-        final RecordingCanvas c = node.start(width, height);
-        c.drawColor(color);
-        decorPainter.setInsets(systemBarInsets);
-        decorPainter.drawDecors(c /* statusBarExcludeFrame */, null /* alreadyDrawnFrame */);
-        node.end(c);
-        final Bitmap hwBitmap = ThreadedRenderer.createHardwareBitmap(node, width, height);
-        if (hwBitmap == null) {
-            return null;
-        }
-        final Rect contentInsets = new Rect(systemBarInsets);
-        final Rect letterboxInsets = topChild.getLetterboxInsets();
-        InsetUtils.addInsets(contentInsets, letterboxInsets);
-
-        // Note, the app theme snapshot is never translucent because we enforce a non-translucent
-        // color above
-        return new TaskSnapshot(
-                System.currentTimeMillis() /* id */,
-                topChild.mActivityComponent, hwBitmap.getHardwareBuffer(),
-                hwBitmap.getColorSpace(), mainWindow.getConfiguration().orientation,
-                mainWindow.getWindowConfiguration().getRotation(), new Point(taskWidth, taskHeight),
-                contentInsets, letterboxInsets, false /* isLowResolution */,
-                false /* isRealSnapshot */, task.getWindowingMode(),
-                getAppearance(task), false /* isTranslucent */, false /* hasImeSurface */);
-    }
-
     /**
      * Called when an {@link ActivityRecord} has been removed.
      */
@@ -621,7 +301,7 @@
     }
 
     void notifyTaskRemovedFromRecents(int taskId, int userId) {
-        mCache.onTaskRemoved(taskId);
+        mCache.onIdRemoved(taskId);
         mPersister.onTaskRemovedFromRecents(taskId, userId);
     }
 
@@ -637,15 +317,6 @@
     }
 
     /**
-     * Temporarily pauses/unpauses persisting of task snapshots.
-     *
-     * @param paused Whether task snapshot persisting should be paused.
-     */
-    void setPersisterPaused(boolean paused) {
-        mPersister.setPaused(paused);
-    }
-
-    /**
      * Called when screen is being turned off.
      */
     void screenTurningOff(int displayId, ScreenOffListener listener) {
@@ -691,34 +362,8 @@
         snapshotTasks(mTmpTasks, allowSnapshotHome);
     }
 
-    /**
-     * @return The {@link Appearance} flags for the top fullscreen opaque window in the given
-     *         {@param task}.
-     */
-    private @Appearance int getAppearance(Task task) {
-        final ActivityRecord topFullscreenActivity = task.getTopFullscreenActivity();
-        final WindowState topFullscreenOpaqueWindow = topFullscreenActivity != null
-                ? topFullscreenActivity.getTopFullscreenOpaqueWindow()
-                : null;
-        if (topFullscreenOpaqueWindow != null) {
-            return topFullscreenOpaqueWindow.mAttrs.insetsFlags.appearance;
-        }
-        return 0;
-    }
-
-    static Rect getSystemBarInsets(Rect frame, InsetsState state) {
-        return state.calculateInsets(
-                frame, Type.systemBars(), false /* ignoreVisibility */).toRect();
-    }
-
     private boolean isAnimatingByRecents(@NonNull Task task) {
         return task.isAnimatingByRecents()
                 || mService.mAtmService.getTransitionController().inRecentsTransition(task);
     }
-
-    void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "mHighResTaskSnapshotScale=" + mHighResTaskSnapshotScale);
-        pw.println(prefix + "mTaskSnapshotEnabled=" + mTaskSnapshotEnabled);
-        mCache.dump(pw, prefix);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index 03098e3..cd15119 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,32 +16,13 @@
 
 package com.android.server.wm;
 
-import static android.graphics.Bitmap.CompressFormat.JPEG;
-
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.annotation.NonNull;
-import android.annotation.TestApi;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.os.Process;
-import android.os.SystemClock;
 import android.util.ArraySet;
-import android.util.AtomicFile;
-import android.util.Slog;
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
-import com.android.server.wm.nano.WindowManagerProtos.TaskSnapshotProto;
 
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayDeque;
 import java.util.Arrays;
 
 /**
@@ -49,32 +30,7 @@
  * <p>
  * Test class: {@link TaskSnapshotPersisterLoaderTest}
  */
-class TaskSnapshotPersister {
-
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskSnapshotPersister" : TAG_WM;
-    private static final String SNAPSHOTS_DIRNAME = "snapshots";
-    private static final String LOW_RES_FILE_POSTFIX = "_reduced";
-    private static final long DELAY_MS = 100;
-    private static final int QUALITY = 95;
-    private static final String PROTO_EXTENSION = ".proto";
-    private static final String BITMAP_EXTENSION = ".jpg";
-    private static final int MAX_STORE_QUEUE_DEPTH = 2;
-
-    @GuardedBy("mLock")
-    private final ArrayDeque<WriteQueueItem> mWriteQueue = new ArrayDeque<>();
-    @GuardedBy("mLock")
-    private final ArrayDeque<StoreWriteQueueItem> mStoreQueueItems = new ArrayDeque<>();
-    @GuardedBy("mLock")
-    private boolean mQueueIdling;
-    @GuardedBy("mLock")
-    private boolean mPaused;
-    private boolean mStarted;
-    private final Object mLock = new Object();
-    private final DirectoryResolver mDirectoryResolver;
-    private final float mLowResScaleFactor;
-    private boolean mEnableLowResSnapshots;
-    private final boolean mUse16BitFormat;
-    private final UserManagerInternal mUserManagerInternal;
+class TaskSnapshotPersister extends BaseAppSnapshotPersister {
 
     /**
      * The list of ids of the tasks that have been persisted since {@link #removeObsoleteFiles} was
@@ -83,45 +39,9 @@
     @GuardedBy("mLock")
     private final ArraySet<Integer> mPersistedTaskIdsSinceLastRemoveObsolete = new ArraySet<>();
 
-    TaskSnapshotPersister(WindowManagerService service, DirectoryResolver resolver) {
-        mDirectoryResolver = resolver;
-        mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
-
-        final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_highResTaskSnapshotScale);
-        final float lowResTaskSnapshotScale = service.mContext.getResources().getFloat(
-                com.android.internal.R.dimen.config_lowResTaskSnapshotScale);
-
-        if (lowResTaskSnapshotScale < 0 || 1 <= lowResTaskSnapshotScale) {
-            throw new RuntimeException("Low-res scale must be between 0 and 1");
-        }
-        if (highResTaskSnapshotScale <= 0 || 1 < highResTaskSnapshotScale) {
-            throw new RuntimeException("High-res scale must be between 0 and 1");
-        }
-        if (highResTaskSnapshotScale <= lowResTaskSnapshotScale) {
-            throw new RuntimeException("High-res scale must be greater than low-res scale");
-        }
-
-        if (lowResTaskSnapshotScale > 0) {
-            mLowResScaleFactor = lowResTaskSnapshotScale / highResTaskSnapshotScale;
-            mEnableLowResSnapshots = true;
-        } else {
-            mLowResScaleFactor = 0;
-            mEnableLowResSnapshots = false;
-        }
-
-        mUse16BitFormat = service.mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_use16BitTaskSnapshotPixelFormat);
-    }
-
-    /**
-     * Starts persisting.
-     */
-    void start() {
-        if (!mStarted) {
-            mStarted = true;
-            mPersister.start();
-        }
+    TaskSnapshotPersister(SnapshotPersistQueue persistQueue,
+            PersistInfoProvider persistInfoProvider) {
+        super(persistQueue, persistInfoProvider);
     }
 
     /**
@@ -134,7 +54,7 @@
     void persistSnapshot(int taskId, int userId, TaskSnapshot snapshot) {
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.add(taskId);
-            sendToQueueLocked(new StoreWriteQueueItem(taskId, userId, snapshot));
+            super.persistSnapshot(taskId, userId, snapshot);
         }
     }
 
@@ -147,7 +67,7 @@
     void onTaskRemovedFromRecents(int taskId, int userId) {
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.remove(taskId);
-            sendToQueueLocked(new DeleteWriteQueueItem(taskId, userId));
+            super.removeSnap(taskId, userId);
         }
     }
 
@@ -162,322 +82,20 @@
     void removeObsoleteFiles(ArraySet<Integer> persistentTaskIds, int[] runningUserIds) {
         synchronized (mLock) {
             mPersistedTaskIdsSinceLastRemoveObsolete.clear();
-            sendToQueueLocked(new RemoveObsoleteFilesQueueItem(persistentTaskIds, runningUserIds));
-        }
-    }
-
-    void setPaused(boolean paused) {
-        synchronized (mLock) {
-            mPaused = paused;
-            if (!paused) {
-                mLock.notifyAll();
-            }
-        }
-    }
-
-    boolean enableLowResSnapshots() {
-        return mEnableLowResSnapshots;
-    }
-
-    /**
-     * Return if task snapshots are stored in 16 bit pixel format.
-     *
-     * @return true if task snapshots are stored in 16 bit pixel format.
-     */
-    boolean use16BitFormat() {
-        return mUse16BitFormat;
-    }
-
-    @TestApi
-    void waitForQueueEmpty() {
-        while (true) {
-            synchronized (mLock) {
-                if (mWriteQueue.isEmpty() && mQueueIdling) {
-                    return;
-                }
-            }
-            SystemClock.sleep(DELAY_MS);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void sendToQueueLocked(WriteQueueItem item) {
-        mWriteQueue.offer(item);
-        item.onQueuedLocked();
-        ensureStoreQueueDepthLocked();
-        if (!mPaused) {
-            mLock.notifyAll();
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void ensureStoreQueueDepthLocked() {
-        while (mStoreQueueItems.size() > MAX_STORE_QUEUE_DEPTH) {
-            final StoreWriteQueueItem item = mStoreQueueItems.poll();
-            mWriteQueue.remove(item);
-            Slog.i(TAG, "Queue is too deep! Purged item with taskid=" + item.mTaskId);
-        }
-    }
-
-    private File getDirectory(int userId) {
-        return new File(mDirectoryResolver.getSystemDirectoryForUser(userId), SNAPSHOTS_DIRNAME);
-    }
-
-    File getProtoFile(int taskId, int userId) {
-        return new File(getDirectory(userId), taskId + PROTO_EXTENSION);
-    }
-
-    File getHighResolutionBitmapFile(int taskId, int userId) {
-        return new File(getDirectory(userId), taskId + BITMAP_EXTENSION);
-    }
-
-    @NonNull
-    File getLowResolutionBitmapFile(int taskId, int userId) {
-        return new File(getDirectory(userId), taskId + LOW_RES_FILE_POSTFIX + BITMAP_EXTENSION);
-    }
-
-    private boolean createDirectory(int userId) {
-        final File dir = getDirectory(userId);
-        return dir.exists() || dir.mkdir();
-    }
-
-    private void deleteSnapshot(int taskId, int userId) {
-        final File protoFile = getProtoFile(taskId, userId);
-        final File bitmapLowResFile = getLowResolutionBitmapFile(taskId, userId);
-        protoFile.delete();
-        if (bitmapLowResFile.exists()) {
-            bitmapLowResFile.delete();
-        }
-        final File bitmapFile = getHighResolutionBitmapFile(taskId, userId);
-        if (bitmapFile.exists()) {
-            bitmapFile.delete();
-        }
-    }
-
-    interface DirectoryResolver {
-        File getSystemDirectoryForUser(int userId);
-    }
-
-    private Thread mPersister = new Thread("TaskSnapshotPersister") {
-        public void run() {
-            android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
-            while (true) {
-                WriteQueueItem next;
-                boolean isReadyToWrite = false;
-                synchronized (mLock) {
-                    if (mPaused) {
-                        next = null;
-                    } else {
-                        next = mWriteQueue.poll();
-                        if (next != null) {
-                            if (next.isReady()) {
-                                isReadyToWrite = true;
-                                next.onDequeuedLocked();
-                            } else {
-                                mWriteQueue.addLast(next);
-                            }
-                        }
-                    }
-                }
-                if (next != null) {
-                    if (isReadyToWrite) {
-                        next.write();
-                    }
-                    SystemClock.sleep(DELAY_MS);
-                }
-                synchronized (mLock) {
-                    final boolean writeQueueEmpty = mWriteQueue.isEmpty();
-                    if (!writeQueueEmpty && !mPaused) {
-                        continue;
-                    }
-                    try {
-                        mQueueIdling = writeQueueEmpty;
-                        mLock.wait();
-                        mQueueIdling = false;
-                    } catch (InterruptedException e) {
-                    }
-                }
-            }
-        }
-    };
-
-    private abstract class WriteQueueItem {
-        /**
-         * @return {@code true} if item is ready to have {@link WriteQueueItem#write} called
-         */
-        boolean isReady() {
-            return true;
-        }
-
-        abstract void write();
-
-        /**
-         * Called when this queue item has been put into the queue.
-         */
-        void onQueuedLocked() {
-        }
-
-        /**
-         * Called when this queue item has been taken out of the queue.
-         */
-        void onDequeuedLocked() {
-        }
-    }
-
-    private class StoreWriteQueueItem extends WriteQueueItem {
-        private final int mTaskId;
-        private final int mUserId;
-        private final TaskSnapshot mSnapshot;
-
-        StoreWriteQueueItem(int taskId, int userId, TaskSnapshot snapshot) {
-            mTaskId = taskId;
-            mUserId = userId;
-            mSnapshot = snapshot;
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        void onQueuedLocked() {
-            mStoreQueueItems.offer(this);
-        }
-
-        @GuardedBy("mLock")
-        @Override
-        void onDequeuedLocked() {
-            mStoreQueueItems.remove(this);
-        }
-
-        @Override
-        boolean isReady() {
-            return mUserManagerInternal.isUserUnlocked(mUserId);
-        }
-
-        @Override
-        void write() {
-            if (!createDirectory(mUserId)) {
-                Slog.e(TAG, "Unable to create snapshot directory for user dir="
-                        + getDirectory(mUserId));
-            }
-            boolean failed = false;
-            if (!writeProto()) {
-                failed = true;
-            }
-            if (!writeBuffer()) {
-                failed = true;
-            }
-            if (failed) {
-                deleteSnapshot(mTaskId, mUserId);
-            }
-        }
-
-        boolean writeProto() {
-            final TaskSnapshotProto proto = new TaskSnapshotProto();
-            proto.orientation = mSnapshot.getOrientation();
-            proto.rotation = mSnapshot.getRotation();
-            proto.taskWidth = mSnapshot.getTaskSize().x;
-            proto.taskHeight = mSnapshot.getTaskSize().y;
-            proto.insetLeft = mSnapshot.getContentInsets().left;
-            proto.insetTop = mSnapshot.getContentInsets().top;
-            proto.insetRight = mSnapshot.getContentInsets().right;
-            proto.insetBottom = mSnapshot.getContentInsets().bottom;
-            proto.letterboxInsetLeft = mSnapshot.getLetterboxInsets().left;
-            proto.letterboxInsetTop = mSnapshot.getLetterboxInsets().top;
-            proto.letterboxInsetRight = mSnapshot.getLetterboxInsets().right;
-            proto.letterboxInsetBottom = mSnapshot.getLetterboxInsets().bottom;
-            proto.isRealSnapshot = mSnapshot.isRealSnapshot();
-            proto.windowingMode = mSnapshot.getWindowingMode();
-            proto.appearance = mSnapshot.getAppearance();
-            proto.isTranslucent = mSnapshot.isTranslucent();
-            proto.topActivityComponent = mSnapshot.getTopActivityComponent().flattenToString();
-            proto.id = mSnapshot.getId();
-            final byte[] bytes = TaskSnapshotProto.toByteArray(proto);
-            final File file = getProtoFile(mTaskId, mUserId);
-            final AtomicFile atomicFile = new AtomicFile(file);
-            FileOutputStream fos = null;
-            try {
-                fos = atomicFile.startWrite();
-                fos.write(bytes);
-                atomicFile.finishWrite(fos);
-            } catch (IOException e) {
-                atomicFile.failWrite(fos);
-                Slog.e(TAG, "Unable to open " + file + " for persisting. " + e);
-                return false;
-            }
-            return true;
-        }
-
-        boolean writeBuffer() {
-            if (TaskSnapshotController.isInvalidHardwareBuffer(mSnapshot.getHardwareBuffer())) {
-                Slog.e(TAG, "Invalid task snapshot hw buffer, taskId=" + mTaskId);
-                return false;
-            }
-            final Bitmap bitmap = Bitmap.wrapHardwareBuffer(
-                    mSnapshot.getHardwareBuffer(), mSnapshot.getColorSpace());
-            if (bitmap == null) {
-                Slog.e(TAG, "Invalid task snapshot hw bitmap");
-                return false;
-            }
-
-            final Bitmap swBitmap = bitmap.copy(Config.ARGB_8888, false /* isMutable */);
-
-            final File file = getHighResolutionBitmapFile(mTaskId, mUserId);
-            try {
-                FileOutputStream fos = new FileOutputStream(file);
-                swBitmap.compress(JPEG, QUALITY, fos);
-                fos.close();
-            } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + file + " for persisting.", e);
-                return false;
-            }
-
-            if (!mEnableLowResSnapshots) {
-                swBitmap.recycle();
-                return true;
-            }
-
-            final Bitmap lowResBitmap = Bitmap.createScaledBitmap(swBitmap,
-                    (int) (bitmap.getWidth() * mLowResScaleFactor),
-                    (int) (bitmap.getHeight() * mLowResScaleFactor), true /* filter */);
-            swBitmap.recycle();
-
-            final File lowResFile = getLowResolutionBitmapFile(mTaskId, mUserId);
-            try {
-                FileOutputStream lowResFos = new FileOutputStream(lowResFile);
-                lowResBitmap.compress(JPEG, QUALITY, lowResFos);
-                lowResFos.close();
-            } catch (IOException e) {
-                Slog.e(TAG, "Unable to open " + lowResFile + " for persisting.", e);
-                return false;
-            }
-            lowResBitmap.recycle();
-
-            return true;
-        }
-    }
-
-    private class DeleteWriteQueueItem extends WriteQueueItem {
-        private final int mTaskId;
-        private final int mUserId;
-
-        DeleteWriteQueueItem(int taskId, int userId) {
-            mTaskId = taskId;
-            mUserId = userId;
-        }
-
-        @Override
-        void write() {
-            deleteSnapshot(mTaskId, mUserId);
+            mSnapshotPersistQueue.sendToQueueLocked(new RemoveObsoleteFilesQueueItem(
+                    persistentTaskIds, runningUserIds, mPersistInfoProvider));
         }
     }
 
     @VisibleForTesting
-    class RemoveObsoleteFilesQueueItem extends WriteQueueItem {
+    class RemoveObsoleteFilesQueueItem extends SnapshotPersistQueue.WriteQueueItem {
         private final ArraySet<Integer> mPersistentTaskIds;
         private final int[] mRunningUserIds;
 
         @VisibleForTesting
         RemoveObsoleteFilesQueueItem(ArraySet<Integer> persistentTaskIds,
-                int[] runningUserIds) {
+                int[] runningUserIds, PersistInfoProvider provider) {
+            super(provider);
             mPersistentTaskIds = new ArraySet<>(persistentTaskIds);
             mRunningUserIds = Arrays.copyOf(runningUserIds, runningUserIds.length);
         }
@@ -489,7 +107,7 @@
                 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
             }
             for (int userId : mRunningUserIds) {
-                final File dir = getDirectory(userId);
+                final File dir = mPersistInfoProvider.getDirectory(userId);
                 final String[] files = dir.list();
                 if (files == null) {
                     continue;
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index a1ddd58..302538f 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -786,7 +786,7 @@
                                 && mTransientLaunches != null) {
                             // If transition is transient, then snapshots are taken at end of
                             // transition.
-                            mController.mTaskSnapshotController.recordTaskSnapshot(
+                            mController.mTaskSnapshotController.recordSnapshot(
                                     task, false /* allowSnapshotHome */);
                         }
                         ar.commitVisibility(false /* visible */, false /* performLayout */,
@@ -1051,7 +1051,7 @@
                 final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
                 if (ar == null || ar.isVisibleRequested() || ar.getTask() == null
                         || ar.getTask().isVisibleRequested()) continue;
-                mController.mTaskSnapshotController.recordTaskSnapshot(
+                mController.mTaskSnapshotController.recordSnapshot(
                         ar.getTask(), false /* allowSnapshotHome */);
             }
         }
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 4c34912..1758102 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -631,12 +631,12 @@
             mWakeT.apply();
             // Usually transitions put quite a load onto the system already (with all the things
             // happening in app), so pause task snapshot persisting to not increase the load.
-            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(true);
+            mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(true);
             mTransitionPlayerProc.setRunningRemoteAnimation(true);
         } else if (mPlayingTransitions.isEmpty()) {
             mWakeT.setEarlyWakeupEnd();
             mWakeT.apply();
-            mAtm.mWindowManager.mTaskSnapshotController.setPersisterPaused(false);
+            mAtm.mWindowManager.mSnapshotPersistQueue.setPaused(false);
             mTransitionPlayerProc.setRunningRemoteAnimation(false);
             mRemotePlayer.clear();
             return;
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index 4a43f4f..c11391e 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -205,10 +205,10 @@
         if (runningExpensiveAnimations && !mRunningExpensiveAnimations) {
             // Usually app transitions put quite a load onto the system already (with all the things
             // happening in app), so pause task snapshot persisting to not increase the load.
-            mService.mTaskSnapshotController.setPersisterPaused(true);
+            mService.mSnapshotPersistQueue.setPaused(true);
             mTransaction.setEarlyWakeupStart();
         } else if (!runningExpensiveAnimations && mRunningExpensiveAnimations) {
-            mService.mTaskSnapshotController.setPersisterPaused(false);
+            mService.mSnapshotPersistQueue.setPaused(false);
             mTransaction.setEarlyWakeupEnd();
         }
         mRunningExpensiveAnimations = runningExpensiveAnimations;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d676c17..6737052 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -450,15 +450,17 @@
             mLocalInsetsSourceProviders = new SparseArray<>();
         }
         for (int i = 0; i < insetsTypes.length; i++) {
+            final @InsetsState.InternalInsetsType int type = insetsTypes[i];
             InsetsSourceProvider insetsSourceProvider =
-                    mLocalInsetsSourceProviders.get(insetsTypes[i]);
+                    mLocalInsetsSourceProviders.get(type);
             if (insetsSourceProvider != null) {
                 if (DEBUG) {
-                    Slog.d(TAG, "The local insets provider for this type " + insetsTypes[i]
+                    Slog.d(TAG, "The local insets provider for this type " + type
                             + " already exists. Overwriting");
                 }
             }
-            insetsSourceProvider = new RectInsetsSourceProvider(new InsetsSource(insetsTypes[i]),
+            insetsSourceProvider = new RectInsetsSourceProvider(
+                    new InsetsSource(type, InsetsState.toPublicType(type)),
                     mDisplayContent.getInsetsStateController(), mDisplayContent);
             mLocalInsetsSourceProviders.put(insetsTypes[i], insetsSourceProvider);
             ((RectInsetsSourceProvider) insetsSourceProvider).setRect(providerFrame);
@@ -1303,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 0012e3f..13de4df 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;
 
     /**
@@ -704,6 +704,7 @@
     // changes the orientation.
     private final PowerManager.WakeLock mScreenFrozenLock;
 
+    final SnapshotPersistQueue mSnapshotPersistQueue;
     final TaskSnapshotController mTaskSnapshotController;
 
     final BlurController mBlurController;
@@ -1210,7 +1211,8 @@
         mSyncEngine = new BLASTSyncEngine(this);
 
         mWindowPlacerLocked = new WindowSurfacePlacer(this);
-        mTaskSnapshotController = new TaskSnapshotController(this);
+        mSnapshotPersistQueue = new SnapshotPersistQueue();
+        mTaskSnapshotController = new TaskSnapshotController(this, mSnapshotPersistQueue);
 
         mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
                 Choreographer.getInstance());
@@ -3163,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
@@ -5168,7 +5196,7 @@
         mSystemReady = true;
         mPolicy.systemReady();
         mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);
-        mTaskSnapshotController.systemReady();
+        mSnapshotPersistQueue.systemReady();
         mHasWideColorGamutSupport = queryWideColorGamutSupport();
         mHasHdrSupport = queryHdrSupport();
         UiThread.getHandler().post(mSettingsObserver::loadSettings);
@@ -6710,12 +6738,12 @@
             pw.print("  mDisplayFrozen="); pw.print(mDisplayFrozen);
                     pw.print(" windows="); pw.print(mWindowsFreezingScreen);
                     pw.print(" client="); pw.print(mClientFreezingScreen);
-                    pw.print(" apps="); pw.print(mAppsFreezingScreen);
+                    pw.print(" apps="); pw.println(mAppsFreezingScreen);
             final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
-            pw.print("  mRotation="); pw.print(defaultDisplayContent.getRotation());
+            pw.print("  mRotation="); pw.println(defaultDisplayContent.getRotation());
             pw.print("  mLastOrientation=");
                     pw.println(defaultDisplayContent.getLastOrientation());
-            pw.print(" waitingForConfig=");
+            pw.print("  mWaitingForConfig=");
                     pw.println(defaultDisplayContent.mWaitingForConfig);
 
             pw.print("  Animation settings: disabled="); pw.print(mAnimationsDisabled);
@@ -7138,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;
@@ -9186,7 +9200,7 @@
 
     @Override
     public void setTaskSnapshotEnabled(boolean enabled) {
-        mTaskSnapshotController.setTaskSnapshotEnabled(enabled);
+        mTaskSnapshotController.setSnapshotEnabled(enabled);
     }
 
     @Override
@@ -9253,7 +9267,7 @@
                     throw new IllegalArgumentException(
                             "Failed to find matching task for taskId=" + taskId);
                 }
-                taskSnapshot = mTaskSnapshotController.captureTaskSnapshot(task, false);
+                taskSnapshot = mTaskSnapshotController.captureSnapshot(task, false);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
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/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index bc382e0..ae31ee8 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -27,8 +27,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -36,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;
@@ -124,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;
@@ -371,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;
 
     /**
@@ -916,7 +908,7 @@
 
         // Skip performing seamless rotation when the controlled insets is IME with visible state.
         if (mControllableInsetProvider != null
-                && mControllableInsetProvider.getSource().getType() == ITYPE_IME) {
+                && mControllableInsetProvider.getSource().getType() == WindowInsets.Type.ime()) {
             return;
         }
 
@@ -1681,14 +1673,10 @@
         if (rotatedState != null) {
             return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
         }
-        final InsetsSourceProvider provider = getControllableInsetProvider();
-        final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
-                ? provider.getSource().getType() : ITYPE_INVALID;
         final InsetsState rawInsetsState =
                 mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
-        final InsetsState insetsStateForWindow = insetsPolicy
-                .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
-                        getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
+        final InsetsState insetsStateForWindow = insetsPolicy.enforceInsetsPolicyForTarget(
+                mAttrs, getWindowingMode(), isAlwaysOnTop(), rawInsetsState);
         return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
                 includeTransient);
     }
@@ -3948,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();
@@ -4208,10 +4186,6 @@
         super.resetDragResizingChangeReported();
     }
 
-    int getResizeMode() {
-        return mResizeMode;
-    }
-
     private boolean computeDragResizing() {
         final Task task = getTask();
         if (task == null) {
@@ -4234,8 +4208,7 @@
             return true;
         }
 
-        return getDisplayContent().mDividerControllerLocked.isResizing()
-                && !task.inFreeformWindowingMode() && !isGoneForLayout();
+        return false;
     }
 
     void setDragResizing() {
@@ -4244,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,
@@ -5938,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());
         }
@@ -6282,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/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 f41d4ab..bcb4ec9 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1228,16 +1228,19 @@
             Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
         }
 
+        mFirstBoot = mPackageManagerService.isFirstBoot();
+        mPackageManager = mSystemContext.getPackageManager();
+        t.traceEnd();
+
+        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());
-
-        mFirstBoot = mPackageManagerService.isFirstBoot();
-        mPackageManager = mSystemContext.getPackageManager();
         t.traceEnd();
+
         if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
             FrameworkStatsLog.write(FrameworkStatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
                     FrameworkStatsLog
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/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index ce0b3a2..b6f1b87 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"
                 ));
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/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..c439639 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,7 +127,23 @@
         "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(
@@ -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,14 +253,11 @@
         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,
@@ -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/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/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
deleted file mode 100644
index ea14ffb..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ /dev/null
@@ -1,282 +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.am;
-
-import static android.os.Process.myPid;
-import static android.os.Process.myUid;
-
-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.Mockito.doAnswer;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-
-import android.app.ActivityManagerInternal;
-import android.app.IApplicationThread;
-import android.app.usage.UsageStatsManagerInternal;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManagerInternal;
-import android.os.Binder;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.DropBoxManagerInternal;
-import com.android.server.LocalServices;
-import com.android.server.am.ActivityManagerService.Injector;
-import com.android.server.appop.AppOpsService;
-import com.android.server.wm.ActivityTaskManagerInternal;
-import com.android.server.wm.ActivityTaskManagerService;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.io.File;
-import java.util.Arrays;
-
-
-/**
- * Tests to verify process starts are completed or timeout correctly
- */
-@MediumTest
-@SuppressWarnings("GuardedBy")
-public class AsyncProcessStartTest {
-    private static final String TAG = "AsyncProcessStartTest";
-
-    private static final String PACKAGE = "com.foo";
-
-    @Rule
-    public final ApplicationExitInfoTest.ServiceThreadRule
-            mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
-
-    private Context mContext;
-    private HandlerThread mHandlerThread;
-
-    @Mock
-    private AppOpsService mAppOpsService;
-    @Mock
-    private DropBoxManagerInternal mDropBoxManagerInt;
-    @Mock
-    private PackageManagerInternal mPackageManagerInt;
-    @Mock
-    private UsageStatsManagerInternal mUsageStatsManagerInt;
-    @Mock
-    private ActivityManagerInternal mActivityManagerInt;
-    @Mock
-    private ActivityTaskManagerInternal mActivityTaskManagerInt;
-    @Mock
-    private BatteryStatsService mBatteryStatsService;
-
-    private ActivityManagerService mRealAms;
-    private ActivityManagerService mAms;
-
-    private ProcessList mRealProcessList = new ProcessList();
-    private ProcessList mProcessList;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
-
-        mHandlerThread = new HandlerThread(TAG);
-        mHandlerThread.start();
-
-        LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
-        LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
-
-        LocalServices.removeServiceForTest(PackageManagerInternal.class);
-        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
-
-        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
-        LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
-
-        LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
-        LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
-
-        doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
-        doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
-        doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
-
-        mRealAms = new ActivityManagerService(
-                new TestInjector(mContext), mServiceThreadRule.getThread());
-        mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
-        mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
-        mRealAms.mAtmInternal = mActivityTaskManagerInt;
-        mRealAms.mPackageManagerInt = mPackageManagerInt;
-        mRealAms.mUsageStatsService = mUsageStatsManagerInt;
-        mRealAms.mProcessesReady = true;
-        mAms = spy(mRealAms);
-        mRealProcessList.mService = mAms;
-        mProcessList = spy(mRealProcessList);
-
-        doAnswer((invocation) -> {
-            Log.v(TAG, "Intercepting isProcStartValidLocked() for "
-                    + Arrays.toString(invocation.getArguments()));
-            return null;
-        }).when(mProcessList).isProcStartValidLocked(any(), anyLong());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mHandlerThread.quit();
-    }
-
-    private class TestInjector extends Injector {
-        TestInjector(Context context) {
-            super(context);
-        }
-
-        @Override
-        public AppOpsService getAppOpsService(File file, Handler handler) {
-            return mAppOpsService;
-        }
-
-        @Override
-        public Handler getUiHandler(ActivityManagerService service) {
-            return mHandlerThread.getThreadHandler();
-        }
-
-        @Override
-        public ProcessList getProcessList(ActivityManagerService service) {
-            return mRealProcessList;
-        }
-
-        @Override
-        public BatteryStatsService getBatteryStatsService() {
-            return mBatteryStatsService;
-        }
-    }
-
-    private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
-            throws Exception {
-        final ApplicationInfo ai = makeApplicationInfo(packageName);
-        return makeActiveProcessRecord(ai, wedge);
-    }
-
-    private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
-            throws Exception {
-        final IApplicationThread thread = mock(IApplicationThread.class);
-        final IBinder threadBinder = new Binder();
-        doReturn(threadBinder).when(thread).asBinder();
-        doAnswer((invocation) -> {
-            Log.v(TAG, "Intercepting bindApplication() for "
-                    + Arrays.toString(invocation.getArguments()));
-            if (!wedge) {
-                mRealAms.finishAttachApplication(0);
-            }
-            return null;
-        }).when(thread).bindApplication(
-                any(), any(),
-                any(), any(),
-                any(), any(),
-                any(), any(),
-                any(),
-                any(), anyInt(),
-                anyBoolean(), anyBoolean(),
-                anyBoolean(), anyBoolean(), any(),
-                any(), any(), any(),
-                any(), any(),
-                any(), any(),
-                any(),
-                anyLong(), anyLong());
-
-        final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
-        r.setPid(myPid());
-        r.setStartUid(myUid());
-        r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
-        r.makeActive(thread, mAms.mProcessStats);
-        doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
-                anyBoolean());
-
-        return r;
-    }
-
-    static ApplicationInfo makeApplicationInfo(String packageName) {
-        final ApplicationInfo ai = new ApplicationInfo();
-        ai.packageName = packageName;
-        ai.processName = packageName;
-        ai.uid = myUid();
-        return ai;
-    }
-
-    /**
-     * Verify that we don't kill a normal process
-     */
-    @Test
-    public void testNormal() throws Exception {
-        ProcessRecord app = startProcessAndWait(false);
-
-        verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
-    }
-
-    /**
-     * Verify that we kill a wedged process after the process start timeout
-     */
-    @Test
-    public void testWedged() throws Exception {
-        ProcessRecord app = startProcessAndWait(true);
-
-        verify(app).killLocked(any(), anyInt(), anyBoolean());
-    }
-
-    private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
-        final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
-        final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
-
-        mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
-                /* expectedStartSeq */ 0, /* procAttached */ false);
-
-        app.getThread().bindApplication(PACKAGE, appInfo,
-                null, null,
-                null,
-                null,
-                null, null,
-                null,
-                null, 0,
-                false, false,
-                true, false,
-                null,
-                null, null,
-                null,
-                null, null, null,
-                null, null,
-                0, 0);
-
-        // Sleep until timeout should have triggered
-        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
-
-        return app;
-    }
-}
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/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..c4ff4f0 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) {
@@ -1111,7 +1111,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 +1132,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 +1153,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 +1178,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/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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 0a4ae6f..ac880ce 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
@@ -83,6 +83,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;
@@ -132,7 +133,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 +145,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 +174,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,7 +191,7 @@
                     .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";
 
@@ -250,9 +252,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(
@@ -320,7 +322,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 +330,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 +347,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 +393,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 +411,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 +437,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 +448,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 +459,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 +468,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 +482,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 +499,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 +639,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 +651,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 +670,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 +726,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 +742,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 +759,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,66 +784,66 @@
 
     @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);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
@@ -798,7 +852,7 @@
 
     @Test
     public void createVirtualKeyboard_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
@@ -807,7 +861,7 @@
 
     @Test
     public void createVirtualMouse_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
@@ -816,7 +870,7 @@
 
     @Test
     public void createVirtualTouchscreen_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
@@ -825,7 +879,7 @@
 
     @Test
     public void createVirtualNavigationTouchpad_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
@@ -847,12 +901,12 @@
 
     @Test
     public void onAudioSessionStarting_noPermission_failsSecurityException() {
-        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID);
+        mDeviceImpl.mVirtualDisplayIds.add(DISPLAY_ID_1);
         doCallRealMethod().when(mContext).enforceCallingOrSelfPermission(
                 eq(Manifest.permission.CREATE_VIRTUAL_DEVICE), anyString());
         assertThrows(SecurityException.class,
                 () -> mDeviceImpl.onAudioSessionStarting(
-                        DISPLAY_ID, mRoutingCallback, mConfigChangedCallback));
+                        DISPLAY_ID_1, mRoutingCallback, mConfigChangedCallback));
     }
 
     @Test
@@ -864,7 +918,7 @@
 
     @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 +928,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 +937,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 +990,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 +1013,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 +1032,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 +1043,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 +1078,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 +1105,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 +1119,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 +1144,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 +1157,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 +1183,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 +1197,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 +1229,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 +1252,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 +1303,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 +1324,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 +1345,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 +1366,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 +1387,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 +1408,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 +1430,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 +1445,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 +1460,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 +1485,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 +1501,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 +1513,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 +1529,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 +1545,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
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
index 0f95231..3a27e3b 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceParamsTest.java
@@ -41,6 +41,8 @@
 
     private static final String SENSOR_NAME = "VirtualSensorName";
     private static final String SENSOR_VENDOR = "VirtualSensorVendor";
+    private static final int PLAYBACK_SESSION_ID = 42;
+    private static final int RECORDING_SESSION_ID = 77;
 
     @Test
     public void parcelable_shouldRecreateSuccessfully() {
@@ -49,6 +51,8 @@
                 .setUsersWithMatchingAccounts(Set.of(UserHandle.of(123), UserHandle.of(456)))
                 .setDevicePolicy(POLICY_TYPE_SENSORS, DEVICE_POLICY_CUSTOM)
                 .setDevicePolicy(POLICY_TYPE_AUDIO, DEVICE_POLICY_CUSTOM)
+                .setAudioPlaybackSessionId(PLAYBACK_SESSION_ID)
+                .setAudioRecordingSessionId(RECORDING_SESSION_ID)
                 .addVirtualSensorConfig(
                         new VirtualSensorConfig.Builder(TYPE_ACCELEROMETER, SENSOR_NAME)
                                 .setVendor(SENSOR_VENDOR)
@@ -65,6 +69,8 @@
                 .containsExactly(UserHandle.of(123), UserHandle.of(456));
         assertThat(params.getDevicePolicy(POLICY_TYPE_SENSORS)).isEqualTo(DEVICE_POLICY_CUSTOM);
         assertThat(params.getDevicePolicy(POLICY_TYPE_AUDIO)).isEqualTo(DEVICE_POLICY_CUSTOM);
+        assertThat(params.getAudioPlaybackSessionId()).isEqualTo(PLAYBACK_SESSION_ID);
+        assertThat(params.getAudioRecordingSessionId()).isEqualTo(RECORDING_SESSION_ID);
 
         List<VirtualSensorConfig> sensorConfigs = params.getVirtualSensorConfigs();
         assertThat(sensorConfigs).hasSize(1);
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..d90f53a 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -135,8 +135,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 +188,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 +265,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 +308,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 +366,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 +386,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 +408,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 +438,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 +467,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 +478,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 +520,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 +615,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 +635,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 +655,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 +675,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();
@@ -670,7 +696,7 @@
      */
     private void assertPersistedEquals(JobInfo firstInfo) throws Exception {
         mTaskStoreUnderTest.clear();
-        JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null);
+        JobStatus first = JobStatus.createFromJobInfo(firstInfo, SOME_UID, null, -1, null, null);
         mTaskStoreUnderTest.add(first);
         waitForPendingIo();
 
@@ -693,6 +719,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());
 
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/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/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 19d8894..4ee87d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -3216,14 +3216,14 @@
     public void testImeInsetsFrozenFlag_resetWhenReportedToBeImeInputTarget() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
-        InsetsSource imeSource = new InsetsSource(ITYPE_IME);
+        InsetsSource imeSource = new InsetsSource(ITYPE_IME, ime());
         app.mAboveInsetsState.addSource(imeSource);
         mDisplayContent.setImeLayeringTarget(app);
         mDisplayContent.updateImeInputAndControlTarget(app);
 
         InsetsState state = app.getInsetsState();
-        assertFalse(state.getSource(ITYPE_IME).isVisible());
-        assertTrue(state.getSource(ITYPE_IME).getFrame().isEmpty());
+        assertFalse(state.getSource(imeSource.getId()).isVisible());
+        assertTrue(state.getSource(imeSource.getId()).getFrame().isEmpty());
 
         // Simulate app is closing and expect IME insets is frozen.
         mDisplayContent.mOpeningApps.clear();
@@ -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/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 10f2270..cf9ec81 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -24,6 +24,7 @@
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
@@ -184,7 +185,7 @@
         mDisplayContent.setLayoutNeeded();
         mDisplayContent.performLayout(true /* initial */, false /* updateImeWindows */);
 
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
         navSource.setFrame(mNavBarWindow.getFrame());
         opaqueDarkNavBar.mAboveInsetsState.addSource(navSource);
         opaqueLightNavBar.mAboveInsetsState.addSource(navSource);
@@ -254,14 +255,15 @@
 
     @Test
     public void testOverlappingWithNavBar() {
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
         navSource.setFrame(new Rect(100, 200, 200, 300));
         testOverlappingWithNavBarType(navSource);
     }
 
     @Test
     public void testOverlappingWithExtraNavBar() {
-        final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        final InsetsSource navSource =
+                new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
         navSource.setFrame(new Rect(100, 200, 200, 300));
         testOverlappingWithNavBarType(navSource);
     }
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/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
index a26cad9..d4e860e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.view.InsetsState.ITYPE_IME;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
@@ -38,7 +39,7 @@
 @RunWith(WindowTestRunner.class)
 public class ImeInsetsSourceProviderTest extends WindowTestsBase {
 
-    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
+    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
     private ImeInsetsSourceProvider mImeProvider;
 
     @Before
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index c7971bc7..9887839 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -197,14 +197,14 @@
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(dialog);
         assertNotNull(dialogControls);
         assertEquals(1, dialogControls.length);
-        assertEquals(ITYPE_NAVIGATION_BAR, dialogControls[0].getType());
+        assertEquals(navigationBars(), dialogControls[0].getType());
 
         // fullscreenApp is hiding status bar, and it can keep controlling status bar.
         final InsetsSourceControl[] fullscreenAppControls =
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(fullscreenApp);
         assertNotNull(fullscreenAppControls);
         assertEquals(1, fullscreenAppControls.length);
-        assertEquals(ITYPE_STATUS_BAR, fullscreenAppControls[0].getType());
+        assertEquals(statusBars(), fullscreenAppControls[0].getType());
 
         // Assume mFocusedWindow is updated but mTopFullscreenOpaqueWindowState hasn't.
         final WindowState newFocusedFullscreenApp = addWindow(TYPE_APPLICATION, "newFullscreenApp");
@@ -232,7 +232,7 @@
                 mDisplayContent.getInsetsStateController().getControlsForDispatch(panel);
         assertNotNull(panelControls);
         assertEquals(1, panelControls.length);
-        assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
+        assertEquals(navigationBars(), panelControls[0].getType());
 
         // Add notificationShade and make it can receive keys.
         final WindowState shade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
@@ -254,7 +254,7 @@
         panelControls = mDisplayContent.getInsetsStateController().getControlsForDispatch(panel);
         assertNotNull(panelControls);
         assertEquals(1, panelControls.length);
-        assertEquals(ITYPE_NAVIGATION_BAR, panelControls[0].getType());
+        assertEquals(navigationBars(), panelControls[0].getType());
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -321,7 +321,7 @@
         assertEquals(2, controls.length);
         for (int i = controls.length - 1; i >= 0; i--) {
             final InsetsSourceControl control = controls[i];
-            if (control.getType() == ITYPE_STATUS_BAR) {
+            if (control.getType() == statusBars()) {
                 assertNull(controls[i].getLeash());
             } else {
                 assertNotNull(controls[i].getLeash());
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 6877e4f..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;
@@ -33,6 +34,7 @@
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -97,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;
@@ -2496,7 +2499,8 @@
         organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
         organizer.putTaskToPrimary(mTask, true);
 
-        final InsetsSource navSource = new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR);
+        final InsetsSource navSource =
+                new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
         navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
 
         mActivity.mWmService.mLetterboxConfiguration.setLetterboxActivityCornersRadius(15);
@@ -2819,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/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 98dee38..06d30fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -23,8 +23,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import android.window.TaskSnapshot;
 import android.platform.test.annotations.Presubmit;
+import android.window.TaskSnapshot;
 
 import androidx.test.filters.SmallTest;
 
@@ -89,7 +89,7 @@
         mCache.putSnapshot(window.getTask(), createSnapshot());
         assertNotNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
                 false /* restoreFromDisk */, false /* isLowResolution */));
-        mCache.onTaskRemoved(window.getTask().mTaskId);
+        mCache.onIdRemoved(window.getTask().mTaskId);
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, 0 /* userId */,
                 false /* restoreFromDisk */, false /* isLowResolution */));
     }
@@ -98,7 +98,7 @@
     public void testReduced_notCached() {
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
                 false /* restoreFromDisk */, false /* isLowResolution */));
 
@@ -115,7 +115,7 @@
     public void testRestoreFromDisk() {
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
                 false /* restoreFromDisk */, false /* isLowResolution */));
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 06a176f..4c7b0aa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -202,7 +202,7 @@
         // Verify no NPE happens when calling createTaskSnapshot.
         try {
             final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
-            mWm.mTaskSnapshotController.createTaskSnapshot(mAppWindow.mActivityRecord.getTask(),
+            mWm.mTaskSnapshotController.createSnapshot(mAppWindow.mActivityRecord.getTask(),
                     1f /* scaleFraction */, PixelFormat.UNKNOWN, null /* outTaskSize */, builder);
         } catch (NullPointerException e) {
             fail("There should be no exception when calling createTaskSnapshot");
@@ -223,7 +223,7 @@
         try {
             final TaskSnapshot.Builder builder = new TaskSnapshot.Builder();
             spyOn(builder);
-            mWm.mTaskSnapshotController.createTaskSnapshot(
+            mWm.mTaskSnapshotController.createSnapshot(
                     mAppWindow.mActivityRecord.getTask(), 1f /* scaleFraction */,
                     PixelFormat.UNKNOWN, null /* outTaskSize */, builder);
             // Verify the builder should includes IME surface.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
index ec0ca01..df5f3d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotLowResDisabledTest.java
@@ -22,11 +22,11 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 
-import android.window.TaskSnapshot;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.util.ArraySet;
+import android.window.TaskSnapshot;
 
 import androidx.test.filters.MediumTest;
 
@@ -38,7 +38,7 @@
 import java.io.File;
 
 /**
- * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ * Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader}
  *
  * Build/Install/Run:
  * atest TaskSnapshotPersisterLoaderTest
@@ -67,7 +67,7 @@
     @Test
     public void testPersistAndLoadSnapshot() {
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] files = new File[]{
                 new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
@@ -91,7 +91,7 @@
         final ArraySet<Integer> taskIds = new ArraySet<>();
         taskIds.add(1);
         mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] existsFiles = new File[]{
                 new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg")};
@@ -111,7 +111,7 @@
         taskIds.add(1);
         mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
         mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] existsFiles = new File[]{
                 new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -128,7 +128,7 @@
     public void testReduced_notCached() {
         final WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, "window");
         mPersister.persistSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertNull(mCache.getSnapshot(window.getTask().mTaskId, mWm.mCurrentUserId,
                 false /* restoreFromDisk */, false /* isLowResolution */));
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index 7409d62..13a4c11 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
@@ -41,7 +42,8 @@
 
 import androidx.test.filters.MediumTest;
 
-import com.android.server.wm.TaskSnapshotLoader.PreRLegacySnapshotConfig;
+import com.android.server.wm.AppSnapshotLoader.PreRLegacySnapshotConfig;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 import com.android.server.wm.TaskSnapshotPersister.RemoveObsoleteFilesQueueItem;
 
 import org.junit.Test;
@@ -51,7 +53,7 @@
 import java.io.File;
 
 /**
- * Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
+ * Test class for {@link TaskSnapshotPersister} and {@link AppSnapshotLoader}
  *
  * Build/Install/Run:
  * atest TaskSnapshotPersisterLoaderTest
@@ -72,7 +74,7 @@
     @Test
     public void testPersistAndLoadSnapshot() {
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] files = new File[]{new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
                 new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg")};
@@ -86,7 +88,7 @@
 
         snapshot.getHardwareBuffer().close();
         mPersister.persistSnapshot(1, mTestUserId, snapshot);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertTrueForFiles(files, file -> !file.exists(),
                 " snapshot files must be removed by invalid buffer");
     }
@@ -95,7 +97,7 @@
     public void testTaskRemovedFromRecents() {
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
         mPersister.onTaskRemovedFromRecents(1, mTestUserId);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.proto").exists());
         assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1.jpg").exists());
         assertFalse(new File(FILES_DIR.getPath() + "/snapshots/1_reduced.jpg").exists());
@@ -113,7 +115,7 @@
         mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
         mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
         mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         assertTrue(SystemClock.elapsedRealtime() - ms > 500);
     }
 
@@ -123,15 +125,15 @@
     @Test
     public void testPurging() {
         mPersister.persistSnapshot(100, mTestUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
-        mPersister.setPaused(true);
+        mSnapshotPersistQueue.waitForQueueEmpty();
+        mSnapshotPersistQueue.setPaused(true);
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
         mPersister.removeObsoleteFiles(new ArraySet<>(), new int[]{mTestUserId});
         mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
         mPersister.persistSnapshot(3, mTestUserId, createSnapshot());
         mPersister.persistSnapshot(4, mTestUserId, createSnapshot());
-        mPersister.setPaused(false);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.setPaused(false);
+        mSnapshotPersistQueue.waitForQueueEmpty();
 
         // Make sure 1,2 were purged but removeObsoleteFiles wasn't.
         final File[] existsFiles = new File[]{
@@ -147,8 +149,10 @@
 
     @Test
     public void testGetTaskId() {
+        PersistInfoProvider persistInfoProvider = mock(PersistInfoProvider.class);
         RemoveObsoleteFilesQueueItem removeObsoleteFilesQueueItem =
-                mPersister.new RemoveObsoleteFilesQueueItem(new ArraySet<>(), new int[]{});
+                mPersister.new RemoveObsoleteFilesQueueItem(
+                        new ArraySet<>(), new int[]{}, persistInfoProvider);
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("blablablulp"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("nothing.err"));
         assertEquals(-1, removeObsoleteFilesQueueItem.getTaskId("/invalid/"));
@@ -311,7 +315,7 @@
         assertFalse(b.isRealSnapshot());
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -334,7 +338,7 @@
         assertEquals(WINDOWING_MODE_PINNED, b.getWindowingMode());
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -357,7 +361,7 @@
         assertFalse(b.isTranslucent());
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -381,7 +385,7 @@
         assertEquals(lightBarFlags, b.getAppearance());
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -402,7 +406,7 @@
                 .build();
         mPersister.persistSnapshot(1, mTestUserId, a);
         mPersister.persistSnapshot(2, mTestUserId, b);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
@@ -418,7 +422,7 @@
         final ArraySet<Integer> taskIds = new ArraySet<>();
         taskIds.add(1);
         mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] existsFiles = new File[]{
                 new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -438,7 +442,7 @@
         taskIds.add(1);
         mPersister.removeObsoleteFiles(taskIds, new int[]{mTestUserId});
         mPersister.persistSnapshot(2, mTestUserId, createSnapshot());
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final File[] existsFiles = new File[]{
                 new File(FILES_DIR.getPath() + "/snapshots/1.proto"),
                 new File(FILES_DIR.getPath() + "/snapshots/1.jpg"),
@@ -456,7 +460,7 @@
                 .build();
         mPersister.persistSnapshot(1, mTestUserId, createSnapshot());
         mPersister.persistSnapshot(2, mTestUserId, a);
-        mPersister.waitForQueueEmpty();
+        mSnapshotPersistQueue.waitForQueueEmpty();
         final TaskSnapshot snapshotA = mLoader.loadTask(1, mTestUserId,
                 false /* isLowResolution */);
         final TaskSnapshot snapshotB = mLoader.loadTask(2, mTestUserId,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
index 677359f..b69874a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterTestBase.java
@@ -45,6 +45,7 @@
 
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -66,8 +67,9 @@
 
     private ContextWrapper mContextSpy;
     private Resources mResourcesSpy;
+    SnapshotPersistQueue mSnapshotPersistQueue;
     TaskSnapshotPersister mPersister;
-    TaskSnapshotLoader mLoader;
+    AppSnapshotLoader mLoader;
     int mTestUserId;
     float mHighResScale;
     float mLowResScale;
@@ -108,9 +110,12 @@
                 com.android.internal.R.dimen.config_lowResTaskSnapshotScale))
                 .thenReturn(mLowResScale);
 
-        mPersister = new TaskSnapshotPersister(mWm, userId -> FILES_DIR);
-        mLoader = new TaskSnapshotLoader(mPersister);
-        mPersister.start();
+        mSnapshotPersistQueue = new SnapshotPersistQueue();
+        PersistInfoProvider provider =
+                TaskSnapshotController.createPersistInfoProvider(mWm, userId -> FILES_DIR);
+        mPersister = new TaskSnapshotPersister(mSnapshotPersistQueue, provider);
+        mLoader = new AppSnapshotLoader(provider);
+        mSnapshotPersistQueue.start();
     }
 
     @After
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/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index ab7cf45..0231b46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1159,7 +1159,7 @@
         // normally.
         mWm.mSyncEngine.abort(openTransition.getSyncId());
 
-        verify(snapshotController, times(1)).recordTaskSnapshot(eq(task2), eq(false));
+        verify(snapshotController, times(1)).recordSnapshot(eq(task2), eq(false));
 
         openTransition.finishTransition();
 
@@ -1183,12 +1183,12 @@
 
         // Make sure we haven't called recordSnapshot (since we are transient, it shouldn't be
         // called until finish).
-        verify(snapshotController, times(0)).recordTaskSnapshot(eq(task1), eq(false));
+        verify(snapshotController, times(0)).recordSnapshot(eq(task1), eq(false));
 
         enteringAnimReports.clear();
         closeTransition.finishTransition();
 
-        verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
+        verify(snapshotController, times(1)).recordSnapshot(eq(task1), eq(false));
         assertTrue(enteringAnimReports.contains(activity2));
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 383722a..19da718 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -18,6 +18,7 @@
 
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
@@ -44,9 +45,9 @@
 @RunWith(WindowTestRunner.class)
 public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
 
-    private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR);
+    private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
     private WindowContainerInsetsSourceProvider mProvider;
-    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME);
+    private InsetsSource mImeSource = new InsetsSource(ITYPE_IME, ime());
     private WindowContainerInsetsSourceProvider mImeProvider;
 
     @Before
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 ebf3166..1a1ca54 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -24,6 +24,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY;
 import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
+import static android.view.WindowInsets.Type.systemOverlays;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -55,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;
@@ -659,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());
@@ -1336,11 +1444,11 @@
                 new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
 
         InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
-                ITYPE_TOP_GENERIC_OVERLAY);
+                ITYPE_TOP_GENERIC_OVERLAY, systemOverlays());
         genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1);
         genericOverlayInsetsProvider1Source.setVisible(true);
         InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource(
-                ITYPE_BOTTOM_GENERIC_OVERLAY);
+                ITYPE_BOTTOM_GENERIC_OVERLAY, systemOverlays());
         genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2);
         genericOverlayInsetsProvider2Source.setVisible(true);
 
@@ -1655,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) {
@@ -1665,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/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index e5e9f54..f24318b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -802,7 +802,7 @@
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
 
         assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
-                .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
+                .valueAt(0).getSource().getId()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
     }
 
     @Test
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 69e3244..5e0e209 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -27,6 +27,7 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
@@ -276,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();
@@ -775,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();
@@ -1017,7 +1015,7 @@
     @SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
     @Test
     public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR);
+        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
         mImeWindow.mAboveInsetsState.addSource(navSource);
         mAppWindow.mAboveInsetsState.addSource(navSource);
 
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/AccessNetworkConstants.java b/telephony/java/android/telephony/AccessNetworkConstants.java
index 0fdf40d..27ba676 100644
--- a/telephony/java/android/telephony/AccessNetworkConstants.java
+++ b/telephony/java/android/telephony/AccessNetworkConstants.java
@@ -605,9 +605,9 @@
         EUTRAN_ARFCN_FREQUENCY_BAND_41(
                 EutranBand.BAND_41, 2496000, 39650, 41589, 2496000, 39650, 41589),
         EUTRAN_ARFCN_FREQUENCY_BAND_42(
-                EutranBand.BAND_42, 3400000, 41950, 43589, 3400000, 41950, 43589),
+                EutranBand.BAND_42, 3400000, 41590, 43589, 3400000, 41590, 43589),
         EUTRAN_ARFCN_FREQUENCY_BAND_43(
-                EutranBand.BAND_43, 3600000, 43950, 45589, 3600000, 43950, 45589),
+                EutranBand.BAND_43, 3600000, 43590, 45589, 3600000, 43590, 45589),
         EUTRAN_ARFCN_FREQUENCY_BAND_44(
                 EutranBand.BAND_44, 703000, 45590, 46589, 703000, 45590, 46589),
         EUTRAN_ARFCN_FREQUENCY_BAND_45(
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index a6e30a5..6f462b1 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -45,6 +45,7 @@
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsRegistrationAttributes;
 import android.telephony.ims.ImsSsData;
+import android.telephony.ims.MediaQualityStatus;
 import android.telephony.ims.RcsUceAdapter;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
@@ -4467,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"
@@ -6347,6 +6360,50 @@
         public static final String KEY_DTMFNB_PAYLOAD_TYPE_INT_ARRAY  =
                 KEY_PREFIX + "dtmfnb_payload_type_int_array";
 
+        /**
+         * This indicates the threshold for RTP packet loss rate in percentage. If measured packet
+         * loss rate crosses this, a callback with {@link MediaQualityStatus} will be invoked to
+         * listeners.
+         * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+         *
+         * <p/>
+         * Valid threshold range : 0 ~ 100
+         *
+         * @hide
+         */
+        public static final String KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT =
+                KEY_PREFIX + "rtp_packet_loss_rate_threshold_int";
+
+
+        /**
+         * This indicates the threshold for RTP jitter value in milliseconds (RFC3550). If measured
+         * jitter value crosses this, a callback with {@link MediaQualityStatus} will be invoked
+         * to listeners.
+         * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+         *
+         * <p/>
+         * Valid threshold range : 0 ~ 10000
+         *
+         * @hide
+         */
+        public static final String KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT =
+                KEY_PREFIX + "rtp_jitter_threshold_millis_int";
+
+        /**
+         * This indicates the threshold for RTP inactivity time in milliseconds. If measured
+         * inactivity timer crosses this, a callback with {@link MediaQualityStatus} will be invoked
+         * to listeners.
+         * See {@link android.telephony.TelephonyCallback.MediaQualityStatusChangedListener}
+         *
+         * <p/>
+         * Valid threshold range : 0 ~ 60000
+         *
+         * @hide
+         */
+        public static final String KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG =
+                KEY_PREFIX + "rtp_inactivity_time_threshold_millis_long";
+
+
         /** @hide */
         @IntDef({
             BANDWIDTH_EFFICIENT,
@@ -6833,6 +6890,9 @@
             defaults.putInt(KEY_AUDIO_AS_BANDWIDTH_KBPS_INT, 41);
             defaults.putInt(KEY_AUDIO_RS_BANDWIDTH_BPS_INT, 600);
             defaults.putInt(KEY_AUDIO_RR_BANDWIDTH_BPS_INT, 2000);
+            defaults.putInt(KEY_VOICE_RTP_PACKET_LOSS_RATE_THRESHOLD_INT, 40);
+            defaults.putInt(KEY_VOICE_RTP_JITTER_THRESHOLD_MILLIS_INT, 120);
+            defaults.putLong(KEY_VOICE_RTP_INACTIVITY_TIME_THRESHOLD_MILLIS_LONG, 5000);
 
             defaults.putIntArray(
                     KEY_AUDIO_INACTIVITY_CALL_END_REASONS_INT_ARRAY,
@@ -6988,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);
@@ -6995,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[] {
@@ -8604,6 +8776,14 @@
         public static final String KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL =
                 KEY_PREFIX + "supports_eap_aka_fast_reauth_bool";
 
+        /**
+         * Type of IP preference used to prioritize ePDG servers. Possible values are
+         * {@link #EPDG_ADDRESS_IPV4_PREFERRED}, {@link #EPDG_ADDRESS_IPV6_PREFERRED},
+         * {@link #EPDG_ADDRESS_IPV4_ONLY}
+         */
+        public static final String KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT =
+                KEY_PREFIX + "epdg_address_ip_type_preference_int";
+
         /** @hide */
         @IntDef({AUTHENTICATION_METHOD_EAP_ONLY, AUTHENTICATION_METHOD_CERT})
         public @interface AuthenticationMethodType {}
@@ -8668,6 +8848,39 @@
          */
         public static final int ID_TYPE_KEY_ID = 11;
 
+        /** @hide */
+        @IntDef({
+                EPDG_ADDRESS_IPV4_PREFERRED,
+                EPDG_ADDRESS_IPV6_PREFERRED,
+                EPDG_ADDRESS_IPV4_ONLY,
+                EPDG_ADDRESS_IPV6_ONLY,
+                EPDG_ADDRESS_SYSTEM_PREFERRED
+        })
+        public @interface EpdgAddressIpPreference {}
+
+        /** Prioritize IPv4 ePDG addresses. */
+        public static final int EPDG_ADDRESS_IPV4_PREFERRED = 0;
+
+        /** Prioritize IPv6 ePDG addresses */
+        public static final int EPDG_ADDRESS_IPV6_PREFERRED = 1;
+
+        /** Use IPv4 ePDG addresses only. */
+        public static final int EPDG_ADDRESS_IPV4_ONLY = 2;
+
+        /** Use IPv6 ePDG addresses only.
+         * @hide
+         */
+        public static final int EPDG_ADDRESS_IPV6_ONLY = 3;
+
+        /** Follow the priority from DNS resolution results, which are sorted by using RFC6724
+         * algorithm.
+         *
+         * @see <a href="https://tools.ietf.org/html/rfc6724#section-6">RFC 6724, Default Address
+         *     Selection for Internet Protocol Version 6 (IPv6)</a>
+         * @hide
+         */
+        public static final int EPDG_ADDRESS_SYSTEM_PREFERRED = 4;
+
         private Iwlan() {}
 
         private static PersistableBundle getDefaults() {
@@ -8751,7 +8964,7 @@
             defaults.putInt(KEY_EPDG_PCO_ID_IPV6_INT, 0);
             defaults.putInt(KEY_EPDG_PCO_ID_IPV4_INT, 0);
             defaults.putBoolean(KEY_SUPPORTS_EAP_AKA_FAST_REAUTH_BOOL, false);
-
+            defaults.putInt(KEY_EPDG_ADDRESS_IP_TYPE_PREFERENCE_INT, EPDG_ADDRESS_IPV4_PREFERRED);
             return defaults;
         }
     }
@@ -9897,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/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index cbd3df4..b0552b4 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -210,10 +210,11 @@
     private final int mTransportType;
 
     /**
-     * The initial registration state
+     * The true registration state of network, This is not affected by any carrier config or
+     * resource overlay.
      */
     @RegistrationState
-    private final int mInitialRegistrationState;
+    private final int mNetworkRegistrationState;
 
     /**
      * The registration state that might have been overridden by config
@@ -290,7 +291,7 @@
         mDomain = domain;
         mTransportType = transportType;
         mRegistrationState = registrationState;
-        mInitialRegistrationState = registrationState;
+        mNetworkRegistrationState = registrationState;
         mRoamingType = (registrationState == REGISTRATION_STATE_ROAMING)
                 ? ServiceState.ROAMING_TYPE_UNKNOWN : ServiceState.ROAMING_TYPE_NOT_ROAMING;
         setAccessNetworkTechnology(accessNetworkTechnology);
@@ -350,7 +351,7 @@
         mDomain = source.readInt();
         mTransportType = source.readInt();
         mRegistrationState = source.readInt();
-        mInitialRegistrationState = source.readInt();
+        mNetworkRegistrationState = source.readInt();
         mRoamingType = source.readInt();
         mAccessNetworkTechnology = source.readInt();
         mRejectCause = source.readInt();
@@ -377,7 +378,7 @@
         mDomain = nri.mDomain;
         mTransportType = nri.mTransportType;
         mRegistrationState = nri.mRegistrationState;
-        mInitialRegistrationState = nri.mInitialRegistrationState;
+        mNetworkRegistrationState = nri.mNetworkRegistrationState;
         mRoamingType = nri.mRoamingType;
         mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
         mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation;
@@ -430,10 +431,14 @@
     }
 
     /**
-     * @return The registration state.
+     * @return The registration state. Note this value can be affected by the carrier config
+     * override.
      *
+     * @deprecated Use {@link #getNetworkRegistrationState}, which is not affected by any carrier
+     * config or resource overlay, instead.
      * @hide
      */
+    @Deprecated
     @SystemApi
     public @RegistrationState int getRegistrationState() {
         if (mRegistrationState == REGISTRATION_STATE_EMERGENCY) {
@@ -449,30 +454,58 @@
     }
 
     /**
-     * @return The initial registration state.
+     * @return The true registration state of network. (This value is not affected by any carrier
+     * config or resource overlay override).
      *
      * @hide
      */
-    public @RegistrationState int getInitialRegistrationState() {
-        return mInitialRegistrationState;
+    @SystemApi
+    public @RegistrationState int getNetworkRegistrationState() {
+        return mNetworkRegistrationState;
     }
 
     /**
-     * @return {@code true} if registered on roaming or home network, {@code false} otherwise.
+     * @return {@code true} if registered on roaming or home network. Note this value can be
+     * affected by the carrier config override.
+     *
+     * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+     * resource overlay, instead.
      */
+    @Deprecated
     public boolean isRegistered() {
         return mRegistrationState == REGISTRATION_STATE_HOME
                 || mRegistrationState == REGISTRATION_STATE_ROAMING;
     }
 
     /**
-     * @return {@code true} if searching for service, {@code false} otherwise.
+     * @return {@code true} if registered on roaming or home network, {@code false} otherwise. (This
+     * value is not affected by any carrier config or resource overlay override).
      */
+    public boolean isNetworkRegistered() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_HOME
+                || mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
+     * @return {@code true} if searching for service, {@code false} otherwise.
+     *
+     * @deprecated Use {@link #isNetworkRegistered}, which is not affected by any carrier config or
+     * resource overlay, instead.
+     */
+    @Deprecated
     public boolean isSearching() {
         return mRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
     }
 
     /**
+     * @return {@code true} if searching for service, {@code false} otherwise. (This value is not
+     * affected by any carrier config or resource overlay override).
+     */
+    public boolean isNetworkSearching() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_NOT_REGISTERED_SEARCHING;
+    }
+
+    /**
      * Get the PLMN-ID for this Network Registration, also known as the RPLMN.
      *
      * <p>If the device is registered, this will return the registered PLMN-ID. If registration
@@ -489,13 +522,25 @@
     }
 
     /**
-     * @return {@code true} if registered on roaming network, {@code false} otherwise.
+     * @return {@code true} if registered on roaming network overridden by config. Note this value
+     * can be affected by the carrier config override.
+     *
+     * @deprecated Use {@link TelephonyDisplayInfo#isRoaming} instead.
      */
+    @Deprecated
     public boolean isRoaming() {
         return mRoamingType != ServiceState.ROAMING_TYPE_NOT_ROAMING;
     }
 
     /**
+     * @return {@code true} if registered on roaming network. (This value is not affected by any
+     * carrier config or resource overlay override).
+     */
+    public boolean isNetworkRoaming() {
+        return mNetworkRegistrationState == REGISTRATION_STATE_ROAMING;
+    }
+
+    /**
      * @hide
      * @return {@code true} if in service.
      */
@@ -525,7 +570,8 @@
     }
 
     /**
-     * @return the current network roaming type.
+     * @return the current network roaming type. Note that this value can be possibly overridden by
+     * the carrier config or resource overlay.
      * @hide
      */
     @SystemApi
@@ -706,8 +752,8 @@
                 .append(" transportType=").append(
                         AccessNetworkConstants.transportTypeToString(mTransportType))
                 .append(" registrationState=").append(registrationStateToString(mRegistrationState))
-                .append(" mInitialRegistrationState=")
-                .append(registrationStateToString(mInitialRegistrationState))
+                .append(" networkRegistrationState=")
+                .append(registrationStateToString(mNetworkRegistrationState))
                 .append(" roamingType=").append(ServiceState.roamingTypeToString(mRoamingType))
                 .append(" accessNetworkTechnology=")
                 .append(TelephonyManager.getNetworkTypeName(mAccessNetworkTechnology))
@@ -728,7 +774,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mDomain, mTransportType, mRegistrationState, mInitialRegistrationState,
+        return Objects.hash(mDomain, mTransportType, mRegistrationState, mNetworkRegistrationState,
                 mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly,
                 mAvailableServices, mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState,
                 mRplmn, mIsUsingCarrierAggregation);
@@ -746,7 +792,7 @@
         return mDomain == other.mDomain
                 && mTransportType == other.mTransportType
                 && mRegistrationState == other.mRegistrationState
-                && mInitialRegistrationState == other.mInitialRegistrationState
+                && mNetworkRegistrationState == other.mNetworkRegistrationState
                 && mRoamingType == other.mRoamingType
                 && mAccessNetworkTechnology == other.mAccessNetworkTechnology
                 && mRejectCause == other.mRejectCause
@@ -769,7 +815,7 @@
         dest.writeInt(mDomain);
         dest.writeInt(mTransportType);
         dest.writeInt(mRegistrationState);
-        dest.writeInt(mInitialRegistrationState);
+        dest.writeInt(mNetworkRegistrationState);
         dest.writeInt(mRoamingType);
         dest.writeInt(mAccessNetworkTechnology);
         dest.writeInt(mRejectCause);
@@ -866,7 +912,7 @@
         private int mTransportType;
 
         @RegistrationState
-        private int mInitialRegistrationState;
+        private int mNetworkRegistrationState;
 
         @NetworkType
         private int mAccessNetworkTechnology;
@@ -904,7 +950,7 @@
         public Builder(@NonNull NetworkRegistrationInfo nri) {
             mDomain = nri.mDomain;
             mTransportType = nri.mTransportType;
-            mInitialRegistrationState = nri.mInitialRegistrationState;
+            mNetworkRegistrationState = nri.mNetworkRegistrationState;
             mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
             mRejectCause = nri.mRejectCause;
             mEmergencyOnly = nri.mEmergencyOnly;
@@ -952,7 +998,7 @@
          * @return The same instance of the builder.
          */
         public @NonNull Builder setRegistrationState(@RegistrationState int registrationState) {
-            mInitialRegistrationState = registrationState;
+            mNetworkRegistrationState = registrationState;
             return this;
         }
 
@@ -1071,7 +1117,7 @@
          */
         @SystemApi
         public @NonNull NetworkRegistrationInfo build() {
-            return new NetworkRegistrationInfo(mDomain, mTransportType, mInitialRegistrationState,
+            return new NetworkRegistrationInfo(mDomain, mTransportType, mNetworkRegistrationState,
                     mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices,
                     mCellIdentity, mRplmn, mVoiceSpecificRegistrationInfo,
                     mDataSpecificRegistrationInfo);
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index cd1a40a..523d0b0 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -630,11 +630,17 @@
     }
 
     /**
-     * Get current roaming indicator of phone
+     * Get current roaming indicator of phone. This roaming state could be overridden by the carrier
+     * config.
      * (note: not just decoding from TS 27.007 7.2)
-     *
+     * @see TelephonyDisplayInfo#isRoaming() for visualization purpose.
      * @return true if TS 27.007 7.2 roaming is true
      *              and ONS is different from SPN
+     * @see CarrierConfigManager#KEY_FORCE_HOME_NETWORK_BOOL
+     * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
      */
     public boolean getRoaming() {
         return getVoiceRoaming() || getDataRoaming();
@@ -649,8 +655,9 @@
     public boolean getVoiceRoaming() {
         return getVoiceRoamingType() != ROAMING_TYPE_NOT_ROAMING;
     }
+
     /**
-     * Get current voice network roaming type
+     * Get current voice roaming type. This roaming type could be overridden by the carrier config.
      * @return roaming type
      * @hide
      */
@@ -700,7 +707,7 @@
     }
 
     /**
-     * Get current data network roaming type
+     * Get current data roaming type. This roaming type could be overridden by the carrier config.
      * @return roaming type
      * @hide
      */
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/android/telephony/TelephonyDisplayInfo.java b/telephony/java/android/telephony/TelephonyDisplayInfo.java
index f4e2ade..e01b10e 100644
--- a/telephony/java/android/telephony/TelephonyDisplayInfo.java
+++ b/telephony/java/android/telephony/TelephonyDisplayInfo.java
@@ -87,10 +87,12 @@
     public static final int OVERRIDE_NETWORK_TYPE_NR_ADVANCED = 5;
 
     @NetworkType
-    private final  int mNetworkType;
+    private final int mNetworkType;
 
     @OverrideNetworkType
-    private final  int mOverrideNetworkType;
+    private final int mOverrideNetworkType;
+
+    private final boolean mIsRoaming;
 
     /**
      * Constructor
@@ -98,18 +100,37 @@
      * @param networkType Current packet-switching cellular network type
      * @param overrideNetworkType The override network type
      *
+     * @deprecated will not use this constructor anymore.
+     * @hide
+     */
+    @Deprecated
+    public TelephonyDisplayInfo(@NetworkType int networkType,
+            @OverrideNetworkType int overrideNetworkType) {
+        this(networkType, overrideNetworkType, false);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param networkType Current packet-switching cellular network type
+     * @param overrideNetworkType The override network type
+     * @param isRoaming True if the device is roaming after override.
+     *
      * @hide
      */
     public TelephonyDisplayInfo(@NetworkType int networkType,
-            @OverrideNetworkType int overrideNetworkType) {
+            @OverrideNetworkType int overrideNetworkType,
+            boolean isRoaming) {
         mNetworkType = networkType;
         mOverrideNetworkType = overrideNetworkType;
+        mIsRoaming = isRoaming;
     }
 
     /** @hide */
     public TelephonyDisplayInfo(Parcel p) {
         mNetworkType = p.readInt();
         mOverrideNetworkType = p.readInt();
+        mIsRoaming = p.readBoolean();
     }
 
     /**
@@ -135,10 +156,25 @@
         return mOverrideNetworkType;
     }
 
+    /**
+     * Get device is roaming or not. Note the isRoaming is for market branding or visualization
+     * purposes only. It cannot be treated as the actual roaming device is camped on.
+     *
+     * @return True if the device is registered on roaming network overridden by config.
+     * @see CarrierConfigManager#KEY_GSM_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY
+     * @see CarrierConfigManager#KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY
+     */
+    public boolean isRoaming() {
+        return mIsRoaming;
+    }
+
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeInt(mNetworkType);
         dest.writeInt(mOverrideNetworkType);
+        dest.writeBoolean(mIsRoaming);
     }
 
     public static final @NonNull Parcelable.Creator<TelephonyDisplayInfo> CREATOR =
@@ -165,12 +201,13 @@
         if (o == null || getClass() != o.getClass()) return false;
         TelephonyDisplayInfo that = (TelephonyDisplayInfo) o;
         return mNetworkType == that.mNetworkType
-                && mOverrideNetworkType == that.mOverrideNetworkType;
+                && mOverrideNetworkType == that.mOverrideNetworkType
+                && mIsRoaming == that.mIsRoaming;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mNetworkType, mOverrideNetworkType);
+        return Objects.hash(mNetworkType, mOverrideNetworkType, mIsRoaming);
     }
 
     /**
@@ -195,6 +232,7 @@
     @Override
     public String toString() {
         return "TelephonyDisplayInfo {network=" + TelephonyManager.getNetworkTypeName(mNetworkType)
-                + ", override=" + overrideNetworkTypeToString(mOverrideNetworkType) + "}";
+                + ", overrideNetwork=" + overrideNetworkTypeToString(mOverrideNetworkType)
+                + ", isRoaming=" + mIsRoaming + "}";
     }
 }
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.aidl b/telephony/java/android/telephony/ims/MediaQualityStatus.aidl
new file mode 100644
index 0000000..e570f6c
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.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.telephony.ims;
+
+parcelable MediaQualityStatus;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/MediaQualityStatus.java b/telephony/java/android/telephony/ims/MediaQualityStatus.java
new file mode 100644
index 0000000..62c289a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaQualityStatus.java
@@ -0,0 +1,287 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.AccessNetworkConstants.TransportType;
+
+import java.util.Objects;
+
+/**
+ * A representation of Media quality status.
+ *
+ * @hide
+ */
+@SystemApi
+public final class MediaQualityStatus implements Parcelable {
+    public static final int MEDIA_SESSION_TYPE_AUDIO =  1;
+    public static final int MEDIA_SESSION_TYPE_VIDEO =  2;
+
+    private final String mImsCallSessionId;
+    private final int mMediaSessionType;
+    private final int mTransportType;
+    private final int mRtpPacketLossRate;
+    private final int mRtpJitter;
+    private final long mRtpInactivityTimeMillis;
+
+    /** @hide */
+    @IntDef(
+            value = {
+                    MEDIA_SESSION_TYPE_AUDIO,
+                    MEDIA_SESSION_TYPE_VIDEO,
+            })
+    public @interface MediaSessionType {}
+
+    /**
+     * Constructor for this
+     *
+     * @param imsCallSessionId IMS call session id of this quality status
+     * @param mediaSessionType media session type of this quality status
+     * @param transportType transport type of this quality status
+     * @param rtpPacketLossRate measured RTP packet loss rate
+     * @param rtpJitter measured RTP jitter value
+     * @param rptInactivityTimeMillis measured RTP inactivity time in milliseconds
+     */
+    private MediaQualityStatus(@NonNull String imsCallSessionId,
+            @MediaSessionType int mediaSessionType, @TransportType int transportType,
+            int rtpPacketLossRate, int rtpJitter, long rptInactivityTimeMillis) {
+        mImsCallSessionId = imsCallSessionId;
+        mMediaSessionType = mediaSessionType;
+        mTransportType = transportType;
+        mRtpPacketLossRate = rtpPacketLossRate;
+        mRtpJitter = rtpJitter;
+        mRtpInactivityTimeMillis = rptInactivityTimeMillis;
+    }
+
+    /**
+     * Retrieves call session ID for this quality status
+     */
+    @NonNull
+    public String getCallSessionId() {
+        return mImsCallSessionId;
+    }
+
+    /**
+     * Retrieves media session type of this quality status
+     */
+    public @MediaSessionType int getMediaSessionType() {
+        return mMediaSessionType;
+    }
+
+
+    /**
+     * Retrieves Transport type for which this media quality was measured.
+     */
+    public @TransportType int getTransportType() {
+        return mTransportType;
+    }
+
+    /**
+     * Retrieves measured RTP packet loss rate in percentage.
+     */
+    @IntRange(from = 0, to = 100)
+    public int getRtpPacketLossRate() {
+        return mRtpPacketLossRate;
+    }
+
+    /**
+     * Retrieves measured RTP jitter(RFC3550) value in milliseconds
+     */
+    public int getRtpJitterMillis() {
+        return mRtpJitter;
+    }
+
+    /**
+     * Retrieves measured RTP inactivity time in milliseconds
+     */
+    public long getRtpInactivityMillis() {
+        return mRtpInactivityTimeMillis;
+    }
+
+    /**
+     * Creates a new instance of {@link MediaQualityStatus} from a parcel.
+     * @param in The parceled data to read.
+     */
+    private MediaQualityStatus(@NonNull Parcel in) {
+        mImsCallSessionId = in.readString();
+        mMediaSessionType = in.readInt();
+        mTransportType = in.readInt();
+        mRtpPacketLossRate = in.readInt();
+        mRtpJitter = in.readInt();
+        mRtpInactivityTimeMillis = in.readLong();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString(mImsCallSessionId);
+        dest.writeInt(mMediaSessionType);
+        dest.writeInt(mTransportType);
+        dest.writeInt(mRtpPacketLossRate);
+        dest.writeInt(mRtpJitter);
+        dest.writeLong(mRtpInactivityTimeMillis);
+    }
+
+    public static final @NonNull Creator<MediaQualityStatus> CREATOR =
+            new Creator<MediaQualityStatus>() {
+                @Override
+                public MediaQualityStatus createFromParcel(@NonNull Parcel in) {
+                    return new MediaQualityStatus(in);
+                }
+
+                @Override
+                public MediaQualityStatus[] newArray(int size) {
+                    return new MediaQualityStatus[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MediaQualityStatus that = (MediaQualityStatus) o;
+        return mImsCallSessionId != null && mImsCallSessionId.equals(that.mImsCallSessionId)
+                && mMediaSessionType == that.mMediaSessionType
+                && mTransportType == that.mTransportType
+                && mRtpPacketLossRate == that.mRtpPacketLossRate
+                && mRtpJitter == that.mRtpJitter
+                && mRtpInactivityTimeMillis == that.mRtpInactivityTimeMillis;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mImsCallSessionId, mMediaSessionType, mTransportType,
+                mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("MediaThreshold{mImsCallSessionId=");
+        sb.append(mImsCallSessionId);
+        sb.append(", mMediaSessionType=");
+        sb.append(mMediaSessionType);
+        sb.append(", mTransportType=");
+        sb.append(mTransportType);
+        sb.append(", mRtpPacketLossRate=");
+        sb.append(mRtpPacketLossRate);
+        sb.append(", mRtpJitter=");
+        sb.append(mRtpJitter);
+        sb.append(", mRtpInactivityTimeMillis=");
+        sb.append(mRtpInactivityTimeMillis);
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Provides a convenient way to set the fields of an {@link MediaQualityStatus} when creating a
+     * new instance.
+     *
+     * <p>The example below shows how you might create a new {@code RtpQualityStatus}:
+     *
+     * <pre><code>
+     *
+     * MediaQualityStatus = new MediaQualityStatus.Builder(
+     *                                          callSessionId, mediaSessionType, transportType)
+     *     .setRtpPacketLossRate(packetLossRate)
+     *     .setRtpJitter(jitter)
+     *     .setRtpInactivityMillis(inactivityTimeMillis)
+     *     .build();
+     * </code></pre>
+     */
+    public static final class Builder {
+        private final String mImsCallSessionId;
+        private final int mMediaSessionType;
+        private final int mTransportType;
+        private int mRtpPacketLossRate;
+        private int mRtpJitter;
+        private long mRtpInactivityTimeMillis;
+
+        /**
+         * Default constructor for the Builder.
+         */
+        public Builder(
+                @NonNull String imsCallSessionId,
+                @MediaSessionType int mediaSessionType,
+                int transportType) {
+            mImsCallSessionId = imsCallSessionId;
+            mMediaSessionType = mediaSessionType;
+            mTransportType = transportType;
+        }
+
+        /**
+         * Set RTP packet loss info.
+         *
+         * @param packetLossRate RTP packet loss rate in percentage
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setRtpPacketLossRate(@IntRange(from = 0, to = 100) int packetLossRate) {
+            this.mRtpPacketLossRate = packetLossRate;
+            return this;
+        }
+
+        /**
+         * Set calculated RTP jitter(RFC3550) value in milliseconds.
+         *
+         * @param jitter calculated RTP jitter value.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setRtpJitterMillis(int jitter) {
+            this.mRtpJitter = jitter;
+            return this;
+        }
+
+        /**
+         * Set measured RTP inactivity time.
+         *
+         * @param inactivityTimeMillis RTP inactivity time in Milliseconds.
+         * @return The same instance of the builder.
+         */
+        @NonNull
+        public Builder setRtpInactivityMillis(long inactivityTimeMillis) {
+            this.mRtpInactivityTimeMillis = inactivityTimeMillis;
+            return this;
+        }
+
+        /**
+         * Build the {@link MediaQualityStatus}
+         *
+         * @return the {@link MediaQualityStatus} object
+         */
+        @NonNull
+        public MediaQualityStatus build() {
+            return new MediaQualityStatus(
+                    mImsCallSessionId,
+                    mMediaSessionType,
+                    mTransportType,
+                    mRtpPacketLossRate,
+                    mRtpJitter,
+                    mRtpInactivityTimeMillis);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/MediaThreshold.aidl b/telephony/java/android/telephony/ims/MediaThreshold.aidl
new file mode 100644
index 0000000..dede418
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaThreshold.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.telephony.ims;
+
+parcelable MediaThreshold;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/MediaThreshold.java b/telephony/java/android/telephony/ims/MediaThreshold.java
new file mode 100644
index 0000000..343aa4a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/MediaThreshold.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.ims.feature.MmTelFeature;
+
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.TreeSet;
+
+/**
+ * A MediaThreshold represents a series of packet loss rate, jitter and rtp inactivity time
+ * thresholds which when crossed should result in a {@link MediaQualityStatus} report being
+ * generated by the {@link ImsService} via {@link MmTelFeature#notifyMediaQualityStatusChanged(
+ * MediaQualityStatus)}
+ *
+ * <p/>
+ * A {@link MediaQualityStatus} should be triggered when any of various
+ * attributes pass one of the thresholds defined here.
+ *
+ *  @hide
+ */
+@SystemApi
+public final class MediaThreshold implements Parcelable {
+    private final int[] mRtpPacketLossRate;
+    private final int[] mRtpJitter;
+    private final long[] mRtpInactivityTimeMillis;
+
+    /**
+     * Retrieves threshold values for RTP packet loss rate in percentage.
+     *
+     * @return int array including threshold values for packet loss rate
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public int[] getThresholdsRtpPacketLossRate() {
+        return mRtpPacketLossRate;
+    }
+
+    /**
+     * Retrieves threshold values for jitter(RFC3550) in milliseconds.
+     *
+     * @return int array including threshold values for RTP jitter.
+     */
+    @NonNull
+    public int[] getThresholdsRtpJitterMillis() {
+        return mRtpJitter;
+    }
+
+    /**
+     * Retrieves threshold values for RTP inactivity time in milliseconds.
+     *
+     * @return int array including threshold values for RTP inactivity time.
+     */
+    @NonNull
+    public long[] getThresholdsRtpInactivityTimeMillis() {
+        return mRtpInactivityTimeMillis;
+    }
+
+    private MediaThreshold(
+            int[] packetLossRateThresholds,
+            int[] jitterThresholds,
+            long[] inactivityTimeThresholds) {
+        mRtpPacketLossRate = packetLossRateThresholds;
+        mRtpJitter = jitterThresholds;
+        mRtpInactivityTimeMillis = inactivityTimeThresholds;
+    }
+
+    /**
+     * Creates a new instance of {@link MediaThreshold} from a parcel.
+     * @param in The parceled data to read.
+     */
+    private MediaThreshold(@NonNull Parcel in) {
+        mRtpPacketLossRate = in.createIntArray();
+        mRtpJitter = in.createIntArray();
+        mRtpInactivityTimeMillis = in.createLongArray();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeIntArray(mRtpPacketLossRate);
+        dest.writeIntArray(mRtpJitter);
+        dest.writeLongArray(mRtpInactivityTimeMillis);
+    }
+
+    public static final @NonNull Creator<MediaThreshold> CREATOR =
+            new Creator<MediaThreshold>() {
+                @Override
+                public MediaThreshold createFromParcel(@NonNull Parcel in) {
+                    return new MediaThreshold(in);
+                }
+
+                @Override
+                public MediaThreshold[] newArray(int size) {
+                    return new MediaThreshold[size];
+                }
+            };
+
+    /**
+     * Returns whether the RTP packet loss rate threshold is valid or not.
+     *
+     * @param packetLossRate packet loss rate
+     * @return the threshold is valid or not.
+     * @hide
+     */
+    public static boolean isValidRtpPacketLossRate(int packetLossRate) {
+        return (packetLossRate >= 0 && packetLossRate <= 100);
+    }
+
+    /**
+     * Returns whether the RTP jitter threshold is valid or not.
+     *
+     * @param jitter jitter value in milliseconds
+     * @return the threshold is valid or not.
+     * @hide
+     */
+    public static boolean isValidJitterMillis(int jitter) {
+        return (jitter >= 0 && jitter <= 10000);
+    }
+
+    /**
+     * Returns whether the RTP packet loss rate threshold is valid or not.
+     *
+     * @param inactivityTime packet loss rate
+     * @return the threshold is valid or not.
+     * @hide
+     */
+    public static boolean isValidRtpInactivityTimeMillis(long inactivityTime) {
+        return (inactivityTime >= 0 && inactivityTime <= 60000);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        MediaThreshold that = (MediaThreshold) o;
+        return Arrays.equals(mRtpPacketLossRate, that.mRtpPacketLossRate)
+                && Arrays.equals(mRtpJitter, that.mRtpJitter)
+                && Arrays.equals(mRtpInactivityTimeMillis, that.mRtpInactivityTimeMillis);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(mRtpPacketLossRate), Arrays.hashCode(mRtpJitter),
+                Arrays.hashCode(mRtpInactivityTimeMillis));
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("MediaThreshold{mRtpPacketLossRate=");
+        for (int i : mRtpPacketLossRate) {
+            sb.append(" ").append(i);
+        }
+        sb.append(", mRtpJitter=");
+        for (int b : mRtpJitter) {
+            sb.append(" ").append(b);
+        }
+        sb.append(", mRtpInactivityTimeMillis=");
+        for (long i : mRtpInactivityTimeMillis) {
+            sb.append(" ").append(i);
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    /**
+     * Provides a convenient way to set the fields of an {@link MediaThreshold} when creating a
+     * new instance.
+     *
+     * <p>The example below shows how you might create a new {@code RtpThreshold}:
+     *
+     * <pre><code>
+     *
+     * RtpThreshold = new RtpThreshold.Builder()
+     *     .setRtpSessionType({@link MediaQualityStatus#MEDIA_SESSION_TYPE_AUDIO} or
+     *                          {@link MediaQualityStatus#MEDIA_SESSION_TYPE_VIDEO})
+     *     .setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds)
+     *     .setThresholdsRtpJitterMillis(int[] jitterThresholds)
+     *     .setThresholdsRtpInactivityTimeMillis(int[] inactivityTimeThresholds)
+     *     .build();
+     * </code></pre>
+     *
+     * @hide
+     */
+    public static final class Builder {
+        private int[] mRtpPacketLossRate = null;
+        private int[] mRtpJitter = null;
+        private long[] mRtpInactivityTimeMillis = null;
+
+        /**
+         * Default constructor for the Builder.
+         *
+         * @hide
+         */
+        public Builder() {
+        }
+
+        /**
+         * Set threshold values for RTP packet loss rate in percentage.
+         * <p/>
+         * The packet loss calculation should be done at least once per
+         * second. It should be calculated with at least the last 3 seconds
+         * of data.
+         *
+         * @param packetLossRateThresholds int array for threshold values.
+         * @return The same instance of the builder.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setThresholdsRtpPacketLossRate(int[] packetLossRateThresholds) {
+            if (packetLossRateThresholds.length > 0) {
+                TreeSet<Integer> thresholds = new TreeSet<>();
+                for (Integer value : packetLossRateThresholds) {
+                    if (isValidRtpPacketLossRate(value)) {
+                        thresholds.add(value);
+                    }
+                }
+                int[] targetArray = new int[thresholds.size()];
+                int i = 0;
+                for (int element : thresholds) {
+                    targetArray[i++] = element;
+                }
+                this.mRtpPacketLossRate = targetArray;
+            } else {
+                this.mRtpPacketLossRate = packetLossRateThresholds;
+            }
+            return this;
+        }
+
+
+        /**
+         * Set threshold values for RTP jitter in Milliseconds.
+         *
+         * @param jitterThresholds int array including threshold values for Jitter.
+         * @return The same instance of the builder.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setThresholdsRtpJitterMillis(int[] jitterThresholds) {
+            if (jitterThresholds.length > 0) {
+                TreeSet<Integer> thresholds = new TreeSet<>();
+                for (Integer value : jitterThresholds) {
+                    if (isValidJitterMillis(value)) {
+                        thresholds.add(value);
+                    }
+                }
+                int[] targetArray = new int[thresholds.size()];
+                int i = 0;
+                for (int element : thresholds) {
+                    targetArray[i++] = element;
+                }
+                this.mRtpJitter = targetArray;
+            } else {
+                this.mRtpJitter = jitterThresholds;
+            }
+            return this;
+        }
+
+        /**
+         * Set threshold values for RTP inactivity time.
+         *
+         * @param inactivityTimeThresholds int array including threshold
+         *                              values for RTP inactivity time.
+         * @return The same instance of the builder.
+         *
+         * @hide
+         */
+        @NonNull
+        public Builder setThresholdsRtpInactivityTimeMillis(long[] inactivityTimeThresholds) {
+            if (inactivityTimeThresholds.length > 0) {
+                TreeSet<Long> thresholds = new TreeSet<>();
+                for (Long value : inactivityTimeThresholds) {
+                    if (isValidRtpInactivityTimeMillis(value)) {
+                        thresholds.add(value);
+                    }
+                }
+                long[] targetArray = new long[thresholds.size()];
+                int i = 0;
+                for (long element : thresholds) {
+                    targetArray[i++] = element;
+                }
+                this.mRtpInactivityTimeMillis = targetArray;
+            } else {
+                this.mRtpInactivityTimeMillis = inactivityTimeThresholds;
+            }
+            return this;
+        }
+
+        /**
+         * Build the {@link MediaThreshold}
+         *
+         * @return the {@link MediaThreshold} object
+         *
+         * @hide
+         */
+        @NonNull
+        public MediaThreshold build() {
+            mRtpPacketLossRate = mRtpPacketLossRate != null ? mRtpPacketLossRate : new int[0];
+            mRtpJitter = mRtpJitter != null ? mRtpJitter : new int[0];
+            mRtpInactivityTimeMillis =
+                    mRtpInactivityTimeMillis != null ? mRtpInactivityTimeMillis : new long[0];
+            return new MediaThreshold(mRtpPacketLossRate, mRtpJitter, mRtpInactivityTimeMillis);
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
index cf7e9e16..2f0eb6c 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelFeature.aidl
@@ -22,6 +22,8 @@
 import android.telephony.ims.aidl.IImsCapabilityCallback;
 import android.telephony.ims.aidl.ISrvccStartedCallback;
 import android.telephony.ims.feature.CapabilityChangeRequest;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.telephony.ims.RtpHeaderExtensionType;
 
 import android.telephony.ims.ImsCallProfile;
@@ -60,6 +62,9 @@
     oneway void notifySrvccCompleted();
     oneway void notifySrvccFailed();
     oneway void notifySrvccCanceled();
+    oneway void setMediaQualityThreshold(int mediaSessionType, in MediaThreshold threshold);
+    MediaQualityStatus queryMediaQualityStatus(int mediaSessionType);
+
     // SMS APIs
     void setSmsListener(IImsSmsListener l);
     oneway void sendSms(in int token, int messageRef, String format, String smsc, boolean retry,
diff --git a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
index b8701f1..e016c61 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsMmTelListener.aidl
@@ -20,6 +20,7 @@
 
 import android.telephony.ims.ImsCallProfile;
 import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.MediaQualityStatus;
 import android.telephony.ims.aidl.IImsCallSessionListener;
 import android.telephony.ims.aidl.IImsTrafficSessionCallback;
 
@@ -42,4 +43,5 @@
             int trafficDirection, in IImsTrafficSessionCallback callback);
     oneway void onModifyImsTrafficSession(int token, int accessNetworkType);
     oneway void onStopImsTrafficSession(int token);
+    oneway void onMediaQualityStatusChanged(in MediaQualityStatus status);
 }
diff --git a/telephony/java/android/telephony/ims/feature/MmTelFeature.java b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
index f412116..1686f38 100644
--- a/telephony/java/android/telephony/ims/feature/MmTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MmTelFeature.java
@@ -34,6 +34,8 @@
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsReasonInfo;
 import android.telephony.ims.ImsService;
+import android.telephony.ims.MediaQualityStatus;
+import android.telephony.ims.MediaThreshold;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.SrvccCall;
 import android.telephony.ims.aidl.IImsCallSessionListener;
@@ -257,7 +259,7 @@
         public void changeCapabilitiesConfiguration(CapabilityChangeRequest request,
                 IImsCapabilityCallback c) {
             executeMethodAsyncNoException(() -> MmTelFeature.this
-                    .requestChangeEnabledCapabilities(request, c),
+                            .requestChangeEnabledCapabilities(request, c),
                     "changeCapabilitiesConfiguration");
         }
 
@@ -269,6 +271,26 @@
         }
 
         @Override
+        public void setMediaQualityThreshold(@MediaQualityStatus.MediaSessionType int sessionType,
+                MediaThreshold mediaThreshold) {
+            if (mediaThreshold != null) {
+                executeMethodAsyncNoException(() -> setMediaThreshold(sessionType, mediaThreshold),
+                        "setMediaQualityThreshold");
+            } else {
+                executeMethodAsyncNoException(() -> clearMediaThreshold(sessionType),
+                        "clearMediaQualityThreshold");
+            }
+        }
+
+        @Override
+        public MediaQualityStatus queryMediaQualityStatus(
+                @MediaQualityStatus.MediaSessionType int sessionType)
+                throws RemoteException {
+            return executeMethodAsyncForResult(() -> MmTelFeature.this.queryMediaQualityStatus(
+                    sessionType), "queryMediaQualityStatus");
+        }
+
+        @Override
         public void setSmsListener(IImsSmsListener l) {
             executeMethodAsyncNoException(() -> MmTelFeature.this.setSmsListener(l),
                     "setSmsListener", getImsSmsImpl().getExecutor());
@@ -696,6 +718,17 @@
         public void onStopImsTrafficSession(int token) {
 
         }
+
+        /**
+         * Called when the IMS provider notifies {@link MediaQualityStatus}.
+         *
+         * @param status media quality status currently measured.
+         * @hide
+         */
+        @Override
+        public void onMediaQualityStatusChanged(MediaQualityStatus status) {
+
+        }
     }
 
     /**
@@ -1060,6 +1093,32 @@
     }
 
     /**
+     * Notify the framework that the measured media quality has crossed a threshold set by {@link
+     * MmTelFeature#setMediaThreshold}
+     *
+     * @param status current media quality status measured.
+     * @hide
+     */
+    @SystemApi
+    public final void notifyMediaQualityStatusChanged(
+            @NonNull MediaQualityStatus status) {
+        if (status == null) {
+            throw new IllegalArgumentException(
+                    "MediaQualityStatus must be non-null!");
+        }
+        Log.i(LOG_TAG, "notifyMediaQualityStatusChanged " + status);
+        IImsMmTelListener listener = getListener();
+        if (listener == null) {
+            throw new IllegalStateException("Session is not available.");
+        }
+        try {
+            listener.onMediaQualityStatusChanged(status);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Notify the framework of an incoming call.
      * @param c The {@link ImsCallSessionImplBase} of the new incoming call.
      * @param extras A bundle containing extra parameters related to the call. See
@@ -1392,6 +1451,62 @@
     }
 
     /**
+     * Called by the framework to pass {@link MediaThreshold}. The MmTelFeature should override this
+     * method to get Media quality threshold. This will pass the consolidated threshold values from
+     * Telephony framework. IMS provider needs to monitor media quality of active call and notify
+     * media quality {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the measured
+     * media quality crosses at least one of {@link MediaThreshold} set by this.
+     *
+     * @param mediaSessionType media session type for this Threshold info.
+     * @param mediaThreshold media threshold information
+     * @hide
+     */
+    @SystemApi
+    public void setMediaThreshold(
+            @MediaQualityStatus.MediaSessionType int mediaSessionType,
+            @NonNull MediaThreshold mediaThreshold) {
+        // Base Implementation - Should be overridden.
+        Log.d(LOG_TAG, "setMediaThreshold is not supported." + mediaThreshold);
+    }
+
+    /**
+     * The MmTelFeature should override this method to clear Media quality thresholds that were
+     * registered and stop media quality status updates.
+     *
+     * @param mediaSessionType media session type
+     * @hide
+     */
+    @SystemApi
+    public void clearMediaThreshold(@MediaQualityStatus.MediaSessionType int mediaSessionType) {
+        // Base Implementation - Should be overridden.
+        Log.d(LOG_TAG, "clearMediaThreshold is not supported." + mediaSessionType);
+    }
+
+    /**
+     * IMS provider should override this method to return currently measured media quality status.
+     *
+     * <p/>
+     * If media quality status is not yet measured after call is active, it needs to notify media
+     * quality status {@link #notifyMediaQualityStatusChanged(MediaQualityStatus)} when the first
+     * measurement is done.
+     *
+     * @param mediaSessionType media session type
+     * @return Current media quality status. It could be null if media quality status is not
+     *         measured yet or {@link MediaThreshold} was not set corresponding to the media session
+     *         type.
+     *
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    public MediaQualityStatus queryMediaQualityStatus(
+            @MediaQualityStatus.MediaSessionType int mediaSessionType) {
+        // Base Implementation - Should be overridden.
+        Log.d(LOG_TAG, "queryMediaQualityStatus is not supported." + mediaSessionType);
+        return null;
+    }
+
+    /**
      * Creates a {@link ImsCallProfile} from the service capabilities & IMS registration state.
      *
      * @param callSessionType a service type that is specified in {@link ImsCallProfile}
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/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/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/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp
index 612e3a6..7381a85 100644
--- a/tools/aapt2/cmd/Convert.cpp
+++ b/tools/aapt2/cmd/Convert.cpp
@@ -364,7 +364,8 @@
   }
   std::unordered_set<ResourceName> resources_exclude_list;
   bool result = ParseResourceConfig(content, context, resources_exclude_list,
-                                    out_options.name_collapse_exemptions);
+                                    out_options.name_collapse_exemptions,
+                                    out_options.path_shorten_exemptions);
   if (!result) {
     return false;
   }
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d7a39bf..dbe7970 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -322,7 +322,8 @@
     return false;
   }
   return ParseResourceConfig(content, context, options->resources_exclude_list,
-                             options->table_flattener_options.name_collapse_exemptions);
+                             options->table_flattener_options.name_collapse_exemptions,
+                             options->table_flattener_options.path_shorten_exemptions);
 }
 
 bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h
index 1879f25..ee53af1 100644
--- a/tools/aapt2/cmd/Optimize.h
+++ b/tools/aapt2/cmd/Optimize.h
@@ -122,7 +122,8 @@
             "--resources-config-path.",
         &options_.table_flattener_options.collapse_key_stringpool);
     AddOptionalSwitch("--shorten-resource-paths",
-        "Shortens the paths of resources inside the APK.",
+        "Shortens the paths of resources inside the APK. Resources can be exempted using the \n"
+        "\"no_path_shorten\" directive in a file specified by --resources-config-path.",
         &options_.shorten_resource_paths);
     // TODO(b/246489170): keep the old option and format until transform to the new one
     AddOptionalFlag("--resource-path-shortening-map",
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 92849cf..1671e1e 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -448,7 +448,8 @@
 
 bool ParseResourceConfig(const std::string& content, IAaptContext* context,
                          std::unordered_set<ResourceName>& out_resource_exclude_list,
-                         std::set<ResourceName>& out_name_collapse_exemptions) {
+                         std::set<ResourceName>& out_name_collapse_exemptions,
+                         std::set<ResourceName>& out_path_shorten_exemptions) {
   for (StringPiece line : util::Tokenize(content, '\n')) {
     line = util::TrimWhitespace(line);
     if (line.empty()) {
@@ -477,6 +478,8 @@
         out_resource_exclude_list.insert(resource_name.ToResourceName());
       } else if (directive == "no_collapse" || directive == "no_obfuscate") {
         out_name_collapse_exemptions.insert(resource_name.ToResourceName());
+      } else if (directive == "no_path_shorten") {
+        out_path_shorten_exemptions.insert(resource_name.ToResourceName());
       }
     }
   }
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 169d5f9..712c07b 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -81,7 +81,8 @@
 
 bool ParseResourceConfig(const std::string& content, IAaptContext* context,
                          std::unordered_set<ResourceName>& out_resource_exclude_list,
-                         std::set<ResourceName>& out_name_collapse_exemptions);
+                         std::set<ResourceName>& out_name_collapse_exemptions,
+                         std::set<ResourceName>& out_path_shorten_exemptions);
 
 }  // namespace aapt
 
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 28a6de8..139bfbc 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -416,19 +416,27 @@
   const std::string& content = R"(
 bool/remove_me#remove
 bool/keep_name#no_collapse
+layout/keep_path#no_path_shorten
 string/foo#no_obfuscate
 dimen/bar#no_obfuscate
+layout/keep_name_and_path#no_collapse,no_path_shorten
 )";
   aapt::test::Context context;
   std::unordered_set<ResourceName> resource_exclusion;
   std::set<ResourceName> name_collapse_exemptions;
+  std::set<ResourceName> path_shorten_exemptions;
 
-  EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+  EXPECT_TRUE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+                                  path_shorten_exemptions));
 
   EXPECT_THAT(name_collapse_exemptions,
               UnorderedElementsAre(ResourceName({}, ResourceType::kString, "foo"),
                                    ResourceName({}, ResourceType::kDimen, "bar"),
-                                   ResourceName({}, ResourceType::kBool, "keep_name")));
+                                   ResourceName({}, ResourceType::kBool, "keep_name"),
+                                   ResourceName({}, ResourceType::kLayout, "keep_name_and_path")));
+  EXPECT_THAT(path_shorten_exemptions,
+              UnorderedElementsAre(ResourceName({}, ResourceType::kLayout, "keep_path"),
+                                   ResourceName({}, ResourceType::kLayout, "keep_name_and_path")));
   EXPECT_THAT(resource_exclusion,
               UnorderedElementsAre(ResourceName({}, ResourceType::kBool, "remove_me")));
 }
@@ -440,9 +448,10 @@
   aapt::test::Context context;
   std::unordered_set<ResourceName> resource_exclusion;
   std::set<ResourceName> name_collapse_exemptions;
+  std::set<ResourceName> path_shorten_exemptions;
 
-  EXPECT_FALSE(
-      ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+  EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+                                   path_shorten_exemptions));
 }
 
 TEST(UtilTest, ParseConfigInvalidName) {
@@ -452,9 +461,10 @@
   aapt::test::Context context;
   std::unordered_set<ResourceName> resource_exclusion;
   std::set<ResourceName> name_collapse_exemptions;
+  std::set<ResourceName> path_shorten_exemptions;
 
-  EXPECT_FALSE(
-      ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+  EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+                                   path_shorten_exemptions));
 }
 
 TEST(UtilTest, ParseConfigNoHash) {
@@ -464,9 +474,10 @@
   aapt::test::Context context;
   std::unordered_set<ResourceName> resource_exclusion;
   std::set<ResourceName> name_collapse_exemptions;
+  std::set<ResourceName> path_shorten_exemptions;
 
-  EXPECT_FALSE(
-      ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions));
+  EXPECT_FALSE(ParseResourceConfig(content, &context, resource_exclusion, name_collapse_exemptions,
+                                   path_shorten_exemptions));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h
index 60605d2..0633bc81 100644
--- a/tools/aapt2/format/binary/TableFlattener.h
+++ b/tools/aapt2/format/binary/TableFlattener.h
@@ -60,6 +60,9 @@
   // Set of resources to avoid collapsing to a single entry in key stringpool.
   std::set<ResourceName> name_collapse_exemptions;
 
+  // Set of resources to avoid path shortening.
+  std::set<ResourceName> path_shorten_exemptions;
+
   // Map from original resource paths to shortened resource paths.
   std::map<std::string, std::string> shortened_path_map;
 
diff --git a/tools/aapt2/optimize/Obfuscator.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index cc21093..8f12f735 100644
--- a/tools/aapt2/optimize/Obfuscator.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -83,13 +83,18 @@
 };
 
 static bool HandleShortenFilePaths(ResourceTable* table,
-                                   std::map<std::string, std::string>& shortened_path_map) {
+                                   std::map<std::string, std::string>& shortened_path_map,
+                                   const std::set<ResourceName>& path_shorten_exemptions) {
   // used to detect collisions
   std::unordered_set<std::string> shortened_paths;
   std::set<FileReference*, PathComparator> file_refs;
   for (auto& package : table->packages) {
     for (auto& type : package->types) {
       for (auto& entry : type->entries) {
+        ResourceName resource_name({}, type->named_type, entry->name);
+        if (path_shorten_exemptions.find(resource_name) != path_shorten_exemptions.end()) {
+          continue;
+        }
         for (auto& config_value : entry->values) {
           FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
           if (file_ref) {
@@ -188,7 +193,8 @@
   HandleCollapseKeyStringPool(table, options_.collapse_key_stringpool,
                               options_.name_collapse_exemptions, options_.id_resource_map);
   if (shorten_resource_paths_) {
-    return HandleShortenFilePaths(table, options_.shortened_path_map);
+    return HandleShortenFilePaths(table, options_.shortened_path_map,
+                                  options_.path_shorten_exemptions);
   }
   return true;
 }
diff --git a/tools/aapt2/optimize/Obfuscator_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index 7f57b71..940cf10 100644
--- a/tools/aapt2/optimize/Obfuscator_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -28,6 +28,7 @@
 using ::testing::AnyOf;
 using ::testing::Eq;
 using ::testing::HasSubstr;
+using ::testing::IsFalse;
 using ::testing::IsTrue;
 using ::testing::Not;
 using ::testing::NotNull;
@@ -102,6 +103,44 @@
   ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
 }
 
+TEST(ObfuscatorTest, SkipPathShortenExemptions) {
+  std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .AddFileReference("android:drawable/xmlfile", "res/drawables/xmlfile.xml")
+          .AddFileReference("android:drawable/xmlfile2", "res/drawables/xmlfile2.xml")
+          .AddString("android:string/string", "res/should/still/be/the/same.png")
+          .Build();
+
+  OptimizeOptions options{.shorten_resource_paths = true};
+  TableFlattenerOptions& flattenerOptions = options.table_flattener_options;
+  flattenerOptions.path_shorten_exemptions.insert(
+      ResourceName({}, ResourceType::kDrawable, "xmlfile"));
+  std::map<std::string, std::string>& path_map = options.table_flattener_options.shortened_path_map;
+  ASSERT_TRUE(Obfuscator(options).Consume(context.get(), table.get()));
+
+  // Expect that the path map to not contain the first drawable which is in exemption set
+  EXPECT_THAT(path_map.find("res/drawables/xmlfile.xml"), Eq(path_map.end()));
+
+  // Expect that the path map to contain the second drawable which is not in exemption set
+  EXPECT_THAT(path_map.find("res/drawables/xmlfile2.xml"), Not(Eq(path_map.end())));
+
+  FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
+  EXPECT_THAT(ref, NotNull());
+  ASSERT_THAT(HasFailure(), IsFalse());
+  // The path of first drawable in exemption was not changed
+  EXPECT_THAT("res/drawables/xmlfile.xml", Eq(*ref->path));
+
+  // The file path of second drawable not in exemption set was changed
+  EXPECT_THAT(path_map.at("res/drawables/xmlfile2.xml"), Not(Eq("res/drawables/xmlfile2.xml")));
+
+  FileReference* ref2 = GetValue<FileReference>(table.get(), "android:drawable/xmlfile2");
+  ASSERT_THAT(ref, NotNull());
+  // The map of second drawable not in exemption correctly points to the new location of the file
+  EXPECT_THAT(path_map["res/drawables/xmlfile2.xml"], Eq(*ref2->path));
+}
+
 TEST(ObfuscatorTest, KeepExtensions) {
   std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
 
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;
     }
 
     /**